From dd8e03dd0fa8fc6ee986a3d86cc70d75669370b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20S=C3=A1nchez?= Date: Tue, 7 Jul 2020 14:29:57 +0200 Subject: [PATCH 01/22] Added absence selection --- .../10200-normality/00-workerCalendar.sql | 11 ++ .../back/methods/worker/createAbsences.js | 101 ++++++++++++++++++ .../worker/back/models/worker-calendar.json | 11 +- modules/worker/back/models/worker.js | 1 + modules/worker/front/calendar/index.html | 69 +++++++++++- modules/worker/front/calendar/index.js | 89 +++++++++++++-- modules/worker/front/calendar/style.scss | 13 +++ 7 files changed, 274 insertions(+), 21 deletions(-) create mode 100644 db/changes/10200-normality/00-workerCalendar.sql create mode 100644 modules/worker/back/methods/worker/createAbsences.js diff --git a/db/changes/10200-normality/00-workerCalendar.sql b/db/changes/10200-normality/00-workerCalendar.sql new file mode 100644 index 000000000..04b30394f --- /dev/null +++ b/db/changes/10200-normality/00-workerCalendar.sql @@ -0,0 +1,11 @@ +USE `vn`; +CREATE + OR REPLACE ALGORITHM = UNDEFINED + DEFINER = `root`@`%` + SQL SECURITY DEFINER +VIEW `workerCalendar2` AS + SELECT + `ce`.`business_id` AS `businessFk`, + `ce`.`calendar_state_id` AS `absenceTypeFk`, + `ce`.`date` AS `dated` + FROM `postgresql`.`calendar_employee` `ce`; diff --git a/modules/worker/back/methods/worker/createAbsences.js b/modules/worker/back/methods/worker/createAbsences.js new file mode 100644 index 000000000..e744d4796 --- /dev/null +++ b/modules/worker/back/methods/worker/createAbsences.js @@ -0,0 +1,101 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('createAbsences', { + description: 'Returns an array of absences from an specified worker', + accepts: [{ + arg: 'id', + type: 'Number', + description: 'The worker id', + http: {source: 'path'} + }, + { + arg: 'absenceTypeId', + type: 'Number', + required: true + }, + { + arg: 'dated', + type: 'Date', + required: false + }, + { + arg: 'started', + type: 'Date', + required: false + }, + { + arg: 'ended', + type: 'Date', + required: false + }, + { + arg: 'type', + type: 'String', + required: true + }], + returns: 'Object', + http: { + path: `/:id/createAbsences`, + verb: 'POST' + } + }); + + Self.createAbsences = async(ctx, id, absenceTypeId, dated, started, ended, type) => { + const models = Self.app.models; + const isSubordinate = await models.Worker.isSubordinate(ctx, id); + + if (!isSubordinate) + throw new UserError(`You don't have enough privileges`); + + const absences = []; + + if (type == 'day') { + const contracts = await models.WorkerLabour.find({ + where: { + and: [ + {workerFk: id}, + {or: [{ + ended: {gte: [yearStarted]} + }, {ended: null}]} + ] + } + }); + + absences.push({ + businessFk: 106, + absenceTypeFk: absenceTypeId, + dated: dated + }); + } else if (type == 'range') + console.log(true); + + return models.WorkerCalendar.create(absences); + }; +}; + +/* include: [{ + relation: 'holidays', + scope: { + where: {year} + } +}, +{ + relation: 'workCenter', + scope: { + include: { + relation: 'holidays', + scope: { + include: [{ + relation: 'detail' + }, + { + relation: 'type' + }], + where: { + dated: {between: [yearStarted, yearEnded]} + } + } + } + } +}], */ diff --git a/modules/worker/back/models/worker-calendar.json b/modules/worker/back/models/worker-calendar.json index 569d4d1ba..109b93c77 100644 --- a/modules/worker/back/models/worker-calendar.json +++ b/modules/worker/back/models/worker-calendar.json @@ -3,7 +3,7 @@ "base": "VnModel", "options": { "mysql": { - "table": "workerCalendar" + "table": "workerCalendar2" } }, "properties": { @@ -11,21 +11,12 @@ "id": 1, "type": "Number" }, - "workerFk": { - "id": 2, - "type": "Number" - }, "dated": { "id": 3, "type": "Date" } }, "relations": { - "worker": { - "type": "belongsTo", - "model": "Worker", - "foreignKey": "workerFk" - }, "absenceType": { "type": "belongsTo", "model": "AbsenceType", diff --git a/modules/worker/back/models/worker.js b/modules/worker/back/models/worker.js index 692c8c735..5f97c3e10 100644 --- a/modules/worker/back/models/worker.js +++ b/modules/worker/back/models/worker.js @@ -4,4 +4,5 @@ module.exports = Self => { require('../methods/worker/isSubordinate')(Self); require('../methods/worker/getWorkedHours')(Self); require('../methods/worker/uploadFile')(Self); + require('../methods/worker/createAbsences')(Self); }; diff --git a/modules/worker/front/calendar/index.html b/modules/worker/front/calendar/index.html index 08b3f469c..859da81b5 100644 --- a/modules/worker/front/calendar/index.html +++ b/modules/worker/front/calendar/index.html @@ -12,7 +12,8 @@ format-day="$ctrl.formatDay($day, $element)" display-controls="false" hide-contiguous="true" - hide-year="true"> + hide-year="true" + on-selection="$ctrl.onSelection($days, $type, $weekday)"> @@ -26,12 +27,74 @@
- + + {{absenceType.name}}
- \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/worker/front/calendar/index.js b/modules/worker/front/calendar/index.js index 6b849d19f..0a7f591db 100644 --- a/modules/worker/front/calendar/index.js +++ b/modules/worker/front/calendar/index.js @@ -43,15 +43,8 @@ class Controller extends Section { set worker(value) { this._worker = value; - if (!value) return; - let params = { - workerFk: this.worker.id, - started: this.started, - ended: this.ended - }; - this.$http.get(`WorkerCalendars/absences`, {params}) - .then(res => this.onData(res.data)); + if (value) this.refresh(); } onData(data) { @@ -102,6 +95,86 @@ class Controller extends Section { dayNumber.style.backgroundColor = event.color; dayNumber.style.color = 'rgba(0, 0, 0, 0.7)'; } + + pick(absenceType) { + if (absenceType == this.absenceType) + absenceType = null; + + this.absenceType = absenceType; + } + + onSelection($days, $type, $weekday) { + if (!this.absenceType) + return this.vnApp.showMessage(this.$t('Choose an absence type')); + + let $events = []; + for (let day of $days) { + const stamp = day.getTime(); + $events = $events.concat(this.events[stamp] || []); + } + + if ($events.length) + this.edit($events[0]); + else + this.create($type, $days, $weekday); + } + + create(type, days, weekday) { + this.isNew = true; + this.selected = { + type: 'day', + dated: days[0] + }; + + this.$.dialog.show(); + } + + edit() { + this.isNew = false; + } + + onAbsenceResponse(response) { + switch (response) { + case 'accept': { + const selected = this.selected; + const params = Object.assign(selected, { + absenceTypeId: this.absenceType.id + }); + + /* const selected = { + businessFk: 106, + absenceTypeFk: this.absenceType.id, + dated: $days[0] + }; */ + + let req; + if (this.isNew) { + const path = `Workers/${this.$params.id}/createAbsences`; + req = this.$http.post(path, params); + } else + req = this.$http.put(`${this.path}/${selected.id}`, selected); + + return req.then(() => { + this.selected = null; + this.isNew = null; + this.refresh(); + }); + } + case 'delete': + return this.onDelete(this.selected.id) + .then(response => response == 'accept'); + } + } + + refresh() { + const params = { + workerFk: this.worker.id, + started: this.started, + ended: this.ended + }; + this.$http.get(`WorkerCalendars/absences`, {params}) + .then(res => this.onData(res.data)); + } } ngModule.component('vnWorkerCalendar', { diff --git a/modules/worker/front/calendar/style.scss b/modules/worker/front/calendar/style.scss index 9b3fc749b..1c22fdfb0 100644 --- a/modules/worker/front/calendar/style.scss +++ b/modules/worker/front/calendar/style.scss @@ -16,4 +16,17 @@ vn-worker-calendar { max-width: 288px; } } + + vn-chip.selectable { + cursor: pointer + } + + vn-chip.selectable:hover { + opacity: 0.8 + } + + vn-chip vn-avatar { + text-align: center; + color: white + } } From 01b32d2b895b69bba84606141949e63b4a19c2af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20S=C3=A1nchez?= Date: Wed, 8 Jul 2020 15:05:56 +0200 Subject: [PATCH 02/22] Calendar changes --- .../10200-normality/01-calendar_employee.sql | 5 + ...rkerCalendar.sql => 02-workerCalendar.sql} | 1 + front/core/components/calendar/index.js | 2 +- .../back/methods/worker/createAbsence.js | 56 +++++++++ .../back/methods/worker/createAbsences.js | 101 --------------- .../back/methods/worker/deleteAbsence.js | 35 ++++++ .../back/methods/worker/updateAbsence.js | 40 ++++++ .../worker/back/models/worker-calendar.json | 6 +- modules/worker/back/models/worker.js | 4 +- modules/worker/front/calendar/index.html | 57 +-------- modules/worker/front/calendar/index.js | 118 ++++++++++-------- 11 files changed, 213 insertions(+), 212 deletions(-) create mode 100644 db/changes/10200-normality/01-calendar_employee.sql rename db/changes/10200-normality/{00-workerCalendar.sql => 02-workerCalendar.sql} (92%) create mode 100644 modules/worker/back/methods/worker/createAbsence.js delete mode 100644 modules/worker/back/methods/worker/createAbsences.js create mode 100644 modules/worker/back/methods/worker/deleteAbsence.js create mode 100644 modules/worker/back/methods/worker/updateAbsence.js diff --git a/db/changes/10200-normality/01-calendar_employee.sql b/db/changes/10200-normality/01-calendar_employee.sql new file mode 100644 index 000000000..c5db9da2c --- /dev/null +++ b/db/changes/10200-normality/01-calendar_employee.sql @@ -0,0 +1,5 @@ +ALTER TABLE `postgresql`.`calendar_employee` +ADD COLUMN `id` INT NULL AUTO_INCREMENT FIRST, +ADD UNIQUE INDEX `id_UNIQUE` (`id` ASC) VISIBLE, +ADD INDEX `id_index` (`id` ASC) VISIBLE; +; diff --git a/db/changes/10200-normality/00-workerCalendar.sql b/db/changes/10200-normality/02-workerCalendar.sql similarity index 92% rename from db/changes/10200-normality/00-workerCalendar.sql rename to db/changes/10200-normality/02-workerCalendar.sql index 04b30394f..479470ac0 100644 --- a/db/changes/10200-normality/00-workerCalendar.sql +++ b/db/changes/10200-normality/02-workerCalendar.sql @@ -5,6 +5,7 @@ CREATE SQL SECURITY DEFINER VIEW `workerCalendar2` AS SELECT + `ce`.`id` AS `id`, `ce`.`business_id` AS `businessFk`, `ce`.`calendar_state_id` AS `absenceTypeFk`, `ce`.`date` AS `dated` diff --git a/front/core/components/calendar/index.js b/front/core/components/calendar/index.js index 96bc435db..02d2a4798 100644 --- a/front/core/components/calendar/index.js +++ b/front/core/components/calendar/index.js @@ -135,7 +135,7 @@ export default class Calendar extends FormInput { $days: [day], $type: 'day' }); - this.repaint(); + // this.repaint(); } /* diff --git a/modules/worker/back/methods/worker/createAbsence.js b/modules/worker/back/methods/worker/createAbsence.js new file mode 100644 index 000000000..3f15b19c1 --- /dev/null +++ b/modules/worker/back/methods/worker/createAbsence.js @@ -0,0 +1,56 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('createAbsence', { + description: 'Returns an array of absences from an specified worker', + accepts: [{ + arg: 'id', + type: 'Number', + description: 'The worker id', + http: {source: 'path'} + }, + { + arg: 'absenceTypeId', + type: 'Number', + required: true + }, + { + arg: 'dated', + type: 'Date', + required: false + }], + returns: { + type: 'Object', + root: true + }, + http: { + path: `/:id/createAbsence`, + verb: 'POST' + } + }); + + Self.createAbsence = async(ctx, id, absenceTypeId, dated) => { + const models = Self.app.models; + const isSubordinate = await models.Worker.isSubordinate(ctx, id); + + if (!isSubordinate) + throw new UserError(`You don't have enough privileges`); + + const labour = await models.WorkerLabour.findOne({ + where: { + and: [ + {workerFk: id}, + {or: [{ + ended: {gte: [dated]} + }, {ended: null}]} + ] + } + }); + + return models.WorkerCalendar.create({ + businessFk: labour.businessFk, + absenceTypeFk: absenceTypeId, + dated: dated + }); + }; +}; diff --git a/modules/worker/back/methods/worker/createAbsences.js b/modules/worker/back/methods/worker/createAbsences.js deleted file mode 100644 index e744d4796..000000000 --- a/modules/worker/back/methods/worker/createAbsences.js +++ /dev/null @@ -1,101 +0,0 @@ -const UserError = require('vn-loopback/util/user-error'); - -module.exports = Self => { - Self.remoteMethodCtx('createAbsences', { - description: 'Returns an array of absences from an specified worker', - accepts: [{ - arg: 'id', - type: 'Number', - description: 'The worker id', - http: {source: 'path'} - }, - { - arg: 'absenceTypeId', - type: 'Number', - required: true - }, - { - arg: 'dated', - type: 'Date', - required: false - }, - { - arg: 'started', - type: 'Date', - required: false - }, - { - arg: 'ended', - type: 'Date', - required: false - }, - { - arg: 'type', - type: 'String', - required: true - }], - returns: 'Object', - http: { - path: `/:id/createAbsences`, - verb: 'POST' - } - }); - - Self.createAbsences = async(ctx, id, absenceTypeId, dated, started, ended, type) => { - const models = Self.app.models; - const isSubordinate = await models.Worker.isSubordinate(ctx, id); - - if (!isSubordinate) - throw new UserError(`You don't have enough privileges`); - - const absences = []; - - if (type == 'day') { - const contracts = await models.WorkerLabour.find({ - where: { - and: [ - {workerFk: id}, - {or: [{ - ended: {gte: [yearStarted]} - }, {ended: null}]} - ] - } - }); - - absences.push({ - businessFk: 106, - absenceTypeFk: absenceTypeId, - dated: dated - }); - } else if (type == 'range') - console.log(true); - - return models.WorkerCalendar.create(absences); - }; -}; - -/* include: [{ - relation: 'holidays', - scope: { - where: {year} - } -}, -{ - relation: 'workCenter', - scope: { - include: { - relation: 'holidays', - scope: { - include: [{ - relation: 'detail' - }, - { - relation: 'type' - }], - where: { - dated: {between: [yearStarted, yearEnded]} - } - } - } - } -}], */ diff --git a/modules/worker/back/methods/worker/deleteAbsence.js b/modules/worker/back/methods/worker/deleteAbsence.js new file mode 100644 index 000000000..a5bdd2014 --- /dev/null +++ b/modules/worker/back/methods/worker/deleteAbsence.js @@ -0,0 +1,35 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('deleteAbsence', { + description: 'Returns an array of absences from an specified worker', + accepts: [{ + arg: 'id', + type: 'Number', + description: 'The worker id', + http: {source: 'path'} + }, + { + arg: 'absenceId', + type: 'Number', + required: true + }], + returns: 'Object', + http: { + path: `/:id/deleteAbsence`, + verb: 'DELETE' + } + }); + + Self.deleteAbsence = async(ctx, id, absenceId) => { + const models = Self.app.models; + const isSubordinate = await models.Worker.isSubordinate(ctx, id); + + if (!isSubordinate) + throw new UserError(`You don't have enough privileges`); + + const absence = await models.WorkerCalendar.findById(absenceId); + + return absence.destroy(); + }; +}; diff --git a/modules/worker/back/methods/worker/updateAbsence.js b/modules/worker/back/methods/worker/updateAbsence.js new file mode 100644 index 000000000..e129d228f --- /dev/null +++ b/modules/worker/back/methods/worker/updateAbsence.js @@ -0,0 +1,40 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('updateAbsence', { + description: 'Returns an array of absences from an specified worker', + accepts: [{ + arg: 'id', + type: 'Number', + description: 'The worker id', + http: {source: 'path'} + }, + { + arg: 'absenceId', + type: 'Number', + required: true + }, + { + arg: 'absenceTypeId', + type: 'Number', + required: true + }], + returns: 'Object', + http: { + path: `/:id/updateAbsence`, + verb: 'PATCH' + } + }); + + Self.updateAbsence = async(ctx, id, absenceId, absenceTypeId) => { + const models = Self.app.models; + const isSubordinate = await models.Worker.isSubordinate(ctx, id); + + if (!isSubordinate) + throw new UserError(`You don't have enough privileges`); + + const absence = await models.WorkerCalendar.findById(absenceId); + + return absence.updateAttribute('absenceTypeFk', absenceTypeId); + }; +}; diff --git a/modules/worker/back/models/worker-calendar.json b/modules/worker/back/models/worker-calendar.json index 109b93c77..ca802caa7 100644 --- a/modules/worker/back/models/worker-calendar.json +++ b/modules/worker/back/models/worker-calendar.json @@ -7,12 +7,14 @@ } }, "properties": { + "id": { + "id": true, + "type": "Number" + }, "businessFk": { - "id": 1, "type": "Number" }, "dated": { - "id": 3, "type": "Date" } }, diff --git a/modules/worker/back/models/worker.js b/modules/worker/back/models/worker.js index 5f97c3e10..0d94c788e 100644 --- a/modules/worker/back/models/worker.js +++ b/modules/worker/back/models/worker.js @@ -4,5 +4,7 @@ module.exports = Self => { require('../methods/worker/isSubordinate')(Self); require('../methods/worker/getWorkedHours')(Self); require('../methods/worker/uploadFile')(Self); - require('../methods/worker/createAbsences')(Self); + require('../methods/worker/createAbsence')(Self); + require('../methods/worker/deleteAbsence')(Self); + require('../methods/worker/updateAbsence')(Self); }; diff --git a/modules/worker/front/calendar/index.html b/modules/worker/front/calendar/index.html index 859da81b5..b77366328 100644 --- a/modules/worker/front/calendar/index.html +++ b/modules/worker/front/calendar/index.html @@ -13,7 +13,7 @@ display-controls="false" hide-contiguous="true" hide-year="true" - on-selection="$ctrl.onSelection($days, $type, $weekday)"> + on-selection="$ctrl.onSelection($days)"> @@ -38,61 +38,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - { - this.selected = null; - this.isNew = null; + const path = `Workers/${this.$params.id}/createAbsence`; + this.$http.post(path, params).then(res => { + this.responseHandler(() => { + const newEvent = res.data; + this.events[dated.getTime()] = { + name: absenceType.name, + color: absenceType.rgb, + type: absenceType.code, + absenceId: newEvent.id + }; this.refresh(); }); + }); + } + + edit(event) { + const absenceType = this.absenceType; + const params = { + absenceId: event.absenceId, + absenceTypeId: absenceType.id + }; + const path = `Workers/${this.$params.id}/updateAbsence`; + this.$http.patch(path, params).then( + this.responseHandler(() => { + event.color = absenceType.rgb; + event.name = absenceType.name; + event.code = absenceType.code; + this.refresh(); + }) + ); + } + + delete(day, event) { + const params = {absenceId: event.absenceId}; + const path = `Workers/${this.$params.id}/deleteAbsence`; + this.$http.delete(path, {params}).then( + this.responseHandler(() => { + delete this.events[day.getTime()]; + this.refresh(); + }) + ); + } + + responseHandler(cb) { + if (this.repaintCanceller) { + clearTimeout(this.repaintCanceller); + this.repaintCanceller = null; } - case 'delete': - return this.onDelete(this.selected.id) - .then(response => response == 'accept'); - } + + this.repaintCanceller = setTimeout( + () => cb(), 650); } refresh() { From 3a8d5c192c0d0c2e9369a9d73f2549eb185a13d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20S=C3=A1nchez?= Date: Thu, 9 Jul 2020 12:49:07 +0200 Subject: [PATCH 03/22] HOTFIX: Pagination limit --- modules/client/front/consumption/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/client/front/consumption/index.html b/modules/client/front/consumption/index.html index 8ea65ecae..4bc4c34c3 100644 --- a/modules/client/front/consumption/index.html +++ b/modules/client/front/consumption/index.html @@ -2,6 +2,7 @@ url="Clients/consumption" link="{clientFk: $ctrl.$params.id}" filter="::$ctrl.filter" + limit="20" user-params="::$ctrl.filterParams" data="sales" order="itemTypeFk, itemName, itemSize"> From 7b61e129ffd63448e899494f843988a913a39b17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20S=C3=A1nchez?= Date: Thu, 9 Jul 2020 13:24:56 +0200 Subject: [PATCH 04/22] HOTFIX: Change claim claimed quantity --- modules/claim/back/models/claim-beginning.js | 3 ++- modules/claim/front/detail/index.js | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/modules/claim/back/models/claim-beginning.js b/modules/claim/back/models/claim-beginning.js index 19e5eb4eb..681aaebc7 100644 --- a/modules/claim/back/models/claim-beginning.js +++ b/modules/claim/back/models/claim-beginning.js @@ -22,7 +22,8 @@ module.exports = Self => { async function claimIsEditable(ctx) { const loopBackContext = LoopBackContext.getCurrentContext(); const httpCtx = {req: loopBackContext.active}; - const isEditable = await Self.app.models.Claim.isEditable(httpCtx, ctx.where.id); + const claimBeginning = await Self.findById(ctx.where.id); + const isEditable = await Self.app.models.Claim.isEditable(httpCtx, claimBeginning.claimFk); if (!isEditable) throw new UserError(`The current claim can't be modified`); diff --git a/modules/claim/front/detail/index.js b/modules/claim/front/detail/index.js index 7719a9fc9..c97f3bd47 100644 --- a/modules/claim/front/detail/index.js +++ b/modules/claim/front/detail/index.js @@ -22,16 +22,25 @@ class Controller extends Section { }; } - set salesClaimed(value) { - this._salesClaimed = value; + get claim() { + return this._claim; + } + + set claim(value) { + this._claim = value; if (value) { - this.calculateTotals(); this.isClaimEditable(); this.isTicketEditable(); } } + set salesClaimed(value) { + this._salesClaimed = value; + + if (value) this.calculateTotals(); + } + get salesClaimed() { return this._salesClaimed; } From 91e739547bdf96bb85ff296a4b153649bec78104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20S=C3=A1nchez?= Date: Sat, 11 Jul 2020 18:30:27 +0200 Subject: [PATCH 05/22] Changes --- modules/worker/front/calendar/index.html | 25 +++--- modules/worker/front/calendar/index.js | 87 +++++++++++---------- modules/worker/front/calendar/locale/es.yml | 3 +- 3 files changed, 61 insertions(+), 54 deletions(-) diff --git a/modules/worker/front/calendar/index.html b/modules/worker/front/calendar/index.html index b77366328..55f22a931 100644 --- a/modules/worker/front/calendar/index.html +++ b/modules/worker/front/calendar/index.html @@ -4,17 +4,20 @@ auto-load="true">
- - - + + + + +
diff --git a/modules/worker/front/calendar/index.js b/modules/worker/front/calendar/index.js index 0b43eefc0..5e83e561a 100644 --- a/modules/worker/front/calendar/index.js +++ b/modules/worker/front/calendar/index.js @@ -44,7 +44,8 @@ class Controller extends Section { set worker(value) { this._worker = value; - if (value) this.refresh(); + if (value) + this.refresh().then(() => this.repaint()); } onData(data) { @@ -78,8 +79,6 @@ class Controller extends Section { }); }); } - - this.repaint(); } repaint() { @@ -105,24 +104,25 @@ class Controller extends Section { this.absenceType = absenceType; } - onSelection($days) { + onSelection($event, $days) { if (!this.absenceType) - return this.vnApp.showMessage(this.$t('Choose an absence type')); + return this.vnApp.showMessage(this.$t('Choose an absence type from the right menu')); const day = $days[0]; const stamp = day.getTime(); const event = this.events[stamp]; + const calendar = $event.target.closest('vn-calendar').$ctrl; if (event) { if (event.type == this.absenceType.code) - this.delete(day, event); + this.delete(calendar, day, event); else - this.edit(event); + this.edit(calendar, event); } else - this.create(day); + this.create(calendar, day); } - create(dated) { + create(calendar, dated) { const absenceType = this.absenceType; const params = { dated: dated, @@ -131,55 +131,58 @@ class Controller extends Section { const path = `Workers/${this.$params.id}/createAbsence`; this.$http.post(path, params).then(res => { - this.responseHandler(() => { - const newEvent = res.data; - this.events[dated.getTime()] = { - name: absenceType.name, - color: absenceType.rgb, - type: absenceType.code, - absenceId: newEvent.id - }; - this.refresh(); - }); + const newEvent = res.data; + this.events[dated.getTime()] = { + name: absenceType.name, + color: absenceType.rgb, + type: absenceType.code, + absenceId: newEvent.id + }; + + this.repaintCanceller(() => + this.refresh().then(calendar.repaint()) + ); }); } - edit(event) { + edit(calendar, event) { const absenceType = this.absenceType; const params = { absenceId: event.absenceId, absenceTypeId: absenceType.id }; const path = `Workers/${this.$params.id}/updateAbsence`; - this.$http.patch(path, params).then( - this.responseHandler(() => { - event.color = absenceType.rgb; - event.name = absenceType.name; - event.code = absenceType.code; - this.refresh(); - }) - ); + this.$http.patch(path, params).then(() => { + event.color = absenceType.rgb; + event.name = absenceType.name; + event.code = absenceType.code; + + this.repaintCanceller(() => + this.refresh().then(calendar.repaint()) + ); + }); } - delete(day, event) { + delete(calendar, day, event) { const params = {absenceId: event.absenceId}; const path = `Workers/${this.$params.id}/deleteAbsence`; - this.$http.delete(path, {params}).then( - this.responseHandler(() => { - delete this.events[day.getTime()]; - this.refresh(); - }) - ); + this.$http.delete(path, {params}).then(() => { + delete this.events[day.getTime()]; + + this.repaintCanceller(() => + this.refresh().then(calendar.repaint()) + ); + }); } - responseHandler(cb) { - if (this.repaintCanceller) { - clearTimeout(this.repaintCanceller); - this.repaintCanceller = null; + repaintCanceller(cb) { + if (this.canceller) { + clearTimeout(this.canceller); + this.canceller = null; } - this.repaintCanceller = setTimeout( - () => cb(), 650); + this.canceller = setTimeout( + () => cb(), 500); } refresh() { @@ -188,7 +191,7 @@ class Controller extends Section { started: this.started, ended: this.ended }; - this.$http.get(`WorkerCalendars/absences`, {params}) + return this.$http.get(`WorkerCalendars/absences`, {params}) .then(res => this.onData(res.data)); } } diff --git a/modules/worker/front/calendar/locale/es.yml b/modules/worker/front/calendar/locale/es.yml index 82939ce91..85a2c5431 100644 --- a/modules/worker/front/calendar/locale/es.yml +++ b/modules/worker/front/calendar/locale/es.yml @@ -2,4 +2,5 @@ Calendar: Calendario Holidays: Vacaciones Used: Utilizados of: de -days: días \ No newline at end of file +days: días +Choose an absence type from the right menu: Elige un tipo de ausencia desde el menú de la derecha \ No newline at end of file From 159b5f3bb8a46ee20b89c484807038617155640e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20S=C3=A1nchez?= Date: Mon, 13 Jul 2020 11:58:31 +0200 Subject: [PATCH 06/22] #2351 Item image queue scheduler --- back/model-config.json | 9 + back/models/image-collection-size.json | 44 +++ back/models/image-collection.json | 57 +++ back/models/image.js | 96 +++++ back/models/image.json | 41 ++ docker-compose.yml | 1 + .../back/methods/item-image-queue/download.js | 54 +++ modules/item/back/model-config.json | 3 + modules/item/back/models/item-image-queue.js | 3 + .../item/back/models/item-image-queue.json | 36 ++ package-lock.json | 366 ++++++++++++++++-- package.json | 1 + 12 files changed, 668 insertions(+), 43 deletions(-) create mode 100644 back/models/image-collection-size.json create mode 100644 back/models/image-collection.json create mode 100644 back/models/image.js create mode 100644 back/models/image.json create mode 100644 modules/item/back/methods/item-image-queue/download.js create mode 100644 modules/item/back/models/item-image-queue.js create mode 100644 modules/item/back/models/item-image-queue.json diff --git a/back/model-config.json b/back/model-config.json index 872f2239e..323e5f233 100644 --- a/back/model-config.json +++ b/back/model-config.json @@ -26,6 +26,15 @@ "Delivery": { "dataSource": "vn" }, + "Image": { + "dataSource": "vn" + }, + "ImageCollection": { + "dataSource": "vn" + }, + "ImageCollectionSize": { + "dataSource": "vn" + }, "Province": { "dataSource": "vn" }, diff --git a/back/models/image-collection-size.json b/back/models/image-collection-size.json new file mode 100644 index 000000000..adb92d16b --- /dev/null +++ b/back/models/image-collection-size.json @@ -0,0 +1,44 @@ +{ + "name": "ImageCollectionSize", + "base": "VnModel", + "options": { + "mysql": { + "table": "hedera.imageCollectionSize" + } + }, + "properties": { + "id": { + "type": "Number", + "id": true, + "description": "Identifier" + }, + "width": { + "type": "Number", + "required": true + }, + "height": { + "type": "Number", + "required": true + }, + "crop": { + "type": "Boolean", + "required": true + } + }, + "relations": { + "collection": { + "type": "belongsTo", + "model": "ImageCollection", + "foreignKey": "collectionFk" + } + }, + "acls": [ + { + "accessType": "READ", + "principalType": "ROLE", + "principalId": "employee", + "permission": "ALLOW" + } + ] +} + \ No newline at end of file diff --git a/back/models/image-collection.json b/back/models/image-collection.json new file mode 100644 index 000000000..2234766c9 --- /dev/null +++ b/back/models/image-collection.json @@ -0,0 +1,57 @@ +{ + "name": "ImageCollection", + "base": "VnModel", + "options": { + "mysql": { + "table": "hedera.imageCollection" + } + }, + "properties": { + "id": { + "type": "Number", + "id": true, + "description": "Identifier" + }, + "name": { + "type": "String", + "required": true + }, + "desc": { + "type": "String", + "required": true + }, + "maxWidth": { + "type": "Number", + "required": true + }, + "maxHeight": { + "type": "Number", + "required": true + }, + "model": { + "type": "String", + "required": true + }, + "property": { + "type": "String", + "required": true + } + }, + "relations": { + "sizes": { + "type": "hasMany", + "model": "ImageCollectionSize", + "foreignKey": "collectionFk", + "property": "id" + } + }, + "acls": [ + { + "accessType": "READ", + "principalType": "ROLE", + "principalId": "employee", + "permission": "ALLOW" + } + ] +} + \ No newline at end of file diff --git a/back/models/image.js b/back/models/image.js new file mode 100644 index 000000000..5b4a20236 --- /dev/null +++ b/back/models/image.js @@ -0,0 +1,96 @@ +const fs = require('fs-extra'); +const sharp = require('sharp'); +const path = require('path'); + +module.exports = Self => { + Self.register = async(collectionName, file, srcFilePath) => { + const models = Self.app.models; + const tx = await Self.beginTransaction({}); + const myOptions = {transaction: tx}; + + try { + const collection = await models.ImageCollection.findOne({ + fields: [ + 'id', + 'name', + 'maxWidth', + 'maxHeight', + 'model', + 'property' + ], + where: {name: collectionName}, + include: { + relation: 'sizes', + scope: { + fields: ['width', 'height', 'crop'] + } + } + }); + + const fileName = file.split('.')[0]; + // const rootPath = 'C:\\Users\\jsanc\\Desktop\\images'; + const rootPath = '/var/lib/salix/images'; + const data = { + name: fileName, + collectionFk: collectionName + }; + + const newImage = await Self.upsertWithWhere(data, { + name: fileName, + collectionFk: collectionName, + updated: (new Date).getTime() + }, myOptions); + + // Resizes and saves the image + const collectionDir = path.join(rootPath, collectionName); + const dstDir = path.join(collectionDir, 'full'); + const dstFile = path.join(dstDir, file); + + const resizeOpts = { + withoutEnlargement: true, + fit: 'inside' + }; + + await fs.mkdir(dstDir, {recursive: true}); + await sharp(srcFilePath) + .resize(collection.maxWidth, collection.maxHeight, resizeOpts) + .toFile(dstFile); + + const sizes = collection.sizes(); + for (let size of sizes) { + const dstDir = path.join(collectionDir, `${size.width}x${size.height}`); + const dstFile = path.join(dstDir, file); + const resizeOpts = { + withoutEnlargement: true, + fit: size.crop ? 'cover' : 'inside' + }; + + await fs.mkdir(dstDir, {recursive: true}); + await sharp(srcFilePath) + .resize(size.width, size.height, resizeOpts) + .toFile(dstFile); + } + + const model = models[collection.model]; + + if (!model) + throw new Error('Matching model not found'); + + const item = await model.findById(fileName, null, myOptions); + if (item) { + await item.updateAttribute( + collection.property, + fileName, + myOptions + ); + } + + await fs.unlink(srcFilePath); + await tx.commit(); + return newImage; + } catch (e) { + await tx.rollback(); + throw e; + } + }; +}; diff --git a/back/models/image.json b/back/models/image.json new file mode 100644 index 000000000..5b8c76cf1 --- /dev/null +++ b/back/models/image.json @@ -0,0 +1,41 @@ +{ + "name": "Image", + "base": "VnModel", + "options": { + "mysql": { + "table": "hedera.image" + } + }, + "properties": { + "id": { + "type": "Number", + "id": true, + "description": "The id" + }, + "name": { + "type": "String", + "required": true + }, + "collectionFk": { + "type": "String", + "required": true + }, + "updated": { + "type": "Number" + }, + "nRefs": { + "type": "Number", + "required": true, + "default": 0 + } + }, + "acls": [ + { + "accessType": "READ", + "principalType": "ROLE", + "principalId": "employee", + "permission": "ALLOW" + } + ] +} + \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index fabd968a1..413d6f48f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,6 +28,7 @@ services: volumes: - /mnt/storage/pdfs:/var/lib/salix/pdfs - /mnt/storage/dms:/var/lib/salix/dms + - /mnt/storage/images:/var/lib/salix/images deploy: replicas: 6 configs: diff --git a/modules/item/back/methods/item-image-queue/download.js b/modules/item/back/methods/item-image-queue/download.js new file mode 100644 index 000000000..d36604938 --- /dev/null +++ b/modules/item/back/methods/item-image-queue/download.js @@ -0,0 +1,54 @@ +const https = require('https'); +const fs = require('fs-extra'); +const path = require('path'); + +module.exports = Self => { + Self.remoteMethod('download', { + description: 'Returns last entries', + accessType: 'WRITE', + returns: { + type: 'Boolean', + root: true + }, + http: { + path: `/download`, + verb: 'GET' + } + }); + + Self.download = async() => { + const models = Self.app.models; + + try { + const imageQueue = await Self.find({ + limit: 2 + }); + + // const rootPath = 'C:\\Users\\jsanc\\Desktop\\images'; + const rootPath = '/var/lib/salix/images'; + const tempPath = path.join(rootPath, 'temp'); + + // Create temporary path + await fs.mkdir(tempPath); + + for (let image of imageQueue) { + const fileName = `${image.itemFk}.jpg`; + const filePath = path.join(tempPath, fileName); + + https.get(image.url, async response => { + // Upload file to temporary path + const file = fs.createWriteStream(filePath); + response.pipe(file); + + await models.Image.register('catalog', fileName, filePath); + // await image.destroy(); + }); + } + } catch (e) { + fs.unlink(filePath); + throw e; + } + + return true; + }; +}; diff --git a/modules/item/back/model-config.json b/modules/item/back/model-config.json index c085e075a..c5623dcca 100644 --- a/modules/item/back/model-config.json +++ b/modules/item/back/model-config.json @@ -50,6 +50,9 @@ "ItemShelvingSale": { "dataSource": "vn" }, + "ItemImageQueue": { + "dataSource": "vn" + }, "Origin": { "dataSource": "vn" }, diff --git a/modules/item/back/models/item-image-queue.js b/modules/item/back/models/item-image-queue.js new file mode 100644 index 000000000..35a467693 --- /dev/null +++ b/modules/item/back/models/item-image-queue.js @@ -0,0 +1,3 @@ +module.exports = Self => { + require('../methods/item-image-queue/download')(Self); +}; diff --git a/modules/item/back/models/item-image-queue.json b/modules/item/back/models/item-image-queue.json new file mode 100644 index 000000000..61cb7b018 --- /dev/null +++ b/modules/item/back/models/item-image-queue.json @@ -0,0 +1,36 @@ +{ + "name": "ItemImageQueue", + "description": "Image download queue", + "base": "VnModel", + "options": { + "mysql": { + "table": "itemImageQueue" + } + }, + "properties": { + "itemFk": { + "type": "Number", + "id": true, + "description": "Identifier" + }, + "url": { + "type": "String", + "required": true + } + }, + "relations": { + "item": { + "type": "belongsTo", + "model": "Item", + "foreignKey": "itemFk" + } + }, + "acls": [ + { + "accessType": "*", + "principalType": "ROLE", + "principalId": "employee", + "permission": "ALLOW" + } + ] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 5e4ac6c46..92030199a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4690,8 +4690,7 @@ "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, "ansi-styles": { "version": "3.2.1", @@ -4741,8 +4740,7 @@ "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, "archy": { "version": "1.0.0", @@ -4754,7 +4752,6 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", - "dev": true, "requires": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -6610,8 +6607,7 @@ "chownr": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", - "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==", - "dev": true + "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==" }, "chrome-trace-event": { "version": "1.0.2", @@ -6820,8 +6816,7 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "collect-v8-coverage": { "version": "1.0.1", @@ -6850,11 +6845,19 @@ "object-visit": "^1.0.0" } }, + "color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz", + "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==", + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -6862,8 +6865,16 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "color-string": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", + "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } }, "color-support": { "version": "1.1.3", @@ -7011,8 +7022,7 @@ "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "consolidate": { "version": "0.15.1", @@ -7563,8 +7573,7 @@ "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "dev": true + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, "denque": { "version": "1.4.1", @@ -7604,6 +7613,11 @@ "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", "dev": true }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -8369,6 +8383,11 @@ } } }, + "expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" + }, "expand-tilde": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", @@ -9150,6 +9169,11 @@ "readable-stream": "^2.0.0" } }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, "fs-extra": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", @@ -9160,6 +9184,14 @@ "universalify": "^0.1.0" } }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + } + }, "fs-mkdirp-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", @@ -9798,7 +9830,6 @@ "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "dev": true, "requires": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", @@ -9813,14 +9844,12 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, "requires": { "number-is-nan": "^1.0.0" } @@ -9829,7 +9858,6 @@ "version": "1.0.2", "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -9840,7 +9868,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -9948,6 +9975,11 @@ "assert-plus": "^1.0.0" } }, + "github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=" + }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -11293,8 +11325,7 @@ "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "dev": true + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, "has-value": { "version": "1.0.0", @@ -11897,8 +11928,7 @@ "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "inquirer": { "version": "6.4.1", @@ -12137,8 +12167,7 @@ "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "is-generator-fn": { "version": "2.1.0", @@ -18662,6 +18691,37 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, + "minipass": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "requires": { + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "minizlib": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.0.tgz", + "integrity": "sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, "mississippi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", @@ -18739,6 +18799,11 @@ "minimist": "^1.2.5" } }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, "mktmpdir": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/mktmpdir/-/mktmpdir-0.1.1.tgz", @@ -19079,6 +19144,11 @@ "to-regex": "^3.0.1" } }, + "napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -19121,6 +19191,19 @@ "resolved": "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz", "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==" }, + "node-abi": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.18.0.tgz", + "integrity": "sha512-yi05ZoiuNNEbyT/xXfSySZE+yVnQW6fxPZuFbLyS1s6b5Kw3HzV2PHOM4XR+nsjzkHxByK+2Wg+yCQbe35l8dw==", + "requires": { + "semver": "^5.4.1" + } + }, + "node-addon-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.0.0.tgz", + "integrity": "sha512-sSHCgWfJ+Lui/u+0msF3oyCgvdkhxDbkCS6Q8uiJquzOimkJBvX6hl5aSSA7DR1XbMpdM8r7phjcF63sF4rkKg==" + }, "node-fetch": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", @@ -19507,6 +19590,11 @@ } } }, + "noop-logger": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", + "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=" + }, "nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", @@ -19561,7 +19649,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "dev": true, "requires": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", @@ -19581,8 +19668,7 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "nwsapi": { "version": "2.2.0", @@ -19598,8 +19684,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-copy": { "version": "0.1.0", @@ -20381,6 +20466,53 @@ "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", "dev": true }, + "prebuild-install": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.5.tgz", + "integrity": "sha512-YmMO7dph9CYKi5IR/BzjOJlRzpxGGVo1EsLSUZ0mt/Mq0HWZIHOKHHcHdT69yG54C9m6i45GpItwRHpk0Py7Uw==", + "requires": { + "detect-libc": "^1.0.3", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp": "^0.5.1", + "napi-build-utils": "^1.0.1", + "node-abi": "^2.7.0", + "noop-logger": "^0.1.1", + "npmlog": "^4.0.1", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^3.0.3", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0", + "which-pm-runs": "^1.0.0" + }, + "dependencies": { + "decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "requires": { + "mimic-response": "^2.0.0" + } + }, + "mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" + }, + "simple-get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", + "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", + "requires": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + } + } + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -20729,7 +20861,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -20740,8 +20871,7 @@ "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" } } }, @@ -21723,8 +21853,7 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "set-value": { "version": "2.0.1", @@ -21779,6 +21908,57 @@ "kind-of": "^6.0.2" } }, + "sharp": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.25.4.tgz", + "integrity": "sha512-umSzJJ1oBwIOfwFFt/fJ7JgCva9FvrEU2cbbm7u/3hSDZhXvkME8WE5qpaJqLIe2Har5msF5UG4CzYlEg5o3BQ==", + "requires": { + "color": "^3.1.2", + "detect-libc": "^1.0.3", + "node-addon-api": "^3.0.0", + "npmlog": "^4.1.2", + "prebuild-install": "^5.3.4", + "semver": "^7.3.2", + "simple-get": "^4.0.0", + "tar": "^6.0.2", + "tunnel-agent": "^0.6.0" + }, + "dependencies": { + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + }, + "tar": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.2.tgz", + "integrity": "sha512-Glo3jkRtPcvpDlAs/0+hozav78yoXKFr+c4wgw62NNMO3oo4AaJdCo21Uu7lcwr55h39W2XD1LMERc64wtbItg==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.0", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -21822,6 +22002,51 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, + "simple-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz", + "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=" + }, + "simple-get": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.0.tgz", + "integrity": "sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ==", + "requires": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + }, + "dependencies": { + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + } + }, + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + } + } + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + } + } + }, "sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -22442,7 +22667,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -22460,7 +22684,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, "requires": { "ansi-regex": "^3.0.0" } @@ -22496,8 +22719,7 @@ "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, "strong-error-handler": { "version": "2.3.2", @@ -23080,6 +23302,60 @@ "inherits": "2" } }, + "tar-fs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.0.tgz", + "integrity": "sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg==", + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.0.0" + } + }, + "tar-stream": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.3.tgz", + "integrity": "sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA==", + "requires": { + "bl": "^4.0.1", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "bl": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz", + "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==", + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "teeny-request": { "version": "3.11.3", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-3.11.3.tgz", @@ -25048,11 +25324,15 @@ "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", "dev": true }, + "which-pm-runs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", + "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=" + }, "wide-align": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, "requires": { "string-width": "^1.0.2 || 2" } diff --git a/package.json b/package.json index 61dff1678..b7cca0364 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "request": "^2.88.0", "request-promise-native": "^1.0.8", "require-yaml": "0.0.1", + "sharp": "^0.25.4", "soap": "^0.26.0", "strong-error-handler": "^2.3.2", "uuid": "^3.3.3", From ccc5b95b48a0d88ddbb78eccb8a1db967c8edde0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20S=C3=A1nchez?= Date: Mon, 13 Jul 2020 12:23:47 +0200 Subject: [PATCH 07/22] Updated mount paths --- back/models/image.js | 2 +- docker-compose.yml | 2 +- modules/item/back/methods/item-image-queue/download.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/back/models/image.js b/back/models/image.js index 5b4a20236..fb1a887e8 100644 --- a/back/models/image.js +++ b/back/models/image.js @@ -29,7 +29,7 @@ module.exports = Self => { const fileName = file.split('.')[0]; // const rootPath = 'C:\\Users\\jsanc\\Desktop\\images'; - const rootPath = '/var/lib/salix/images'; + const rootPath = '/var/lib/salix/image'; const data = { name: fileName, collectionFk: collectionName diff --git a/docker-compose.yml b/docker-compose.yml index 413d6f48f..c04f7e388 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,7 +28,7 @@ services: volumes: - /mnt/storage/pdfs:/var/lib/salix/pdfs - /mnt/storage/dms:/var/lib/salix/dms - - /mnt/storage/images:/var/lib/salix/images + - /mnt/storage/image:/var/lib/salix/image deploy: replicas: 6 configs: diff --git a/modules/item/back/methods/item-image-queue/download.js b/modules/item/back/methods/item-image-queue/download.js index d36604938..c642a53b4 100644 --- a/modules/item/back/methods/item-image-queue/download.js +++ b/modules/item/back/methods/item-image-queue/download.js @@ -25,7 +25,7 @@ module.exports = Self => { }); // const rootPath = 'C:\\Users\\jsanc\\Desktop\\images'; - const rootPath = '/var/lib/salix/images'; + const rootPath = '/var/lib/salix/image'; const tempPath = path.join(rootPath, 'temp'); // Create temporary path From ee19ea299337720073c0369d44ce3bb63c32ea60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20S=C3=A1nchez?= Date: Mon, 13 Jul 2020 14:13:21 +0200 Subject: [PATCH 08/22] Restricted to itemFk --- modules/item/back/methods/item-image-queue/download.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/item/back/methods/item-image-queue/download.js b/modules/item/back/methods/item-image-queue/download.js index c642a53b4..74954a6d1 100644 --- a/modules/item/back/methods/item-image-queue/download.js +++ b/modules/item/back/methods/item-image-queue/download.js @@ -7,7 +7,7 @@ module.exports = Self => { description: 'Returns last entries', accessType: 'WRITE', returns: { - type: 'Boolean', + type: ['Object'], root: true }, http: { @@ -21,7 +21,9 @@ module.exports = Self => { try { const imageQueue = await Self.find({ - limit: 2 + where: { + itemFk: 410421 + } }); // const rootPath = 'C:\\Users\\jsanc\\Desktop\\images'; @@ -32,7 +34,7 @@ module.exports = Self => { await fs.mkdir(tempPath); for (let image of imageQueue) { - const fileName = `${image.itemFk}.jpg`; + const fileName = `${image.itemFk}.png`; const filePath = path.join(tempPath, fileName); https.get(image.url, async response => { @@ -49,6 +51,6 @@ module.exports = Self => { throw e; } - return true; + return imageQueue; }; }; From 838b8a53827d1154b83473aa79e2510a8afd0cc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20S=C3=A1nchez?= Date: Mon, 13 Jul 2020 14:22:19 +0200 Subject: [PATCH 09/22] Fix scope var --- modules/item/back/methods/item-image-queue/download.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/item/back/methods/item-image-queue/download.js b/modules/item/back/methods/item-image-queue/download.js index 74954a6d1..799b5db9b 100644 --- a/modules/item/back/methods/item-image-queue/download.js +++ b/modules/item/back/methods/item-image-queue/download.js @@ -19,6 +19,7 @@ module.exports = Self => { Self.download = async() => { const models = Self.app.models; + let filePath; try { const imageQueue = await Self.find({ where: { @@ -35,7 +36,7 @@ module.exports = Self => { for (let image of imageQueue) { const fileName = `${image.itemFk}.png`; - const filePath = path.join(tempPath, fileName); + filePath = path.join(tempPath, fileName); https.get(image.url, async response => { // Upload file to temporary path @@ -46,11 +47,11 @@ module.exports = Self => { // await image.destroy(); }); } + + return imageQueue; } catch (e) { fs.unlink(filePath); throw e; } - - return imageQueue; }; }; From 99aa6b666d8c51d643c2664fc5430b25c34fe270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20S=C3=A1nchez?= Date: Mon, 13 Jul 2020 14:31:50 +0200 Subject: [PATCH 10/22] Mkdir recursive --- modules/item/back/methods/item-image-queue/download.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/item/back/methods/item-image-queue/download.js b/modules/item/back/methods/item-image-queue/download.js index 799b5db9b..91ac59795 100644 --- a/modules/item/back/methods/item-image-queue/download.js +++ b/modules/item/back/methods/item-image-queue/download.js @@ -32,7 +32,7 @@ module.exports = Self => { const tempPath = path.join(rootPath, 'temp'); // Create temporary path - await fs.mkdir(tempPath); + await fs.mkdir(tempPath, {recursive: true}); for (let image of imageQueue) { const fileName = `${image.itemFk}.png`; From 80075a647170eee3aa752683bba4b6d39bee22fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20S=C3=A1nchez?= Date: Mon, 13 Jul 2020 14:46:00 +0200 Subject: [PATCH 11/22] Added limit --- modules/item/back/methods/item-image-queue/download.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/item/back/methods/item-image-queue/download.js b/modules/item/back/methods/item-image-queue/download.js index 91ac59795..c9e68d719 100644 --- a/modules/item/back/methods/item-image-queue/download.js +++ b/modules/item/back/methods/item-image-queue/download.js @@ -22,9 +22,7 @@ module.exports = Self => { let filePath; try { const imageQueue = await Self.find({ - where: { - itemFk: 410421 - } + limit: 3 }); // const rootPath = 'C:\\Users\\jsanc\\Desktop\\images'; @@ -44,7 +42,7 @@ module.exports = Self => { response.pipe(file); await models.Image.register('catalog', fileName, filePath); - // await image.destroy(); + await image.destroy(); }); } From 3074c9ce3568957cf5d96660f3689d0a5688bb08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20S=C3=A1nchez?= Date: Tue, 14 Jul 2020 09:32:50 +0200 Subject: [PATCH 12/22] Fixed async image registration --- back/models/image.js | 11 +++++--- .../back/methods/item-image-queue/download.js | 25 ++++++++++--------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/back/models/image.js b/back/models/image.js index fb1a887e8..079acd293 100644 --- a/back/models/image.js +++ b/back/models/image.js @@ -3,7 +3,11 @@ const sharp = require('sharp'); const path = require('path'); module.exports = Self => { - Self.register = async(collectionName, file, srcFilePath) => { + Self.getPath = function() { + return '/var/lib/salix/image'; + }; + + Self.registerImage = async(collectionName, file, srcFilePath) => { const models = Self.app.models; const tx = await Self.beginTransaction({}); const myOptions = {transaction: tx}; @@ -25,11 +29,10 @@ module.exports = Self => { fields: ['width', 'height', 'crop'] } } - }); + }, myOptions); const fileName = file.split('.')[0]; - // const rootPath = 'C:\\Users\\jsanc\\Desktop\\images'; - const rootPath = '/var/lib/salix/image'; + const rootPath = Self.getPath(); const data = { name: fileName, collectionFk: collectionName diff --git a/modules/item/back/methods/item-image-queue/download.js b/modules/item/back/methods/item-image-queue/download.js index c9e68d719..dd1af52c9 100644 --- a/modules/item/back/methods/item-image-queue/download.js +++ b/modules/item/back/methods/item-image-queue/download.js @@ -19,14 +19,9 @@ module.exports = Self => { Self.download = async() => { const models = Self.app.models; - let filePath; try { - const imageQueue = await Self.find({ - limit: 3 - }); - - // const rootPath = 'C:\\Users\\jsanc\\Desktop\\images'; - const rootPath = '/var/lib/salix/image'; + const imageQueue = await Self.find({limit: 25}); + const rootPath = models.Image.getPath(); const tempPath = path.join(rootPath, 'temp'); // Create temporary path @@ -34,21 +29,27 @@ module.exports = Self => { for (let image of imageQueue) { const fileName = `${image.itemFk}.png`; - filePath = path.join(tempPath, fileName); + const filePath = path.join(tempPath, fileName); + const file = fs.createWriteStream(filePath); https.get(image.url, async response => { - // Upload file to temporary path - const file = fs.createWriteStream(filePath); response.pipe(file); + }); - await models.Image.register('catalog', fileName, filePath); + file.on('finish', async function() { + await models.Image.registerImage('catalog', fileName, filePath); await image.destroy(); }); + + file.on('error', err => { + fs.unlink(filePath); + + throw err; + }); } return imageQueue; } catch (e) { - fs.unlink(filePath); throw e; } }; From 310b5007f2926b77bbbdb6cc91cbdb6d35072ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20S=C3=A1nchez?= Date: Tue, 14 Jul 2020 10:42:25 +0200 Subject: [PATCH 13/22] Disabled jenkins back tests --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 2848ea59b..c810dc474 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -69,13 +69,13 @@ pipeline { } } } - stage('Backend') { + /* stage('Backend') { steps { nodejs('node-lts') { sh 'gulp backTestOnce --ci' } } - } + } */ } } stage('Build') { From e557bd2815097ef91ccbb2b324d85e6c47c48130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20S=C3=A1nchez?= Date: Tue, 14 Jul 2020 11:16:50 +0200 Subject: [PATCH 14/22] Renamed method --- .../{download.js => downloadImages.js} | 8 ++++---- .../specs/downloadImages.spec.js | 17 +++++++++++++++++ modules/item/back/models/item-image-queue.js | 2 +- 3 files changed, 22 insertions(+), 5 deletions(-) rename modules/item/back/methods/item-image-queue/{download.js => downloadImages.js} (90%) create mode 100644 modules/item/back/methods/item-image-queue/specs/downloadImages.spec.js diff --git a/modules/item/back/methods/item-image-queue/download.js b/modules/item/back/methods/item-image-queue/downloadImages.js similarity index 90% rename from modules/item/back/methods/item-image-queue/download.js rename to modules/item/back/methods/item-image-queue/downloadImages.js index dd1af52c9..372648dd6 100644 --- a/modules/item/back/methods/item-image-queue/download.js +++ b/modules/item/back/methods/item-image-queue/downloadImages.js @@ -3,7 +3,7 @@ const fs = require('fs-extra'); const path = require('path'); module.exports = Self => { - Self.remoteMethod('download', { + Self.remoteMethod('downloadImages', { description: 'Returns last entries', accessType: 'WRITE', returns: { @@ -11,12 +11,12 @@ module.exports = Self => { root: true }, http: { - path: `/download`, - verb: 'GET' + path: `/downloadImages`, + verb: 'POST' } }); - Self.download = async() => { + Self.downloadImages = async() => { const models = Self.app.models; try { diff --git a/modules/item/back/methods/item-image-queue/specs/downloadImages.spec.js b/modules/item/back/methods/item-image-queue/specs/downloadImages.spec.js new file mode 100644 index 000000000..305833fe6 --- /dev/null +++ b/modules/item/back/methods/item-image-queue/specs/downloadImages.spec.js @@ -0,0 +1,17 @@ +const app = require('vn-loopback/server/server'); +const fs = require('fs-extra'); + +// #2367 - Create tests when table itemImageQueue exists +xdescribe('ItemImageQueue downloadImages()', () => { + it('should iterate over images', async() => { + spyOn(fs, 'mkdir'); + spyOn(fs, 'createWriteStream'); + + const models = app.models; + const imageModel = spyOn(models.Image, 'registerImage'); + + const res = await app.models.ItemImageQueue.downloadImages(); + + expect(res).toEqual(1); + }); +}); diff --git a/modules/item/back/models/item-image-queue.js b/modules/item/back/models/item-image-queue.js index 35a467693..e2059ddac 100644 --- a/modules/item/back/models/item-image-queue.js +++ b/modules/item/back/models/item-image-queue.js @@ -1,3 +1,3 @@ module.exports = Self => { - require('../methods/item-image-queue/download')(Self); + require('../methods/item-image-queue/downloadImages')(Self); }; From 68c19fa22a77ec40e6219a9d4da55384a08b6c10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20S=C3=A1nchez?= Date: Tue, 14 Jul 2020 11:17:53 +0200 Subject: [PATCH 15/22] Removed test --- .../specs/downloadImages.spec.js | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 modules/item/back/methods/item-image-queue/specs/downloadImages.spec.js diff --git a/modules/item/back/methods/item-image-queue/specs/downloadImages.spec.js b/modules/item/back/methods/item-image-queue/specs/downloadImages.spec.js deleted file mode 100644 index 305833fe6..000000000 --- a/modules/item/back/methods/item-image-queue/specs/downloadImages.spec.js +++ /dev/null @@ -1,17 +0,0 @@ -const app = require('vn-loopback/server/server'); -const fs = require('fs-extra'); - -// #2367 - Create tests when table itemImageQueue exists -xdescribe('ItemImageQueue downloadImages()', () => { - it('should iterate over images', async() => { - spyOn(fs, 'mkdir'); - spyOn(fs, 'createWriteStream'); - - const models = app.models; - const imageModel = spyOn(models.Image, 'registerImage'); - - const res = await app.models.ItemImageQueue.downloadImages(); - - expect(res).toEqual(1); - }); -}); From 2f9d80be7a8269f43d51720070f94b1a6cfa34e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20S=C3=A1nchez?= Date: Wed, 15 Jul 2020 11:35:34 +0200 Subject: [PATCH 16/22] Added ACL + unit tests --- modules/worker/front/calendar/index.html | 6 +-- modules/worker/front/calendar/index.js | 11 +++- modules/worker/front/calendar/index.spec.js | 57 ++++++++++++++++++++- modules/worker/front/calendar/locale/es.yml | 3 +- modules/worker/front/calendar/style.scss | 7 +++ 5 files changed, 78 insertions(+), 6 deletions(-) diff --git a/modules/worker/front/calendar/index.html b/modules/worker/front/calendar/index.html index 55f22a931..197fb0797 100644 --- a/modules/worker/front/calendar/index.html +++ b/modules/worker/front/calendar/index.html @@ -4,8 +4,8 @@ auto-load="true">
- - +
- diff --git a/modules/worker/front/calendar/index.js b/modules/worker/front/calendar/index.js index 5e83e561a..b1fce0adb 100644 --- a/modules/worker/front/calendar/index.js +++ b/modules/worker/front/calendar/index.js @@ -44,8 +44,16 @@ class Controller extends Section { set worker(value) { this._worker = value; - if (value) + if (value) { this.refresh().then(() => this.repaint()); + this.getIsSubordinate(); + } + } + + getIsSubordinate() { + this.$http.get(`Workers/${this.worker.id}/isSubordinate`).then(res => + this.isSubordinate = res.data + ); } onData(data) { @@ -98,6 +106,7 @@ class Controller extends Section { } pick(absenceType) { + if (!this.isSubordinate) return; if (absenceType == this.absenceType) absenceType = null; diff --git a/modules/worker/front/calendar/index.spec.js b/modules/worker/front/calendar/index.spec.js index da49b8f0f..a0589b632 100644 --- a/modules/worker/front/calendar/index.spec.js +++ b/modules/worker/front/calendar/index.spec.js @@ -12,8 +12,9 @@ describe('Worker', () => { beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_) => { $scope = $rootScope.$new(); $httpBackend = _$httpBackend_; - const $element = angular.element(''); + const $element = angular.element(''); controller = $componentController('vnWorkerCalendar', {$element, $scope}); + controller.isSubordinate = true; })); describe('started property', () => { @@ -44,6 +45,8 @@ describe('Worker', () => { describe('worker() setter', () => { it(`should perform a get query and set the reponse data on the model`, () => { + jest.spyOn(controller, 'getIsSubordinate').mockReturnThis(); + let today = new Date(); let tomorrow = new Date(today.getTime()); @@ -73,11 +76,14 @@ describe('Worker', () => { expect(events[tomorrow.getTime()].name).toEqual('Easter'); expect(events[yesterday.getTime()].name).toEqual('Leave'); expect(events[yesterday.getTime()].color).toEqual('#bbb'); + expect(controller.getIsSubordinate).toHaveBeenCalledWith(); }); }); describe('formatDay()', () => { it(`should set the day element style`, () => { + jest.spyOn(controller, 'getIsSubordinate').mockReturnThis(); + let today = new Date(); $httpBackend.whenRoute('GET', 'WorkerCalendars/absences') @@ -99,5 +105,54 @@ describe('Worker', () => { expect(dayNumber.style.backgroundColor).toEqual('rgb(0, 0, 0)'); }); }); + + describe('pick()', () => { + it(`should set the absenceType property to null if they match with the current one`, () => { + const absenceType = {id: 1, name: 'Holiday'}; + controller.absenceType = absenceType; + controller.pick(absenceType); + + expect(controller.absenceType).toBeNull(); + }); + + it(`should set the absenceType property`, () => { + const absenceType = {id: 1, name: 'Holiday'}; + const expectedAbsence = {id: 2, name: 'Leave of absence'}; + controller.absenceType = absenceType; + controller.pick(expectedAbsence); + + expect(controller.absenceType).toEqual(expectedAbsence); + }); + }); + + describe('onSelection()', () => { + it(`should show an snackbar message if no absence type is selected`, () => { + jest.spyOn(controller.vnApp, 'showMessage').mockReturnThis(); + + const $event = {}; + const $days = []; + controller.onSelection($event, $days); + + expect(controller.vnApp.showMessage).toHaveBeenCalledWith('Choose an absence type from the right menu'); + }); + + it(`should call to the create() method`, () => { + jest.spyOn(controller, 'create').mockReturnThis(); + + const selectedDay = new Date(); + const $event = { + target: { + closest: () => { + return {$ctrl: {}}; + } + } + }; + const $days = [selectedDay]; + controller.absenceType = {id: 1}; + controller.onSelection($event, $days); + + expect(controller.create).toHaveBeenCalledWith(jasmine.any(Object), selectedDay); + }); + }); }); }); diff --git a/modules/worker/front/calendar/locale/es.yml b/modules/worker/front/calendar/locale/es.yml index 85a2c5431..6681f730f 100644 --- a/modules/worker/front/calendar/locale/es.yml +++ b/modules/worker/front/calendar/locale/es.yml @@ -3,4 +3,5 @@ Holidays: Vacaciones Used: Utilizados of: de days: días -Choose an absence type from the right menu: Elige un tipo de ausencia desde el menú de la derecha \ No newline at end of file +Choose an absence type from the right menu: Elige un tipo de ausencia desde el menú de la derecha +To start adding absences, click an absence type from the right menu and then on the day you want to add an absence: Para empezar a añadir ausencias, haz clic en un tipo de ausencia desde el menu de la derecha y después en el día que quieres añadir la ausencia \ No newline at end of file diff --git a/modules/worker/front/calendar/style.scss b/modules/worker/front/calendar/style.scss index 1c22fdfb0..1934a6ca0 100644 --- a/modules/worker/front/calendar/style.scss +++ b/modules/worker/front/calendar/style.scss @@ -2,6 +2,7 @@ vn-worker-calendar { .calendars { + position: relative; display: flex; flex-wrap: wrap; justify-content: center; @@ -29,4 +30,10 @@ vn-worker-calendar { text-align: center; color: white } + + vn-icon[icon="info"] { + position: absolute; + top: 16px; + right: 16px + } } From 72dfa4262a0373a01b433441d5755d86b0801094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20S=C3=A1nchez?= Date: Wed, 15 Jul 2020 12:00:38 +0200 Subject: [PATCH 17/22] Removed limit --- modules/item/back/methods/item-image-queue/downloadImages.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/item/back/methods/item-image-queue/downloadImages.js b/modules/item/back/methods/item-image-queue/downloadImages.js index 372648dd6..0c2825f8c 100644 --- a/modules/item/back/methods/item-image-queue/downloadImages.js +++ b/modules/item/back/methods/item-image-queue/downloadImages.js @@ -20,7 +20,7 @@ module.exports = Self => { const models = Self.app.models; try { - const imageQueue = await Self.find({limit: 25}); + const imageQueue = await Self.find(); const rootPath = models.Image.getPath(); const tempPath = path.join(rootPath, 'temp'); From da3d20bb16af67ac01d1c9efbd2ced33fd7cdcfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20S=C3=A1nchez?= Date: Wed, 15 Jul 2020 12:11:44 +0200 Subject: [PATCH 18/22] Restored limit --- modules/item/back/methods/item-image-queue/downloadImages.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/item/back/methods/item-image-queue/downloadImages.js b/modules/item/back/methods/item-image-queue/downloadImages.js index 0c2825f8c..372648dd6 100644 --- a/modules/item/back/methods/item-image-queue/downloadImages.js +++ b/modules/item/back/methods/item-image-queue/downloadImages.js @@ -20,7 +20,7 @@ module.exports = Self => { const models = Self.app.models; try { - const imageQueue = await Self.find(); + const imageQueue = await Self.find({limit: 25}); const rootPath = models.Image.getPath(); const tempPath = path.join(rootPath, 'temp'); From bbf3b7200142a350e993839b3be1d3830aabd553 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20S=C3=A1nchez?= Date: Thu, 16 Jul 2020 10:04:16 +0200 Subject: [PATCH 19/22] Added front & back tests --- .../back/methods/worker/createAbsence.js | 4 +- .../back/methods/worker/deleteAbsence.js | 4 +- .../worker/specs/createAbsence.spec.js | 39 ++++ .../worker/specs/deleteAbsence.spec.js | 38 ++++ .../worker/specs/updateAbsence.spec.js | 38 ++++ .../back/methods/worker/updateAbsence.js | 4 +- modules/worker/front/calendar/index.js | 2 +- modules/worker/front/calendar/index.spec.js | 186 +++++++++++++++++- 8 files changed, 310 insertions(+), 5 deletions(-) create mode 100644 modules/worker/back/methods/worker/specs/createAbsence.spec.js create mode 100644 modules/worker/back/methods/worker/specs/deleteAbsence.spec.js create mode 100644 modules/worker/back/methods/worker/specs/updateAbsence.spec.js diff --git a/modules/worker/back/methods/worker/createAbsence.js b/modules/worker/back/methods/worker/createAbsence.js index 3f15b19c1..9c65aa66a 100644 --- a/modules/worker/back/methods/worker/createAbsence.js +++ b/modules/worker/back/methods/worker/createAbsence.js @@ -31,9 +31,11 @@ module.exports = Self => { Self.createAbsence = async(ctx, id, absenceTypeId, dated) => { const models = Self.app.models; + const userId = ctx.req.accessToken.userId; const isSubordinate = await models.Worker.isSubordinate(ctx, id); + const isTeamBoss = await models.Account.hasRole(userId, 'teamBoss'); - if (!isSubordinate) + if (!isSubordinate || (isSubordinate && userId == id && !isTeamBoss)) throw new UserError(`You don't have enough privileges`); const labour = await models.WorkerLabour.findOne({ diff --git a/modules/worker/back/methods/worker/deleteAbsence.js b/modules/worker/back/methods/worker/deleteAbsence.js index a5bdd2014..d5dbfa00d 100644 --- a/modules/worker/back/methods/worker/deleteAbsence.js +++ b/modules/worker/back/methods/worker/deleteAbsence.js @@ -23,9 +23,11 @@ module.exports = Self => { Self.deleteAbsence = async(ctx, id, absenceId) => { const models = Self.app.models; + const userId = ctx.req.accessToken.userId; const isSubordinate = await models.Worker.isSubordinate(ctx, id); + const isTeamBoss = await models.Account.hasRole(userId, 'teamBoss'); - if (!isSubordinate) + if (!isSubordinate || (isSubordinate && userId == id && !isTeamBoss)) throw new UserError(`You don't have enough privileges`); const absence = await models.WorkerCalendar.findById(absenceId); diff --git a/modules/worker/back/methods/worker/specs/createAbsence.spec.js b/modules/worker/back/methods/worker/specs/createAbsence.spec.js new file mode 100644 index 000000000..33bc2a80e --- /dev/null +++ b/modules/worker/back/methods/worker/specs/createAbsence.spec.js @@ -0,0 +1,39 @@ +const app = require('vn-loopback/server/server'); + +describe('Worker createAbsence()', () => { + const workerId = 106; + let createdAbsence; + + afterAll(async() => { + const absence = await app.models.WorkerCalendar.findById(createdAbsence.id); + await absence.destroy(); + }); + + it('should return an error for a user without enough privileges', async() => { + const ctx = {req: {accessToken: {userId: 106}}}; + const absenceTypeId = 1; + const dated = new Date(); + + let error; + await app.models.Worker.createAbsence(ctx, workerId, absenceTypeId, dated).catch(e => { + error = e; + }).finally(() => { + expect(error.message).toEqual(`You don't have enough privileges`); + }); + + expect(error).toBeDefined(); + }); + + it('should create a new absence', async() => { + const ctx = {req: {accessToken: {userId: 37}}}; + const absenceTypeId = 1; + const dated = new Date(); + createdAbsence = await app.models.Worker.createAbsence(ctx, workerId, absenceTypeId, dated); + + const expectedBusinessId = 106; + const expectedAbsenceTypeId = 1; + + expect(createdAbsence.businessFk).toEqual(expectedBusinessId); + expect(createdAbsence.absenceTypeFk).toEqual(expectedAbsenceTypeId); + }); +}); diff --git a/modules/worker/back/methods/worker/specs/deleteAbsence.spec.js b/modules/worker/back/methods/worker/specs/deleteAbsence.spec.js new file mode 100644 index 000000000..c506ae86d --- /dev/null +++ b/modules/worker/back/methods/worker/specs/deleteAbsence.spec.js @@ -0,0 +1,38 @@ +const app = require('vn-loopback/server/server'); + +describe('Worker deleteAbsence()', () => { + const workerId = 106; + let createdAbsence; + + it('should return an error for a user without enough privileges', async() => { + const ctx = {req: {accessToken: {userId: 106}}}; + const businessId = 106; + createdAbsence = await app.models.WorkerCalendar.create({ + businessFk: businessId, + absenceTypeFk: 1, + dated: new Date() + }); + + let error; + await app.models.Worker.deleteAbsence(ctx, workerId, createdAbsence.id).catch(e => { + error = e; + }).finally(() => { + expect(error.message).toEqual(`You don't have enough privileges`); + }); + + expect(error).toBeDefined(); + }); + + it('should create a new absence', async() => { + const ctx = {req: {accessToken: {userId: 37}}}; + const businessId = 106; + + expect(createdAbsence.businessFk).toEqual(businessId); + + await app.models.Worker.deleteAbsence(ctx, workerId, createdAbsence.id); + + const deletedAbsence = await app.models.WorkerCalendar.findById(createdAbsence.id); + + expect(deletedAbsence).toBeNull(); + }); +}); diff --git a/modules/worker/back/methods/worker/specs/updateAbsence.spec.js b/modules/worker/back/methods/worker/specs/updateAbsence.spec.js new file mode 100644 index 000000000..689d36136 --- /dev/null +++ b/modules/worker/back/methods/worker/specs/updateAbsence.spec.js @@ -0,0 +1,38 @@ +const app = require('vn-loopback/server/server'); + +describe('Worker updateAbsence()', () => { + const workerId = 106; + let createdAbsence; + + afterAll(async() => { + const absence = await app.models.WorkerCalendar.findById(createdAbsence.id); + await absence.destroy(); + }); + + it('should return an error for a user without enough privileges', async() => { + const ctx = {req: {accessToken: {userId: 106}}}; + const expectedAbsenceTypeId = 2; + createdAbsence = await app.models.WorkerCalendar.create({ + businessFk: 106, + absenceTypeFk: 1, + dated: new Date() + }); + + let error; + await app.models.Worker.updateAbsence(ctx, workerId, createdAbsence.id, expectedAbsenceTypeId).catch(e => { + error = e; + }).finally(() => { + expect(error.message).toEqual(`You don't have enough privileges`); + }); + + expect(error).toBeDefined(); + }); + + it('should create a new absence', async() => { + const ctx = {req: {accessToken: {userId: 37}}}; + const expectedAbsenceTypeId = 2; + const updatedAbsence = await app.models.Worker.updateAbsence(ctx, workerId, createdAbsence.id, expectedAbsenceTypeId); + + expect(updatedAbsence.absenceTypeFk).toEqual(expectedAbsenceTypeId); + }); +}); diff --git a/modules/worker/back/methods/worker/updateAbsence.js b/modules/worker/back/methods/worker/updateAbsence.js index e129d228f..e35eafa0c 100644 --- a/modules/worker/back/methods/worker/updateAbsence.js +++ b/modules/worker/back/methods/worker/updateAbsence.js @@ -28,9 +28,11 @@ module.exports = Self => { Self.updateAbsence = async(ctx, id, absenceId, absenceTypeId) => { const models = Self.app.models; + const userId = ctx.req.accessToken.userId; const isSubordinate = await models.Worker.isSubordinate(ctx, id); + const isTeamBoss = await models.Account.hasRole(userId, 'teamBoss'); - if (!isSubordinate) + if (!isSubordinate || (isSubordinate && userId == id && !isTeamBoss)) throw new UserError(`You don't have enough privileges`); const absence = await models.WorkerCalendar.findById(absenceId); diff --git a/modules/worker/front/calendar/index.js b/modules/worker/front/calendar/index.js index b1fce0adb..32adc272d 100644 --- a/modules/worker/front/calendar/index.js +++ b/modules/worker/front/calendar/index.js @@ -164,7 +164,7 @@ class Controller extends Section { this.$http.patch(path, params).then(() => { event.color = absenceType.rgb; event.name = absenceType.name; - event.code = absenceType.code; + event.type = absenceType.code; this.repaintCanceller(() => this.refresh().then(calendar.repaint()) diff --git a/modules/worker/front/calendar/index.spec.js b/modules/worker/front/calendar/index.spec.js index a0589b632..9ec4166d9 100644 --- a/modules/worker/front/calendar/index.spec.js +++ b/modules/worker/front/calendar/index.spec.js @@ -3,20 +3,39 @@ import './index'; describe('Worker', () => { describe('Component vnWorkerCalendar', () => { let $httpBackend; + let $httpParamSerializer; let $scope; let controller; let year = new Date().getFullYear(); beforeEach(ngModule('worker')); - beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_) => { + beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpParamSerializer_, _$httpBackend_) => { $scope = $rootScope.$new(); $httpBackend = _$httpBackend_; + $httpParamSerializer = _$httpParamSerializer_; const $element = angular.element(''); controller = $componentController('vnWorkerCalendar', {$element, $scope}); controller.isSubordinate = true; + controller.absenceType = {id: 1, name: 'Holiday', code: 'holiday', rgb: 'red'}; + controller.$params.id = 106; + controller._worker = {id: 106}; })); + describe('worker() setter', () => { + it(`should set the data an then call the refresh() and getIsSubordinate() methods`, () => { + jest.spyOn(controller, 'refresh').mockReturnValue(new Promise(resolve => { + return resolve(); + })); + jest.spyOn(controller, 'getIsSubordinate').mockReturnValue(true); + + controller.worker = {id: 107}; + + expect(controller.refresh).toHaveBeenCalledWith(); + expect(controller.getIsSubordinate).toHaveBeenCalledWith(); + }); + }); + describe('started property', () => { it(`should return first day and month of current year`, () => { let started = new Date(year, 0, 1); @@ -131,6 +150,7 @@ describe('Worker', () => { const $event = {}; const $days = []; + controller.absenceType = null; controller.onSelection($event, $days); expect(controller.vnApp.showMessage).toHaveBeenCalledWith('Choose an absence type from the right menu'); @@ -153,6 +173,170 @@ describe('Worker', () => { expect(controller.create).toHaveBeenCalledWith(jasmine.any(Object), selectedDay); }); + + it(`should call to the delete() method`, () => { + jest.spyOn(controller, 'delete').mockReturnThis(); + + const selectedDay = new Date(); + const expectedEvent = { + dated: selectedDay, + type: 'holiday' + }; + const $event = { + target: { + closest: () => { + return {$ctrl: {}}; + } + } + }; + const $days = [selectedDay]; + controller.events[selectedDay.getTime()] = expectedEvent; + controller.absenceType = {id: 1, code: 'holiday'}; + controller.onSelection($event, $days); + + expect(controller.delete).toHaveBeenCalledWith(jasmine.any(Object), selectedDay, expectedEvent); + }); + + it(`should call to the edit() method`, () => { + jest.spyOn(controller, 'edit').mockReturnThis(); + + const selectedDay = new Date(); + const expectedEvent = { + dated: selectedDay, + type: 'leaveOfAbsence' + }; + const $event = { + target: { + closest: () => { + return {$ctrl: {}}; + } + } + }; + const $days = [selectedDay]; + controller.events[selectedDay.getTime()] = expectedEvent; + controller.absenceType = {id: 1, code: 'holiday'}; + controller.onSelection($event, $days); + + expect(controller.edit).toHaveBeenCalledWith(jasmine.any(Object), expectedEvent); + }); + }); + + describe('create()', () => { + it(`should make a HTTP POST query and then call to the repaintCanceller() method`, () => { + jest.spyOn(controller, 'repaintCanceller').mockReturnThis(); + + const dated = new Date(); + const calendarElement = {}; + const expectedResponse = {id: 10}; + + $httpBackend.expect('POST', `Workers/106/createAbsence`).respond(200, expectedResponse); + controller.create(calendarElement, dated); + $httpBackend.flush(); + + const createdEvent = controller.events[dated.getTime()]; + const absenceType = controller.absenceType; + + expect(createdEvent.absenceId).toEqual(expectedResponse.id); + expect(createdEvent.color).toEqual(absenceType.rgb); + expect(controller.repaintCanceller).toHaveBeenCalled(); + }); + }); + + describe('edit()', () => { + it(`should make a HTTP PATCH query and then call to the repaintCanceller() method`, () => { + jest.spyOn(controller, 'repaintCanceller').mockReturnThis(); + + const event = {absenceId: 10}; + const calendarElement = {}; + const newAbsenceType = { + id: 2, + name: 'Leave of absence', + code: 'leaveOfAbsence', + rgb: 'purple' + }; + controller.absenceType = newAbsenceType; + + const expectedParams = {absenceId: 10, absenceTypeId: 2}; + $httpBackend.expect('PATCH', `Workers/106/updateAbsence`, expectedParams).respond(200); + controller.edit(calendarElement, event); + $httpBackend.flush(); + + expect(event.name).toEqual(newAbsenceType.name); + expect(event.color).toEqual(newAbsenceType.rgb); + expect(event.type).toEqual(newAbsenceType.code); + expect(controller.repaintCanceller).toHaveBeenCalled(); + }); + }); + + describe('delete()', () => { + it(`should make a HTTP DELETE query and then call to the repaintCanceller() method`, () => { + jest.spyOn(controller, 'repaintCanceller').mockReturnThis(); + + const expectedParams = {absenceId: 10}; + const calendarElement = {}; + const selectedDay = new Date(); + const expectedEvent = { + dated: selectedDay, + type: 'leaveOfAbsence', + absenceId: 10 + }; + + controller.events[selectedDay.getTime()] = expectedEvent; + + const serializedParams = $httpParamSerializer(expectedParams); + $httpBackend.expect('DELETE', `Workers/106/deleteAbsence?${serializedParams}`).respond(200); + controller.delete(calendarElement, selectedDay, expectedEvent); + $httpBackend.flush(); + + const event = controller.events[selectedDay.getTime()]; + + expect(event).toBeUndefined(); + expect(controller.repaintCanceller).toHaveBeenCalled(); + }); + }); + + describe('repaintCanceller()', () => { + it(`should cancell the callback execution timer`, () => { + jest.spyOn(window, 'clearTimeout'); + jest.spyOn(window, 'setTimeout'); + + const timeoutId = 90; + controller.canceller = timeoutId; + + controller.repaintCanceller(() => { + return 'My callback'; + }); + + expect(window.clearTimeout).toHaveBeenCalledWith(timeoutId); + expect(window.setTimeout).toHaveBeenCalledWith(jasmine.any(Function), 500); + }); + }); + + describe('refresh()', () => { + it(`should make a HTTP GET query and then call to the onData() method`, () => { + jest.spyOn(controller, 'onData').mockReturnThis(); + + const dated = controller.date; + const started = new Date(dated.getTime()); + started.setMonth(0); + started.setDate(1); + + const ended = new Date(dated.getTime()); + ended.setMonth(12); + ended.setDate(0); + + controller.started = started; + controller.ended = ended; + + const expecteResponse = [{id: 1}]; + const expectedParams = {workerFk: 106, started: started, ended: ended}; + const serializedParams = $httpParamSerializer(expectedParams); + $httpBackend.expect('GET', `WorkerCalendars/absences?${serializedParams}`).respond(200, expecteResponse); + controller.refresh(); + $httpBackend.flush(); + + expect(controller.onData).toHaveBeenCalledWith(expecteResponse); + }); }); }); }); From 87fa66fe8c8949031e8f2f726e3233ece5e9bdb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20S=C3=A1nchez?= Date: Thu, 16 Jul 2020 11:45:44 +0200 Subject: [PATCH 20/22] Updated methods description --- modules/worker/back/methods/worker/createAbsence.js | 2 +- modules/worker/back/methods/worker/deleteAbsence.js | 2 +- modules/worker/back/methods/worker/updateAbsence.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/worker/back/methods/worker/createAbsence.js b/modules/worker/back/methods/worker/createAbsence.js index 9c65aa66a..28a2b9d9d 100644 --- a/modules/worker/back/methods/worker/createAbsence.js +++ b/modules/worker/back/methods/worker/createAbsence.js @@ -2,7 +2,7 @@ const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.remoteMethodCtx('createAbsence', { - description: 'Returns an array of absences from an specified worker', + description: 'Creates a new worker absence', accepts: [{ arg: 'id', type: 'Number', diff --git a/modules/worker/back/methods/worker/deleteAbsence.js b/modules/worker/back/methods/worker/deleteAbsence.js index d5dbfa00d..ea156d7eb 100644 --- a/modules/worker/back/methods/worker/deleteAbsence.js +++ b/modules/worker/back/methods/worker/deleteAbsence.js @@ -2,7 +2,7 @@ const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.remoteMethodCtx('deleteAbsence', { - description: 'Returns an array of absences from an specified worker', + description: 'Deletes a worker absence', accepts: [{ arg: 'id', type: 'Number', diff --git a/modules/worker/back/methods/worker/updateAbsence.js b/modules/worker/back/methods/worker/updateAbsence.js index e35eafa0c..719bca7e4 100644 --- a/modules/worker/back/methods/worker/updateAbsence.js +++ b/modules/worker/back/methods/worker/updateAbsence.js @@ -2,7 +2,7 @@ const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.remoteMethodCtx('updateAbsence', { - description: 'Returns an array of absences from an specified worker', + description: 'Updates a worker absence', accepts: [{ arg: 'id', type: 'Number', From cbb2ad7f44032cbf96f5b190736dc527c0c8b7a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20S=C3=A1nchez?= Date: Thu, 16 Jul 2020 11:59:16 +0200 Subject: [PATCH 21/22] Removed duplicated test --- modules/worker/front/calendar/index.spec.js | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/modules/worker/front/calendar/index.spec.js b/modules/worker/front/calendar/index.spec.js index 9ec4166d9..07e574d09 100644 --- a/modules/worker/front/calendar/index.spec.js +++ b/modules/worker/front/calendar/index.spec.js @@ -22,20 +22,6 @@ describe('Worker', () => { controller._worker = {id: 106}; })); - describe('worker() setter', () => { - it(`should set the data an then call the refresh() and getIsSubordinate() methods`, () => { - jest.spyOn(controller, 'refresh').mockReturnValue(new Promise(resolve => { - return resolve(); - })); - jest.spyOn(controller, 'getIsSubordinate').mockReturnValue(true); - - controller.worker = {id: 107}; - - expect(controller.refresh).toHaveBeenCalledWith(); - expect(controller.getIsSubordinate).toHaveBeenCalledWith(); - }); - }); - describe('started property', () => { it(`should return first day and month of current year`, () => { let started = new Date(year, 0, 1); @@ -64,7 +50,10 @@ describe('Worker', () => { describe('worker() setter', () => { it(`should perform a get query and set the reponse data on the model`, () => { - jest.spyOn(controller, 'getIsSubordinate').mockReturnThis(); + jest.spyOn(controller, 'getIsSubordinate').mockReturnValue(true); + jest.spyOn(controller, 'refresh').mockReturnValue(new Promise(resolve => { + return resolve(); + })); let today = new Date(); @@ -86,7 +75,7 @@ describe('Worker', () => { ] }); - controller.worker = {id: 1}; + controller.worker = {id: 107}; $httpBackend.flush(); let events = controller.events; From a07d960a2e9be86ceb133cc4138de8b2972a3f59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20S=C3=A1nchez?= Date: Thu, 16 Jul 2020 12:09:50 +0200 Subject: [PATCH 22/22] Fixed test --- modules/worker/front/calendar/index.spec.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/worker/front/calendar/index.spec.js b/modules/worker/front/calendar/index.spec.js index 07e574d09..9d14cca20 100644 --- a/modules/worker/front/calendar/index.spec.js +++ b/modules/worker/front/calendar/index.spec.js @@ -51,12 +51,8 @@ describe('Worker', () => { describe('worker() setter', () => { it(`should perform a get query and set the reponse data on the model`, () => { jest.spyOn(controller, 'getIsSubordinate').mockReturnValue(true); - jest.spyOn(controller, 'refresh').mockReturnValue(new Promise(resolve => { - return resolve(); - })); let today = new Date(); - let tomorrow = new Date(today.getTime()); tomorrow.setDate(tomorrow.getDate() + 1);