Skip to content

Commit 7a73858

Browse files
authored
Merge pull request #665 from eisbilir/feature/new-milestone-concept
update phase status
2 parents 75ec578 + 9ef86a7 commit 7a73858

File tree

9 files changed

+86
-32
lines changed

9 files changed

+86
-32
lines changed

docs/Project API.postman_collection.json

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"info": {
3-
"_postman_id": "34327e29-e237-4bca-9101-5d2b6a1e25ff",
3+
"_postman_id": "52f34e21-5b0b-4eb0-99fa-cbd1ac7f215a",
44
"name": "Project API",
55
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
66
},
@@ -5240,7 +5240,7 @@
52405240
],
52415241
"body": {
52425242
"mode": "raw",
5243-
"raw": "{\n\t\"name\": \"test project phase xxx\",\n\t\"status\": \"inactive\",\n\t\"startDate\": \"2018-05-14T00:00:00\",\n\t\"endDate\": \"2018-05-15T00:00:00\",\n\t\"budget\": 30,\n\t\"progress\": 15,\n\t\"details\": {\n\t\t\"message\": \"phase details\"\n\t}\n}"
5243+
"raw": "{\n\t\"name\": \"test project phase xxx\",\n\t\"status\": \"in_review\",\n\t\"startDate\": \"2018-05-14T00:00:00\",\n\t\"endDate\": \"2018-05-15T00:00:00\",\n\t\"budget\": 30,\n\t\"progress\": 15,\n\t\"details\": {\n\t\t\"message\": \"phase details\"\n\t}\n}"
52445244
},
52455245
"url": {
52465246
"raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}",
@@ -5273,7 +5273,7 @@
52735273
],
52745274
"body": {
52755275
"mode": "raw",
5276-
"raw": "{\n\t\"name\": \"test project phase xxx\",\n\t\"status\": \"inactive\",\n\t\"startDate\": \"2018-05-14T00:00:00\",\n\t\"endDate\": \"2018-05-15T00:00:00\",\n\t\"budget\": 30,\n\t\"progress\": 15,\n\t\"details\": {\n\t\t\"message\": \"phase details\"\n\t},\n\t\"order\": 1\n}"
5276+
"raw": "{\n\t\"name\": \"test project phase xxx\",\n\t\"status\": \"in_review\",\n\t\"startDate\": \"2018-05-14T00:00:00\",\n\t\"endDate\": \"2018-05-15T00:00:00\",\n\t\"budget\": 30,\n\t\"progress\": 15,\n\t\"details\": {\n\t\t\"message\": \"phase details\"\n\t},\n\t\"order\": 1\n}"
52775277
},
52785278
"url": {
52795279
"raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}",
@@ -5306,7 +5306,7 @@
53065306
],
53075307
"body": {
53085308
"mode": "raw",
5309-
"raw": "{\n\t\"name\": \"test project phase xxx\",\n\t\"status\": \"inactive\",\n\t\"startDate\": \"2018-05-14T00:00:00\",\n\t\"endDate\": \"2018-05-15T00:00:00\",\n\t\"budget\": 30,\n\t\"progress\": 15,\n\t\"details\": {\n\t\t\"message\": \"phase details\"\n\t},\n \"members\": [{{phaseMemberId-1}},{{phaseMemberId-2}}]\n}"
5309+
"raw": "{\n\t\"name\": \"test project phase xxx\",\n\t\"status\": \"in_review\",\n\t\"startDate\": \"2018-05-14T00:00:00\",\n\t\"endDate\": \"2018-05-15T00:00:00\",\n\t\"budget\": 30,\n\t\"progress\": 15,\n\t\"details\": {\n\t\t\"message\": \"phase details\"\n\t},\n \"members\": [{{phaseMemberId-1}},{{phaseMemberId-2}}]\n}"
53105310
},
53115311
"url": {
53125312
"raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}",
@@ -6106,7 +6106,7 @@
61066106
],
61076107
"body": {
61086108
"mode": "raw",
6109-
"raw": "{\n\t\"name\": \"test project phase\",\n\t\"status\": \"active\",\n\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\"budget\": 20,\n\t\"details\": {\n\t\t\"aDetails\": \"a details\"\n\t}\n}"
6109+
"raw": "{\n\t\"name\": \"test project phase\",\n\t\"status\": \"in_review\",\n\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\"budget\": 20,\n\t\"details\": {\n\t\t\"aDetails\": \"a details\"\n\t}\n}"
61106110
},
61116111
"url": {
61126112
"raw": "{{api-url}}/projects/{{projectId}}/phases",
@@ -6171,6 +6171,39 @@
61716171
},
61726172
"response": []
61736173
},
6174+
{
6175+
"name": "Update Phase",
6176+
"request": {
6177+
"method": "PATCH",
6178+
"header": [
6179+
{
6180+
"key": "Authorization",
6181+
"value": "Bearer {{jwt-token}}"
6182+
},
6183+
{
6184+
"key": "Content-Type",
6185+
"value": "application/json"
6186+
}
6187+
],
6188+
"body": {
6189+
"mode": "raw",
6190+
"raw": "{\n\t\"status\": \"in_review\"\n}"
6191+
},
6192+
"url": {
6193+
"raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}",
6194+
"host": [
6195+
"{{api-url}}"
6196+
],
6197+
"path": [
6198+
"projects",
6199+
"{{projectId}}",
6200+
"phases",
6201+
"{{phaseId}}"
6202+
]
6203+
}
6204+
},
6205+
"response": []
6206+
},
61746207
{
61756208
"name": "Create Phase Approval - approve Copy",
61766209
"event": [

migrations/20210802_project_phase_approval_table.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ CREATE TABLE "project_phase_approval" (
1212
"id" int8 NOT NULL DEFAULT nextval('project_phase_approval_id_seq'::regclass),
1313
"phaseId" int8 NOT NULL,
1414
"decision" "enum_project_phase_approval_decision" NOT NULL,
15-
"comment" varchar NOT NULL,
15+
"comment" varchar,
1616
"startDate" timestamptz NOT NULL,
1717
"endDate" timestamptz,
1818
"expectedEndDate" timestamptz NOT NULL,

src/models/projectPhaseApproval.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module.exports = function defineProjectPhaseApproval(sequelize, DataTypes) {
33
id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
44
phaseId: { type: DataTypes.BIGINT, allowNull: false },
55
decision: { type: DataTypes.ENUM, values: ['approve', 'reject'], allowNull: false },
6-
comment: { type: DataTypes.STRING, allowNull: false },
6+
comment: { type: DataTypes.STRING, allowNull: true },
77
startDate: { type: DataTypes.DATE, allowNull: false },
88
endDate: { type: DataTypes.DATE, allowNull: true },
99
expectedEndDate: { type: DataTypes.DATE, allowNull: false },

src/routes/phaseApprovals/create.js

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import validate from 'express-validation';
44
import { middleware as tcMiddleware } from 'tc-core-library-js';
55
import models from '../../models';
66
import util from '../../util';
7-
import { EVENT, RESOURCES, ROUTES } from '../../constants';
7+
import { EVENT, RESOURCES, ROUTES, PROJECT_PHASE_STATUS } from '../../constants';
88

99
/**
1010
* API to create a project phase approval.
@@ -14,7 +14,7 @@ const permissions = tcMiddleware.permissions;
1414
const createPhaseApprovalValidations = {
1515
body: Joi.object().keys({
1616
decision: Joi.string().valid('approve', 'reject').required(),
17-
comment: Joi.string().trim().max(255).required(),
17+
comment: Joi.string().trim().max(255).optional(),
1818
startDate: Joi.date().default(Date()),
1919
endDate: Joi.date().min(Joi.ref('startDate')).default(Date()),
2020
expectedEndDate: Joi.date().min(Joi.ref('startDate')).default(Date()),
@@ -36,6 +36,7 @@ module.exports = [
3636
const createdBy = _.parseInt(req.authUser.userId);
3737
const updatedBy = _.parseInt(req.authUser.userId);
3838
_.assign(approvalData, { phaseId, createdBy, updatedBy });
39+
let transaction;
3940
try {
4041
// check if project and phase exist
4142
const phase = await models.ProjectPhase.findOne({
@@ -54,7 +55,16 @@ module.exports = [
5455
err.status = 404;
5556
throw (err);
5657
}
57-
const phaseApproval = (await models.ProjectPhaseApproval.create(approvalData)).toJSON();
58+
if (phase.status !== PROJECT_PHASE_STATUS.IN_REVIEW) {
59+
const err = new Error(`Phase with id ${phaseId} must be ` +
60+
`${PROJECT_PHASE_STATUS.IN_REVIEW} status to make approval`);
61+
err.status = 400;
62+
throw (err);
63+
}
64+
transaction = await models.sequelize.transaction();
65+
const created = await models.ProjectPhaseApproval.create(approvalData, { transaction });
66+
await phase.update({ status: PROJECT_PHASE_STATUS.REVIEWED }, { transaction });
67+
const phaseApproval = created.toJSON();
5868
req.log.debug('created phase approval', JSON.stringify(phaseApproval, null, 2));
5969
const updatedPhase = _.cloneDeep(phase.toJSON());
6070
const approvals = _.isArray(updatedPhase.approvals) ? updatedPhase.approvals : [];
@@ -68,8 +78,12 @@ module.exports = [
6878
updatedPhase,
6979
phase.toJSON(),
7080
ROUTES.PHASES.UPDATE);
81+
await transaction.commit();
7182
res.json(phaseApproval);
7283
} catch (err) {
84+
if (!_.isUndefined(transaction)) {
85+
await transaction.rollback();
86+
}
7387
next(err);
7488
}
7589
},

src/routes/phaseApprovals/create.spec.js

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ describe('Create phase approvals', () => {
7171
models.ProjectPhase.create({
7272
name: 'test project phase',
7373
projectId,
74-
status: 'active',
74+
status: 'in_review',
7575
startDate: '2018-05-15T00:00:00Z',
7676
endDate: '2018-05-15T12:00:00Z',
7777
budget: 20.0,
@@ -168,6 +168,13 @@ describe('Create phase approvals', () => {
168168
});
169169
});
170170

171+
it('should update phase status to "reviewed" after approve', (done) => {
172+
models.ProjectPhase.findOne({ id: phaseId }).then((phase) => {
173+
phase.dataValues.status.should.be.eql('reviewed');
174+
done();
175+
});
176+
});
177+
171178
it('should return 400 when decision field is missing', (done) => {
172179
request(server)
173180
.post(`/v5/projects/${projectId}/phases/${phaseId}/approvals`)
@@ -183,21 +190,6 @@ describe('Create phase approvals', () => {
183190
});
184191
});
185192

186-
it('should return 400 when comment field is missing', (done) => {
187-
request(server)
188-
.post(`/v5/projects/${projectId}/phases/${phaseId}/approvals`)
189-
.set({
190-
Authorization: `Bearer ${testUtil.jwts.member}`,
191-
})
192-
.send(_.omit(requestBody, 'comment'))
193-
.expect(400)
194-
.end((err, res) => {
195-
const resJson = res.body;
196-
validateError(resJson, 'validation error: "comment" is required');
197-
done();
198-
});
199-
});
200-
201193
it.skip('should return 400 when startDate field is missing', (done) => {
202194
request(server)
203195
.post(`/v5/projects/${projectId}/phases/${phaseId}/approvals`)
@@ -275,5 +267,20 @@ describe('Create phase approvals', () => {
275267
done();
276268
});
277269
});
270+
271+
it('should return 400 when phase status is not in_review', (done) => {
272+
request(server)
273+
.post(`/v5/projects/${projectId}/phases/${phaseId}/approvals`)
274+
.set({
275+
Authorization: `Bearer ${testUtil.jwts.member}`,
276+
})
277+
.send(requestBody)
278+
.expect(400)
279+
.end((err, res) => {
280+
const resJson = res.body;
281+
validateError(resJson, `Phase with id ${phaseId} must be in_review status to make approval`);
282+
done();
283+
});
284+
});
278285
});
279286
});

src/routes/phaseProducts/delete.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ module.exports = [
4444
req,
4545
EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_REMOVED,
4646
RESOURCES.PHASE_PRODUCT,
47-
_.pick(deleted.toJSON(), 'id'));
47+
_.pick(deleted.toJSON(), ['id', 'projectId']));
4848

4949
res.status(204).json({});
5050
})

src/routes/phases/create.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Joi from 'joi';
44

55
import models from '../../models';
66
import util from '../../util';
7-
import { EVENT, RESOURCES } from '../../constants';
7+
import { EVENT, RESOURCES, PROJECT_PHASE_STATUS } from '../../constants';
88

99
import updatePhaseMemberService from '../phaseMembers/updateService';
1010

@@ -16,7 +16,7 @@ const addProjectPhaseValidations = {
1616
name: Joi.string().required(),
1717
description: Joi.string().optional(),
1818
requirements: Joi.string().optional(),
19-
status: Joi.string().required(),
19+
status: Joi.string().valid(..._.values(PROJECT_PHASE_STATUS)).required(),
2020
startDate: Joi.date().optional(),
2121
endDate: Joi.date().optional(),
2222
duration: Joi.number().min(0).optional(),

src/routes/phases/update.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import Joi from 'joi';
55
import { middleware as tcMiddleware } from 'tc-core-library-js';
66
import models from '../../models';
77
import util from '../../util';
8-
import { EVENT, RESOURCES, ROUTES } from '../../constants';
8+
import { EVENT, RESOURCES, ROUTES, PROJECT_PHASE_STATUS } from '../../constants';
99

1010
import updatePhaseMemberService from '../phaseMembers/updateService';
1111

@@ -16,7 +16,7 @@ const updateProjectPhaseValidation = {
1616
name: Joi.string().optional(),
1717
description: Joi.string().optional(),
1818
requirements: Joi.string().optional(),
19-
status: Joi.string().optional(),
19+
status: Joi.string().valid(..._.values(PROJECT_PHASE_STATUS)).optional(),
2020
startDate: Joi.date().optional(),
2121
endDate: Joi.date().optional(),
2222
duration: Joi.number().min(0).optional(),

src/routes/phases/update.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const updateBody = {
3535
name: 'test project phase xxx',
3636
description: 'test project phase description xxx',
3737
requirements: 'test project phase requirements xxx',
38-
status: 'inactive',
38+
status: 'in_review',
3939
startDate: '2018-05-11T00:00:00Z',
4040
endDate: '2018-05-12T12:00:00Z',
4141
budget: 123456.789,

0 commit comments

Comments
 (0)