diff --git a/Jenkinsfile b/Jenkinsfile index 75a6bd384..e1215375e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -7,8 +7,8 @@ pipeline { } environment { REGISTRY = 'registry.verdnatura.es' - DOCKER_HOST_1 = 'tcp://vch1.verdnatura.es:2376' - DOCKER_HOST_2 = 'tcp://vch2.verdnatura.es:2376' + DOCKER_HOST_1 = 'vch1.verdnatura.es' + DOCKER_HOST_2 = 'vch2.verdnatura.es' TAG = "${env.BRANCH_NAME}" } stages { @@ -44,14 +44,14 @@ pipeline { echo "Committer: ${env.GIT_COMMITTER_EMAIL}" } } - stage('Build') { + stage('Install') { + environment { + NODE_ENV = "" + } steps { nodejs('node-lts') { - withEnv(['NODE_ENV=']) { - sh 'npm install --no-audit' - sh 'gulp install' - } - sh 'gulp build' + sh 'npm install --no-audit' + sh 'gulp install' } } } @@ -62,18 +62,16 @@ pipeline { environment { NODE_ENV = "" FIREFOX_BIN = "/opt/firefox/firefox-bin" + DB_HOST = "${env.DOCKER_HOST_2}" } steps { nodejs('node-lts') { sh 'karma start --junit' - - sh 'gulp docker' - // sh 'gulp backendUnitTest --junit' // FIXME: Docker isn't at localhost - sh 'docker rm -f salix-db' + sh 'gulp backTestDocker --junit --random --run-chown' } } } - stage('Docker') { + stage('Build') { when { not { branch 'dev' } } @@ -81,6 +79,10 @@ pipeline { CREDS = credentials('docker-registry') } steps { + nodejs('node-lts') { + sh 'gulp build' + } + sh 'docker login --username $CREDS_USR --password $CREDS_PSW $REGISTRY' sh 'docker-compose build --parallel' sh 'docker-compose push' @@ -96,7 +98,7 @@ pipeline { parallel { stage('Host 1') { environment { - DOCKER_HOST = "${env.DOCKER_HOST_1}" + DOCKER_HOST = "tcp://${env.DOCKER_HOST_1}:2376" } steps { withCredentials([dockerCert(credentialsId: 'docker', variable: 'DOCKER_CERT_PATH')]) { @@ -107,7 +109,7 @@ pipeline { } stage('Host 2') { environment { - DOCKER_HOST = "${env.DOCKER_HOST_2}" + DOCKER_HOST = "tcp://${env.DOCKER_HOST_2}:2376" } steps { withCredentials([dockerCert(credentialsId: 'docker', variable: 'DOCKER_CERT_PATH')]) { @@ -130,8 +132,10 @@ pipeline { post { always { script { - if (env.BRANCH_NAME == 'dev') + if (env.BRANCH_NAME == 'dev') { junit '*/junitresults.xml' + junit 'junitresults.xml' + } if (!env.GIT_COMMITTER_EMAIL) return try { diff --git a/gulpfile.js b/gulpfile.js index b57ec7c81..d69dd5d0d 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -16,6 +16,13 @@ let langs = ['es', 'en']; let srcDir = './front'; let modulesDir = './modules'; let buildDir = 'dist'; +let containerId = 'salix-db'; + +let dataSources = require('./loopback/server/datasources.json'); +let dbConf = dataSources.vn; + +if (process.env.DB_HOST) + dbConf.host = process.env.DB_HOST; let backSources = [ '!node_modules', @@ -67,43 +74,55 @@ defaultTask.description = `Starts all application services`; // Backend tests -function backendUnitTest() { +async function backTestOnly() { + let bootOptions; + + if (argv['random']) + bootOptions = {dataSources}; + let app = require(`./loopback/server/server`); + app.boot(bootOptions); - let specFiles = [ - 'back/**/*.spec.js', - 'loopback/**/*.spec.js', - 'modules/*/back/**/*.spec.js' - ]; + await new Promise((resolve, reject) => { + const jasmine = require('gulp-jasmine'); - const jasmine = require('gulp-jasmine'); - let options = {errorOnFail: false}; + let options = {errorOnFail: false}; - if (argv.junit || argv.j) { - const reporters = require('jasmine-reporters'); - options.reporter = new reporters.JUnitXmlReporter(); - } + if (argv.junit) { + const reporters = require('jasmine-reporters'); + options.reporter = new reporters.JUnitXmlReporter(); + } - return gulp.src(specFiles) - .pipe(jasmine(options)) - .on('jasmineDone', function() { - app.disconnect(); - }); + let backSpecFiles = [ + 'back/**/*.spec.js', + 'loopback/**/*.spec.js', + 'modules/*/back/**/*.spec.js' + ]; + + gulp.src(backSpecFiles) + .pipe(jasmine(options)) + .on('end', resolve) + .resume(); + }); + + await app.disconnect(); } -backendUnitTest.description = `Runs the backend tests only, can receive args --junit or -j to save reports on a xml file`; +backTestOnly.description = `Runs the backend tests only, can receive --junit arg to save reports on a xml file`; -const dockerAndBackTest = gulp.series(docker, backendUnitTest); -dockerAndBackTest.description = `Restarts database and runs the backend tests`; +async function backTestDocker() { + let containerId = await docker(); + await backTestOnly(); + if (argv['random']) + await execP(`docker rm -fv ${containerId}`); +} +backTestDocker.description = `Runs backend tests using in site container`; function backTest(done) { const nodemon = require('gulp-nodemon'); - let gulpBin = isWindows - ? 'node_modules/.bin/gulp.cmd' - : 'node_modules/.bin/gulp'; nodemon({ - exec: gulpBin, - args: ['backendUnitTest'], + exec: ['node ./node_modules/gulp/bin/gulp.js'], + args: ['backTestOnly'], watch: backSources, done: done }); @@ -325,20 +344,35 @@ watch.description = `Watches for changes in routes and locale files`; * to avoid a bug with OverlayFS driver on MacOS. */ async function docker() { - try { - await execP('docker rm -fv salix-db'); - } catch (e) {} - let d = new Date(); let pad = v => v < 10 ? '0' + v : v; let stamp = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; await execP(`docker build --build-arg STAMP=${stamp} -t salix-db ./services/db`); + let dockerArgs = `--name ${containerId} -p 3306:${dbConf.port}`; + + if (argv['random']) + dockerArgs = '-p 3306'; + else { + try { + await execP(`docker rm -fv ${containerId}`); + } catch (e) {} + } + let runChown = process.platform != 'linux'; if (argv['run-chown']) runChown = true; - await execP(`docker run --env RUN_CHOWN=${runChown} -d --name salix-db -p 3306:3306 salix-db`); + let result = await execP(`docker run --env RUN_CHOWN=${runChown} -d ${dockerArgs} salix-db`); + containerId = result.stdout; + + if (argv['random']) { + let inspect = await execP(`docker inspect -f "{{json .NetworkSettings.Ports}}" ${containerId}`); + let ports = JSON.parse(inspect.stdout); + dbConf.port = ports['3306/tcp'][0]['HostPort']; + } + if (runChown) await dockerWait(); + return containerId; } docker.description = `Builds the database image and runs a container`; @@ -351,7 +385,7 @@ docker.description = `Builds the database image and runs a container`; async function dockerStart() { let state; try { - let result = await execP('docker container inspect -f "{{json .State}}" salix-db'); + let result = await execP(`docker inspect -f "{{json .State}}" ${containerId}`); state = JSON.parse(result.stdout); } catch (err) { return await docker(); @@ -361,7 +395,7 @@ async function dockerStart() { case 'running': return; case 'exited': - await execP('docker start salix-db'); + await execP(`docker start ${containerId}`); await dockerWait(); return; default: @@ -376,7 +410,14 @@ function dockerWait() { let interval = 100; let elapsedTime = 0; - let maxInterval = 5 * 60 * 1000; + let maxInterval = 4 * 60 * 1000; + + let myConf = { + user: dbConf.username, + password: dbConf.password, + host: dbConf.host, + port: dbConf.port + }; log('Waiting for MySQL init process...'); checker(); @@ -386,7 +427,7 @@ function dockerWait() { let state; try { - let result = await execP('docker container inspect -f "{{json .State}}" salix-db'); + let result = await execP(`docker container inspect -f "{{json .State}}" ${containerId}`); state = JSON.parse(result.stdout); } catch (err) { return reject(new Error(err.message)); @@ -395,18 +436,17 @@ function dockerWait() { if (state.Status === 'exited') return reject(new Error('Docker exited, please see the docker logs for more info')); - let conn = mysql.createConnection({ - host: 'localhost', - user: 'root', - password: 'root' - }); + let conn = mysql.createConnection(myConf); conn.on('error', () => {}); conn.connect(err => { conn.destroy(); - if (!err) return resolve(); + if (!err) { + log('MySQL process ready.'); + return resolve(); + } if (elapsedTime >= maxInterval) - reject(new Error(`MySQL not initialized whithin ${elapsedTime} secs`)); + reject(new Error(`MySQL not initialized whithin ${elapsedTime / 1000} secs`)); else setTimeout(checker, interval); }); @@ -444,21 +484,21 @@ module.exports = { back, backOnly, backWatch, - backendUnitTest, - dockerAndBackTest, backTest, - e2eOnly, + backTestOnly, + backTestDocker, e2e, - smokesOnly, + e2eOnly, smokes, - install, + smokesOnly, i, + install, build, clean, webpack, webpackDevServer, - locales, routes, + locales, localesRoutes, watch, docker, diff --git a/loopback/server/server.js b/loopback/server/server.js index 3d8413671..188b5eb0f 100644 --- a/loopback/server/server.js +++ b/loopback/server/server.js @@ -34,98 +34,106 @@ let modulesDir = `${appDir}/modules`; // Internationalization -if (fs.existsSync(localeDir)) { - i18n.configure({ - directory: localeDir, - defaultLocale: 'es' - }); - - app.use(i18n.init); -} - -// Initialization - app.disconnect = async function() { let promises = []; - for (let ds in app.dataSources) - promises.push(app.dataSources[ds].disconnect()); + for (let ds in this.dataSources) + promises.push(this.dataSources[ds].disconnect()); return await Promise.all(promises); }; -app.start = function(port) { - function onListen() { - let baseUrl = app.get('url').replace(/\/$/, ''); - let explorerPath = app.get('loopback-component-explorer').mountPath; +app.start = function(bootOptions, port, callback) { + let onListen = () => { + let baseUrl = this.get('url').replace(/\/$/, ''); + let explorerPath = this.get('loopback-component-explorer').mountPath; console.log(`Browse your REST API at: %s`, `${baseUrl}${explorerPath}`); - app.emit('started'); - } + this.emit('started'); + callback && callback(); + }; - let args = port ? [port, onListen] : [onListen]; - return app.listen(...args); + this.boot(bootOptions, err => { + if (err) throw err; + let args = port ? [port, onListen] : [onListen]; + return this.listen(...args); + }); }; -let config = require('./config.json'); +app.boot = function(bootOptions, callback) { + // Internatinalization -for (let key in config) - app.set(key, config[key]); + if (fs.existsSync(localeDir)) { + i18n.configure({ + directory: localeDir, + defaultLocale: 'es' + }); -let modelConfigFiles = [ - `${__dirname}/model-config.json` -]; -let modelSources = [ - `loopback/common/models`, - `loopback/server/models`, - `${__dirname}/../common/models` -]; -let mixinDirs = [ - `loopback/common/mixins`, - `loopback/server/mixins`, - `${__dirname}/../common/mixins` -]; -let bootDirs = [ - `${__dirname}/boot` -]; + this.use(i18n.init); + } -addPath(`${appDir}/back`); + // Initialization -let modules = fs.readdirSync(modulesDir); -for (let mod of modules) - addPath(`${modulesDir}/${mod}/back`); + let config = require('./config.json'); -function addPath(path) { - modelConfigFiles.push(`${path}/model-config.json`); - modelSources.push(`${path}/models`); - mixinDirs.push(`${path}/mixins`); - bootDirs.push(`${path}/boot`); -} + for (let key in config) + this.set(key, config[key]); -let models = {}; -for (file of modelConfigFiles) { - if (fs.existsSync(file)) { - let fileModels = require(file); - for (let key in fileModels) { - if (models[key]) - console.warn(`Redeclaration of '${key}' at ${file}`); + let modelConfigFiles = [ + `${__dirname}/model-config.json` + ]; + let modelSources = [ + `loopback/common/models`, + `loopback/server/models`, + `${__dirname}/../common/models` + ]; + let mixinDirs = [ + `loopback/common/mixins`, + `loopback/server/mixins`, + `${__dirname}/../common/mixins` + ]; + let bootDirs = [ + `${__dirname}/boot` + ]; + + addPath(`${appDir}/back`); + + let modules = fs.readdirSync(modulesDir); + for (let mod of modules) + addPath(`${modulesDir}/${mod}/back`); + + function addPath(path) { + modelConfigFiles.push(`${path}/model-config.json`); + modelSources.push(`${path}/models`); + mixinDirs.push(`${path}/mixins`); + bootDirs.push(`${path}/boot`); + } + + let models = {}; + for (file of modelConfigFiles) { + if (fs.existsSync(file)) { + let fileModels = require(file); + for (let key in fileModels) { + if (models[key]) + console.warn(`Redeclaration of '${key}' at ${file}`); + } + Object.assign(models, fileModels); } - Object.assign(models, fileModels); } -} -let bootOptions = { - appRootDir: __dirname, - appConfigRootDir: rootDir, - modelsRootDir: rootDir, - models: models, - modelSources: modelSources, - mixinDirs: mixinDirs, - bootDirs: bootDirs + let myBootOptions = { + appRootDir: __dirname, + appConfigRootDir: rootDir, + modelsRootDir: rootDir, + models: models, + modelSources: modelSources, + mixinDirs: mixinDirs, + bootDirs: bootDirs + }; + + if (fs.existsSync(`/etc/salix`)) + myBootOptions.dsRootDir = `/etc/salix`; + + Object.assign(myBootOptions, bootOptions); + boot(this, myBootOptions, callback); }; -if (fs.existsSync(`/etc/salix`)) - bootOptions.dsRootDir = `/etc/salix`; - -boot(app, bootOptions, function(err) { - if (err) throw err; - if (require.main === module) - app.start(); -}); +if (require.main === module) + app.start(); diff --git a/modules/claim/front/descriptor/index.html b/modules/claim/front/descriptor/index.html index 91cc5802d..27d984fb6 100644 --- a/modules/claim/front/descriptor/index.html +++ b/modules/claim/front/descriptor/index.html @@ -70,8 +70,8 @@ + vn-id="confirm-pickup-order" + on-response="$ctrl.sendPickupOrder(response)" + question="Send Pickup order" + message="Are you sure you want to send it?"> \ No newline at end of file diff --git a/modules/claim/front/descriptor/index.js b/modules/claim/front/descriptor/index.js index abbdcf43f..266983c98 100644 --- a/modules/claim/front/descriptor/index.js +++ b/modules/claim/front/descriptor/index.js @@ -8,7 +8,8 @@ class Controller { this.$translate = $translate; this.vnApp = vnApp; this.moreOptions = [ - {callback: this.showConfirmDialog, name: 'Pickup order'} + {callback: this.showPickupOrder, name: 'Show Pickup order'}, + {callback: this.confirmPickupOrder, name: 'Send Pickup order'} ]; } @@ -47,15 +48,17 @@ class Controller { callback.call(this); } - showConfirmDialog() { - this.$scope.confirmDialog.show(); + showPickupOrder() { + let url = `/api/report/rpt-claim-pickup-order?claimFk=${this.claim.id}`; + window.open(url); } - returnDialog(response) { - if (response === 'CANCEL') { - let url = `/api/report/rpt-claim-pickup-order?claimFk=${this.claim.id}`; - window.open(url); - } else if (response === 'ACCEPT') { + confirmPickupOrder() { + this.$scope.confirmPickupOrder.show(); + } + + sendPickupOrder(response) { + if (response === 'ACCEPT') { this.$http.post(`/api/email/claim-pickup-order`, {claimFk: this.claim.id}).then( () => this.vnApp.showMessage(this.$translate.instant('Notification sent!')) ); diff --git a/modules/claim/front/descriptor/index.spec.js b/modules/claim/front/descriptor/index.spec.js new file mode 100644 index 000000000..97fa0f9c3 --- /dev/null +++ b/modules/claim/front/descriptor/index.spec.js @@ -0,0 +1,52 @@ +import './index.js'; + +describe('Item Component vnClaimDescriptor', () => { + let $httpBackend; + let controller; + + beforeEach(() => { + ngModule('claim'); + }); + + beforeEach(angular.mock.inject(($componentController, _$httpBackend_) => { + $httpBackend = _$httpBackend_; + controller = $componentController('vnClaimDescriptor'); + controller.claim = {id: 2}; + })); + + describe('showPickupOrder()', () => { + it('should open a new window showing a pickup order PDF document', () => { + let expectedPath = '/api/report/rpt-claim-pickup-order?claimFk=2'; + spyOn(window, 'open'); + controller.showPickupOrder(); + + expect(window.open).toHaveBeenCalledWith(expectedPath); + }); + }); + + describe('confirmPickupOrder()', () => { + it('should call confirmPickupOrder.show()', () => { + controller.$scope.confirmPickupOrder = { + show: jasmine.createSpy('show') + }; + controller.claim = {id: 2}; + controller.confirmPickupOrder(); + + expect(controller.$scope.confirmPickupOrder.show).toHaveBeenCalledWith(); + }); + }); + + describe('sendPickupOrder(response)', () => { + it('should make a query and call vnApp.showMessage() if the response is ACCEPT', () => { + spyOn(controller.vnApp, 'showMessage'); + + $httpBackend.when('POST', `/api/email/claim-pickup-order`, {claimFk: 2}).respond(); + $httpBackend.expect('POST', `/api/email/claim-pickup-order`, {claimFk: 2}).respond(); + controller.sendPickupOrder('ACCEPT'); + $httpBackend.flush(); + + expect(controller.vnApp.showMessage).toHaveBeenCalledWith('Notification sent!'); + }); + }); +}); + diff --git a/modules/claim/front/locale/es.yml b/modules/claim/front/locale/es.yml index d348c8ff1..307494dcd 100644 --- a/modules/claim/front/locale/es.yml +++ b/modules/claim/front/locale/es.yml @@ -1,5 +1,6 @@ #Ordenar alfabeticamente Add sale: Añadir linea +Are you sure you want to send it?: ¿Seguro que quieres enviarlo? Client Id: Id cliente Claimed ticket: Ticket reclamado Observation: Observación @@ -7,5 +8,6 @@ Responsible: Responsable Remove sale: Borrar linea Claim Id: Id reclamación Created: Creado -Pickup order: Orden de recogida -Do you want to send it directly?: ¿Quieres enviarlo directamente? +Send Pickup order: Enviar orden de recogida +Show Pickup order: Ver orden de recogida + diff --git a/modules/client/front/billing-data/index.js b/modules/client/front/billing-data/index.js index fd7a0e8b8..a23f5fa9e 100644 --- a/modules/client/front/billing-data/index.js +++ b/modules/client/front/billing-data/index.js @@ -17,6 +17,9 @@ export default class Controller { if (!value) return; + if (!value.bankEntityFk) + this.autofillBic(); + this.newBankEntity = { countryFk: Number.parseInt(value.countryFk) };