|
1 | 1 | /**
|
2 | 2 | * API to add a phase as work
|
3 | 3 | */
|
4 |
| - import validate from 'express-validation'; |
5 |
| - import _ from 'lodash'; |
6 |
| - import Joi from 'joi'; |
7 |
| - import Sequelize from 'sequelize'; |
| 4 | +import validate from 'express-validation'; |
| 5 | +import _ from 'lodash'; |
| 6 | +import Joi from 'joi'; |
| 7 | +import Sequelize from 'sequelize'; |
8 | 8 |
|
9 |
| - import models from '../../models'; |
10 |
| - import util from '../../util'; |
| 9 | +import models from '../../models'; |
| 10 | +import util from '../../util'; |
| 11 | +import { EVENT } from '../../constants'; |
11 | 12 |
|
12 |
| - const permissions = require('tc-core-library-js').middleware.permissions; |
| 13 | +const permissions = require('tc-core-library-js').middleware.permissions; |
13 | 14 |
|
14 |
| - const schema = { |
15 |
| - params: { |
16 |
| - projectId: Joi.number().integer().positive().required(), |
17 |
| - workStreamId: Joi.number().integer().positive().required(), |
18 |
| - }, |
19 |
| - body: { |
20 |
| - param: Joi.object().keys({ |
21 |
| - name: Joi.string().required(), |
22 |
| - status: Joi.string().required(), |
23 |
| - startDate: Joi.date().optional(), |
24 |
| - endDate: Joi.date().optional(), |
25 |
| - duration: Joi.number().min(0).optional(), |
26 |
| - budget: Joi.number().min(0).optional(), |
27 |
| - spentBudget: Joi.number().min(0).optional(), |
28 |
| - progress: Joi.number().min(0).optional(), |
29 |
| - details: Joi.any().optional(), |
30 |
| - order: Joi.number().integer().optional(), |
31 |
| - productTemplateId: Joi.number().integer().positive().optional(), |
32 |
| - }).required(), |
33 |
| - }, |
34 |
| - }; |
| 15 | +const schema = { |
| 16 | + params: { |
| 17 | + projectId: Joi.number().integer().positive().required(), |
| 18 | + workStreamId: Joi.number().integer().positive().required(), |
| 19 | + }, |
| 20 | + body: { |
| 21 | + param: Joi.object().keys({ |
| 22 | + name: Joi.string().required(), |
| 23 | + status: Joi.string().required(), |
| 24 | + startDate: Joi.date().optional(), |
| 25 | + endDate: Joi.date().optional(), |
| 26 | + duration: Joi.number().min(0).optional(), |
| 27 | + budget: Joi.number().min(0).optional(), |
| 28 | + spentBudget: Joi.number().min(0).optional(), |
| 29 | + progress: Joi.number().min(0).optional(), |
| 30 | + details: Joi.any().optional(), |
| 31 | + order: Joi.number().integer().optional(), |
| 32 | + productTemplateId: Joi.number().integer().positive().optional(), |
| 33 | + }).required(), |
| 34 | + }, |
| 35 | +}; |
35 | 36 |
|
36 |
| - module.exports = [ |
37 |
| - // validate request payload |
38 |
| - validate(schema), |
39 |
| - // check permission |
40 |
| - permissions('work.create'), |
41 |
| - // do the real work |
42 |
| - (req, res, next) => { |
43 |
| - // default values |
44 |
| - const projectId = _.parseInt(req.params.projectId); |
45 |
| - const workStreamId = _.parseInt(req.params.workStreamId); |
| 37 | +module.exports = [ |
| 38 | + // validate request payload |
| 39 | + validate(schema), |
| 40 | + // check permission |
| 41 | + permissions('work.create'), |
| 42 | + // do the real work |
| 43 | + (req, res, next) => { |
| 44 | + // default values |
| 45 | + const projectId = _.parseInt(req.params.projectId); |
| 46 | + const workStreamId = _.parseInt(req.params.workStreamId); |
46 | 47 |
|
47 |
| - const data = req.body.param; |
48 |
| - _.assign(data, { |
49 |
| - projectId, |
50 |
| - createdBy: req.authUser.userId, |
51 |
| - updatedBy: req.authUser.userId, |
52 |
| - }); |
| 48 | + const data = req.body.param; |
| 49 | + _.assign(data, { |
| 50 | + projectId, |
| 51 | + createdBy: req.authUser.userId, |
| 52 | + updatedBy: req.authUser.userId, |
| 53 | + }); |
53 | 54 |
|
54 |
| - let existingWorkStream = null; |
55 |
| - let newProjectPhase = null; |
| 55 | + let existingWorkStream = null; |
| 56 | + let newProjectPhase = null; |
56 | 57 |
|
57 |
| - req.log.debug('Create Work - Starting transaction'); |
58 |
| - models.sequelize.transaction(() => models.WorkStream.findOne({ |
59 |
| - where: { |
60 |
| - id: workStreamId, |
61 |
| - projectId, |
62 |
| - deletedAt: { $eq: null }, |
63 |
| - }, |
64 |
| - }) |
65 |
| - .then((_existingWorkStream) => { |
66 |
| - if (!_existingWorkStream) { |
67 |
| - // handle 404 |
68 |
| - const err = new Error(`active work stream not found for project id ${projectId} ` + |
69 |
| - `and work stream id ${workStreamId}`); |
70 |
| - err.status = 404; |
71 |
| - throw err; |
72 |
| - } |
| 58 | + req.log.debug('Create Work - Starting transaction'); |
| 59 | + models.sequelize.transaction(() => models.WorkStream.findOne({ |
| 60 | + where: { |
| 61 | + id: workStreamId, |
| 62 | + projectId, |
| 63 | + deletedAt: { $eq: null }, |
| 64 | + }, |
| 65 | + }) |
| 66 | + .then((_existingWorkStream) => { |
| 67 | + if (!_existingWorkStream) { |
| 68 | + // handle 404 |
| 69 | + const err = new Error(`active work stream not found for project id ${projectId} ` + |
| 70 | + `and work stream id ${workStreamId}`); |
| 71 | + err.status = 404; |
| 72 | + throw err; |
| 73 | + } |
73 | 74 |
|
74 |
| - existingWorkStream = _existingWorkStream; |
| 75 | + existingWorkStream = _existingWorkStream; |
75 | 76 |
|
76 |
| - if (data.startDate !== null && data.endDate !== null && data.startDate > data.endDate) { |
77 |
| - const err = new Error('startDate must not be after endDate.'); |
78 |
| - err.status = 422; |
79 |
| - throw err; |
80 |
| - } |
81 |
| - return models.ProjectPhase.create(data); |
82 |
| - }) |
83 |
| - .then((_newProjectPhase) => { |
84 |
| - newProjectPhase = _.omit(_newProjectPhase, ['deletedAt', 'deletedBy']); |
85 |
| - return existingWorkStream.addProjectPhase(_newProjectPhase.id); |
| 77 | + if (data.startDate !== null && data.endDate !== null && data.startDate > data.endDate) { |
| 78 | + const err = new Error('startDate must not be after endDate.'); |
| 79 | + err.status = 422; |
| 80 | + throw err; |
| 81 | + } |
| 82 | + return models.ProjectPhase.create(data); |
86 | 83 | })
|
87 |
| - .then(() => { |
88 |
| - req.log.debug('re-ordering the other phases'); |
| 84 | + .then((_newProjectPhase) => { |
| 85 | + newProjectPhase = _.omit(_newProjectPhase, ['deletedAt', 'deletedBy']); |
| 86 | + return existingWorkStream.addProjectPhase(_newProjectPhase.id); |
| 87 | + }) |
| 88 | + .then(() => { |
| 89 | + req.log.debug('re-ordering the other phases'); |
89 | 90 |
|
90 |
| - if (_.isNil(newProjectPhase.order)) { |
91 |
| - return Promise.resolve(); |
92 |
| - } |
93 |
| - // Increase the order of the other phases in the same project, |
94 |
| - // which have `order` >= this phase order |
95 |
| - return models.ProjectPhase.update({ order: Sequelize.literal('"order" + 1') }, { |
96 |
| - where: { |
97 |
| - projectId, |
98 |
| - id: { $ne: newProjectPhase.id }, |
99 |
| - order: { $gte: newProjectPhase.order }, |
100 |
| - }, |
101 |
| - }); |
102 |
| - }) |
103 |
| - .then(() => { |
104 |
| - if (_.isNil(data.productTemplateId)) { |
105 |
| - return Promise.resolve(); |
106 |
| - } |
| 91 | + if (_.isNil(newProjectPhase.order)) { |
| 92 | + return Promise.resolve(); |
| 93 | + } |
| 94 | + // Increase the order of the other phases in the same project, |
| 95 | + // which have `order` >= this phase order |
| 96 | + return models.ProjectPhase.update({ order: Sequelize.literal('"order" + 1') }, { |
| 97 | + where: { |
| 98 | + projectId, |
| 99 | + id: { $ne: newProjectPhase.id }, |
| 100 | + order: { $gte: newProjectPhase.order }, |
| 101 | + }, |
| 102 | + }); |
| 103 | + }) |
| 104 | + .then(() => { |
| 105 | + if (_.isNil(data.productTemplateId)) { |
| 106 | + return Promise.resolve(); |
| 107 | + } |
| 108 | + |
| 109 | + // Get the product template |
| 110 | + return models.ProductTemplate.findById(data.productTemplateId) |
| 111 | + .then((productTemplate) => { |
| 112 | + if (!productTemplate) { |
| 113 | + const err = new Error(`Product template does not exist with id = ${data.productTemplateId}`); |
| 114 | + err.status = 422; |
| 115 | + throw err; |
| 116 | + } |
107 | 117 |
|
108 |
| - // Get the product template |
109 |
| - return models.ProductTemplate.findById(data.productTemplateId) |
110 |
| - .then((productTemplate) => { |
111 |
| - if (!productTemplate) { |
112 |
| - const err = new Error(`Product template does not exist with id = ${data.productTemplateId}`); |
113 |
| - err.status = 422; |
114 |
| - throw err; |
115 |
| - } |
| 118 | + // Create the phase product |
| 119 | + return models.PhaseProduct.create({ |
| 120 | + name: productTemplate.name, |
| 121 | + templateId: data.productTemplateId, |
| 122 | + type: productTemplate.productKey, |
| 123 | + projectId, |
| 124 | + phaseId: newProjectPhase.id, |
| 125 | + createdBy: req.authUser.userId, |
| 126 | + updatedBy: req.authUser.userId, |
| 127 | + }) |
| 128 | + .then((phaseProduct) => { |
| 129 | + newProjectPhase.products = [ |
| 130 | + _.omit(phaseProduct.toJSON(), ['deletedAt', 'deletedBy']), |
| 131 | + ]; |
| 132 | + }); |
| 133 | + }); |
| 134 | + })) |
| 135 | + .then(() => { |
| 136 | + // Send events to buses |
| 137 | + req.log.debug('Sending event to RabbitMQ bus for project phase %d', newProjectPhase.id); |
| 138 | + req.app.services.pubsub.publish(EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED, |
| 139 | + newProjectPhase, |
| 140 | + { correlationId: req.id }, |
| 141 | + ); |
| 142 | + req.log.debug('Sending event to Kafka bus for project phase %d', newProjectPhase.id); |
| 143 | + req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED, { req, created: newProjectPhase }); |
116 | 144 |
|
117 |
| - // Create the phase product |
118 |
| - return models.PhaseProduct.create({ |
119 |
| - name: productTemplate.name, |
120 |
| - templateId: data.productTemplateId, |
121 |
| - type: productTemplate.productKey, |
122 |
| - projectId, |
123 |
| - phaseId: newProjectPhase.id, |
124 |
| - createdBy: req.authUser.userId, |
125 |
| - updatedBy: req.authUser.userId, |
126 |
| - }) |
127 |
| - .then((phaseProduct) => { |
128 |
| - newProjectPhase.products = [ |
129 |
| - _.omit(phaseProduct.toJSON(), ['deletedAt', 'deletedBy']), |
130 |
| - ]; |
131 |
| - }); |
132 |
| - }); |
133 |
| - })) |
134 |
| - .then(() => { |
135 |
| - res.status(201).json(util.wrapResponse(req.id, newProjectPhase)); |
136 |
| - }) |
137 |
| - .catch(next); |
138 |
| - }, |
139 |
| - ]; |
| 145 | + res.status(201).json(util.wrapResponse(req.id, newProjectPhase, 1, 201)); |
| 146 | + }) |
| 147 | + .catch((err) => { |
| 148 | + util.handleError('Error creating work', err, req, next); |
| 149 | + }); |
| 150 | + }, |
| 151 | +]; |
0 commit comments