diff --git a/packages/component-fixture-manager/src/fixtures/fragments.js b/packages/component-fixture-manager/src/fixtures/fragments.js index 026712ff89a6764daef0f2af34120f5ac7870741..15e4cb3fa981f4316d1d686a6dc2414d783ed89b 100644 --- a/packages/component-fixture-manager/src/fixtures/fragments.js +++ b/packages/component-fixture-manager/src/fixtures/fragments.js @@ -99,6 +99,69 @@ const fragments = { createdOn: chance.timestamp(), updatedOn: chance.timestamp(), }, + { + recommendation: 'publish', + recommendationType: 'editorRecommendation', + comments: [ + { + content: chance.paragraph(), + public: true, + files: [ + { + id: chance.guid(), + name: 'file.pdf', + size: chance.natural(), + }, + ], + }, + ], + id: chance.guid(), + userId: handlingEditor.id, + createdOn: 1542361074012, + updatedOn: chance.timestamp(), + }, + { + recommendation: 'return-to-handling-editor', + recommendationType: 'editorRecommendation', + comments: [ + { + content: chance.paragraph(), + public: true, + files: [ + { + id: chance.guid(), + name: 'file.pdf', + size: chance.natural(), + }, + ], + }, + ], + id: chance.guid(), + userId: admin.id, + createdOn: 1542361115749, + updatedOn: chance.timestamp(), + }, + { + recommendation: 'publish', + recommendationType: 'editorRecommendation', + comments: [ + { + content: chance.paragraph(), + public: chance.bool(), + files: [ + { + id: chance.guid(), + name: 'file.pdf', + size: chance.natural(), + }, + ], + }, + ], + id: chance.guid(), + userId: handlingEditor.id, + createdOn: 1542361115750, + updatedOn: chance.timestamp(), + }, { recommendation: 'publish', recommendationType: 'editorRecommendation', @@ -117,7 +180,7 @@ const fragments = { ], id: chance.guid(), userId: admin.id, - createdOn: chance.timestamp(), + createdOn: 1542361115751, updatedOn: chance.timestamp(), }, ], @@ -457,6 +520,48 @@ const fragments = { updatedOn: chance.timestamp(), submittedOn: chance.timestamp(), }, + { + recommendation: 'publish', + recommendationType: 'editorRecommendation', + comments: [ + { + content: chance.paragraph(), + public: true, + files: [ + { + id: chance.guid(), + name: 'file.pdf', + size: chance.natural(), + }, + ], + }, + ], + id: chance.guid(), + userId: handlingEditor.id, + createdOn: 1542361074012, + updatedOn: chance.timestamp(), + }, + { + recommendation: 'return-to-handling-editor', + recommendationType: 'editorRecommendation', + comments: [ + { + content: chance.paragraph(), + public: true, + files: [ + { + id: chance.guid(), + name: 'file.pdf', + size: chance.natural(), + }, + ], + }, + ], + id: chance.guid(), + userId: admin.id, + createdOn: 1542361115749, + updatedOn: chance.timestamp(), + }, ], authors: [ { diff --git a/packages/component-helper-service/src/services/Fragment.js b/packages/component-helper-service/src/services/Fragment.js index a11783f269aebde945aefbef266d7d3d3fb09e88..7a0f27ae03d656269c6e5c757c094dd083f1ddb8 100644 --- a/packages/component-helper-service/src/services/Fragment.js +++ b/packages/component-helper-service/src/services/Fragment.js @@ -1,4 +1,4 @@ -const { get, remove } = require('lodash') +const { get, remove, findLast, last } = require('lodash') const config = require('config') const User = require('./User') @@ -150,6 +150,16 @@ class Fragment { ) } + canHEMakeAnotherRecommendation(currentUserRecommendations) { + const lastHERecommendation = last(currentUserRecommendations) + const { fragment: { recommendations = [] } } = this + const returnToHERecommendation = findLast( + recommendations, + r => r.recommendation === 'return-to-handling-editor', + ) + if (!returnToHERecommendation) return false + return returnToHERecommendation.createdOn > lastHERecommendation.createdOn + } async getReviewersAndEditorsData({ collection, UserModel }) { const { invitations = [], diff --git a/packages/component-helper-service/src/tests/fragment.test.js b/packages/component-helper-service/src/tests/fragment.test.js index 26598cfe8bd73fc40a8966a03ab51aa3552f3668..c5623dc18a8d7cf4812d3695189319f48b7e2202 100644 --- a/packages/component-helper-service/src/tests/fragment.test.js +++ b/packages/component-helper-service/src/tests/fragment.test.js @@ -15,6 +15,8 @@ const { recommendations: configRecommendations } = config const acceptedReviewerId = chance.guid() const submittedReviewerId1 = chance.guid() const submittedReviewerId2 = chance.guid() +const handlingEditorId = chance.guid() +const editorInChiefId = chance.guid() const fragment = { invitations: [ { @@ -281,4 +283,93 @@ describe('Fragment helper', () => { } }) }) + describe('canHEMakeAnotherRecommendation', () => { + it('should return true when He makes a recommendation after EIC decision was to return to HE', async () => { + testFragment.recommendations = [ + { + recommendation: 'publish', + recommendationType: 'editorRecommendation', + comments: [ + { + content: chance.paragraph(), + public: true, + files: [ + { + id: chance.guid(), + name: 'file.pdf', + size: chance.natural(), + }, + ], + }, + ], + id: chance.guid(), + userId: handlingEditorId, + createdOn: 1542361074012, + updatedOn: chance.timestamp(), + }, + { + recommendation: 'return-to-handling-editor', + recommendationType: 'editorRecommendation', + comments: [ + { + content: chance.paragraph(), + public: true, + files: [ + { + id: chance.guid(), + name: 'file.pdf', + size: chance.natural(), + }, + ], + }, + ], + id: chance.guid(), + userId: editorInChiefId, + createdOn: 1542361115749, + updatedOn: chance.timestamp(), + }, + ] + const currentUserRecommendations = testFragment.recommendations.filter( + r => r.userId === handlingEditorId, + ) + const fragmentHelper = new Fragment({ fragment: testFragment }) + const canHEMakeAnotherRecommendation = await fragmentHelper.canHEMakeAnotherRecommendation( + currentUserRecommendations, + ) + expect(canHEMakeAnotherRecommendation).toBe(true) + }) + it('should return false when He makes another recommendation', async () => { + testFragment.recommendations = [ + { + recommendation: 'publish', + recommendationType: 'editorRecommendation', + comments: [ + { + content: chance.paragraph(), + public: true, + files: [ + { + id: chance.guid(), + name: 'file.pdf', + size: chance.natural(), + }, + ], + }, + ], + id: chance.guid(), + userId: handlingEditorId, + createdOn: 1542361074012, + updatedOn: chance.timestamp(), + }, + ] + const currentUserRecommendations = testFragment.recommendations.filter( + r => r.userId === handlingEditorId, + ) + const fragmentHelper = new Fragment({ fragment: testFragment }) + const canHEMakeAnotherRecommendation = await fragmentHelper.canHEMakeAnotherRecommendation( + currentUserRecommendations, + ) + expect(canHEMakeAnotherRecommendation).toBe(false) + }) + }) }) diff --git a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/post.js b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/post.js index 2531241656d85e7d5090e4b9256e00dcb01f0ae6..b47234c1f1a017149269ba6e415ee49d5af6b159 100644 --- a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/post.js +++ b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/post.js @@ -1,5 +1,5 @@ const uuid = require('uuid') -const { pick, get, set, has, isEmpty, last } = require('lodash') +const { pick, get, set, has, isEmpty, last, chain } = require('lodash') const config = require('config') const { v4 } = require('uuid') const logger = require('@pubsweet/logger') @@ -54,9 +54,16 @@ module.exports = models => async (req, res) => { error: notFoundError.message, }) } - const currentUserRecommendation = get(fragment, 'recommendations', []).filter( - r => r.userId === req.user, - ) + const currentUserRecommendations = get( + fragment, + 'recommendations', + [], + ).filter(r => r.userId === req.user) + + const lastFragmentRecommendation = chain(fragment) + .get('recommendations', []) + .last() + .value() const authsome = authsomeHelper.getAuthsome(models) const target = { @@ -89,16 +96,32 @@ module.exports = models => async (req, res) => { } if ( last(collection.fragments) === fragmentId && - !isEmpty(currentUserRecommendation) + !isEmpty(currentUserRecommendations) ) { if (recommendationType === recommendations.type.review) { return res .status(400) .json({ error: 'Cannot write another review on this version.' }) } - return res - .status(400) - .json({ error: 'Cannot make another recommendation on this version.' }) + if ( + recommendationType === recommendations.type.editor && + !isEditorInChief && + !fragmentHelper.canHEMakeAnotherRecommendation(currentUserRecommendations) + ) { + return res.status(400).json({ + error: 'Cannot make another recommendation on this version.', + }) + } + if ( + recommendationType === recommendations.type.editor && + isEditorInChief && + recommendation !== recommendations.reject && + lastFragmentRecommendation.recommendation === 'return-to-handling-editor' + ) { + return res.status(400).json({ + error: 'Cannot make another recommendation on this version.', + }) + } } if ( diff --git a/packages/component-manuscript-manager/src/tests/collections/get.test.js b/packages/component-manuscript-manager/src/tests/collections/get.test.js index fb88e5658444b5c3bad2f2cf5ac7760bf9900537..aa4a1cfec4a3e9de92809e07e056a81b2aaede1f 100644 --- a/packages/component-manuscript-manager/src/tests/collections/get.test.js +++ b/packages/component-manuscript-manager/src/tests/collections/get.test.js @@ -61,7 +61,7 @@ describe('Get collections route handler', () => { expect(data).toHaveLength(2) expect(data[0].type).toEqual('collection') - expect(data[0].currentVersion.recommendations).toHaveLength(3) + expect(data[0].currentVersion.recommendations).toHaveLength(6) expect(data[0].currentVersion.authors[0]).not.toHaveProperty('email') }) diff --git a/packages/component-manuscript-manager/src/tests/fragmentsRecommendations/post.test.js b/packages/component-manuscript-manager/src/tests/fragmentsRecommendations/post.test.js index df52effe144daf17b1a9ec16e546c4f189b62187..5638eb69ff3be3a1a164b0226377f6c38d7f0486 100644 --- a/packages/component-manuscript-manager/src/tests/fragmentsRecommendations/post.test.js +++ b/packages/component-manuscript-manager/src/tests/fragmentsRecommendations/post.test.js @@ -571,6 +571,52 @@ describe('Post fragments recommendations route handler', () => { expect(data.error).toEqual('Cannot write another review on this version.') }) + it('should return success when creating another recommendation as a HE on the same version when EiC returned manuscript to He ', async () => { + const { noRecommendationHE } = testFixtures.users + const { noEditorRecomedationCollection } = testFixtures.collections + const { noEditorRecomedationFragment } = testFixtures.fragments + + const res = await requests.sendRequest({ + body, + userId: noRecommendationHE.id, + models, + route, + path, + params: { + collectionId: noEditorRecomedationCollection.id, + fragmentId: noEditorRecomedationFragment.id, + }, + }) + expect(res.statusCode).toBe(200) + const data = JSON.parse(res._getData()) + expect(data.userId).toEqual(noRecommendationHE.id) + }) + + it('should return an error when creating another recommendation as a HE on the same version after EiC made decision to publish', async () => { + const { handlingEditor } = testFixtures.users + const { collection } = testFixtures.collections + const { fragment } = testFixtures.fragments + body.recommendation = 'publish' + body.recommendationType = 'editorRecommendation' + + const res = await requests.sendRequest({ + body, + userId: handlingEditor.id, + models, + route, + path, + params: { + collectionId: collection.id, + fragmentId: fragment.id, + }, + }) + expect(res.statusCode).toBe(400) + const data = JSON.parse(res._getData()) + expect(data.error).toEqual( + 'Cannot make another recommendation on this version.', + ) + }) + it('should return an error when an EiC makes a decision on an older version of a manuscript', async () => { const { editorInChief } = testFixtures.users const { twoVersionsCollection } = testFixtures.collections