3
3
*/
4
4
import validate from 'express-validation' ;
5
5
import _ from 'lodash' ;
6
+ import moment from 'moment' ;
6
7
import Joi from 'joi' ;
7
8
import Sequelize from 'sequelize' ;
8
9
import { middleware as tcMiddleware } from 'tc-core-library-js' ;
@@ -13,6 +14,52 @@ import models from '../../models';
13
14
14
15
const permissions = tcMiddleware . permissions ;
15
16
17
+ /**
18
+ * Cascades endDate/completionDate changes to all milestones with a greater order than the given one.
19
+ * @param {Object } updatedMilestone the milestone that was updated
20
+ * @returns {Promise<void> } a promise that resolves to the last found milestone. If no milestone exists with an
21
+ * order greater than the passed <b>updatedMilestone</b>, the promise will resolve to the passed
22
+ * <b>updatedMilestone</b>
23
+ */
24
+ function updateComingMilestones ( updatedMilestone ) {
25
+ return models . Milestone . findAll ( {
26
+ where : {
27
+ timelineId : updatedMilestone . timelineId ,
28
+ order : { $gt : updatedMilestone . order } ,
29
+ } ,
30
+ } ) . then ( ( affectedMilestones ) => {
31
+ const comingMilestones = _ . sortBy ( affectedMilestones , 'order' ) ;
32
+ let startDate = moment . utc ( updatedMilestone . completionDate
33
+ ? updatedMilestone . completionDate
34
+ : updatedMilestone . endDate ) . add ( 1 , 'days' ) . toDate ( ) ;
35
+ const promises = _ . map ( comingMilestones , ( _milestone ) => {
36
+ const milestone = _milestone ;
37
+
38
+ // Update the milestone startDate if different than the iterated startDate
39
+ if ( ! _ . isEqual ( milestone . startDate , startDate ) ) {
40
+ milestone . startDate = startDate ;
41
+ milestone . updatedBy = updatedMilestone . updatedBy ;
42
+ }
43
+
44
+ // Calculate the endDate, and update it if different
45
+ const endDate = moment . utc ( startDate ) . add ( milestone . duration - 1 , 'days' ) . toDate ( ) ;
46
+ if ( ! _ . isEqual ( milestone . endDate , endDate ) ) {
47
+ milestone . endDate = endDate ;
48
+ milestone . updatedBy = updatedMilestone . updatedBy ;
49
+ }
50
+
51
+ // Set the next startDate value to the next day after completionDate if present or the endDate
52
+ startDate = moment . utc ( milestone . completionDate
53
+ ? milestone . completionDate
54
+ : milestone . endDate ) . add ( 1 , 'days' ) . toDate ( ) ;
55
+ return milestone . save ( ) ;
56
+ } ) ;
57
+
58
+ // Resolve promise to the last updated milestone, or to the passed in updatedMilestone
59
+ return Promise . all ( promises ) . then ( updatedMilestones => updatedMilestones . pop ( ) || updatedMilestone ) ;
60
+ } ) ;
61
+ }
62
+
16
63
const schema = {
17
64
params : {
18
65
timelineId : Joi . number ( ) . integer ( ) . positive ( ) . required ( ) ,
@@ -23,9 +70,9 @@ const schema = {
23
70
id : Joi . any ( ) . strip ( ) ,
24
71
name : Joi . string ( ) . max ( 255 ) . optional ( ) ,
25
72
description : Joi . string ( ) . max ( 255 ) ,
26
- duration : Joi . number ( ) . integer ( ) . optional ( ) ,
27
- startDate : Joi . date ( ) . optional ( ) ,
28
- endDate : Joi . date ( ) . allow ( null ) ,
73
+ duration : Joi . number ( ) . integer ( ) . min ( 1 ) . optional ( ) ,
74
+ startDate : Joi . any ( ) . forbidden ( ) ,
75
+ endDate : Joi . any ( ) . forbidden ( ) ,
29
76
completionDate : Joi . date ( ) . allow ( null ) ,
30
77
status : Joi . string ( ) . max ( 45 ) . optional ( ) ,
31
78
type : Joi . string ( ) . max ( 45 ) . optional ( ) ,
@@ -62,24 +109,7 @@ module.exports = [
62
109
timelineId : req . params . timelineId ,
63
110
} ) ;
64
111
65
- // Validate startDate and endDate to be within the timeline startDate and endDate
66
- let error ;
67
- if ( req . body . param . startDate < req . timeline . startDate ) {
68
- error = 'Milestone startDate must not be before the timeline startDate' ;
69
- } else if ( req . body . param . endDate && req . timeline . endDate && req . body . param . endDate > req . timeline . endDate ) {
70
- error = 'Milestone endDate must not be after the timeline endDate' ;
71
- }
72
- if ( entityToUpdate . endDate && entityToUpdate . endDate < entityToUpdate . startDate ) {
73
- error = 'Milestone endDate must not be before startDate' ;
74
- }
75
- if ( entityToUpdate . completionDate && entityToUpdate . completionDate < entityToUpdate . startDate ) {
76
- error = 'Milestone endDate must not be before startDate' ;
77
- }
78
- if ( error ) {
79
- const apiErr = new Error ( error ) ;
80
- apiErr . status = 422 ;
81
- return next ( apiErr ) ;
82
- }
112
+ const timeline = req . timeline ;
83
113
84
114
let original ;
85
115
let updated ;
@@ -95,11 +125,21 @@ module.exports = [
95
125
return Promise . reject ( apiErr ) ;
96
126
}
97
127
128
+ if ( entityToUpdate . completionDate && entityToUpdate . completionDate < milestone . startDate ) {
129
+ const apiErr = new Error ( 'The milestone completionDate should be greater or equal than the startDate.' ) ;
130
+ apiErr . status = 422 ;
131
+ return Promise . reject ( apiErr ) ;
132
+ }
133
+
98
134
original = _ . omit ( milestone . toJSON ( ) , [ 'deletedAt' , 'deletedBy' ] ) ;
99
135
100
136
// Merge JSON fields
101
137
entityToUpdate . details = util . mergeJsonObjects ( milestone . details , entityToUpdate . details ) ;
102
138
139
+ if ( entityToUpdate . duration && entityToUpdate . duration !== milestone . duration ) {
140
+ entityToUpdate . endDate = moment . utc ( milestone . startDate ) . add ( entityToUpdate . duration - 1 , 'days' ) . toDate ( ) ;
141
+ }
142
+
103
143
// Update
104
144
return milestone . update ( entityToUpdate ) ;
105
145
} )
@@ -146,6 +186,21 @@ module.exports = [
146
186
} ,
147
187
} ) ;
148
188
} ) ;
189
+ } )
190
+ . then ( ( ) => {
191
+ // Update dates of the other milestones only if the completionDate or the duration changed
192
+ if ( ! _ . isEqual ( original . completionDate , updated . completionDate ) || original . duration !== updated . duration ) {
193
+ return updateComingMilestones ( updated )
194
+ . then ( ( lastTimelineMilestone ) => {
195
+ if ( ! _ . isEqual ( lastTimelineMilestone . endDate , timeline . endDate ) ) {
196
+ timeline . endDate = lastTimelineMilestone . endDate ;
197
+ timeline . updatedBy = lastTimelineMilestone . updatedBy ;
198
+ return timeline . save ( ) ;
199
+ }
200
+ return Promise . resolve ( ) ;
201
+ } ) ;
202
+ }
203
+ return Promise . resolve ( ) ;
149
204
} ) ,
150
205
)
151
206
. then ( ( ) => {
0 commit comments