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,37 @@ 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
+ * @param {Object } transaction the wrapping transaction
21
+ * @returns {Promise<void> } a promise
22
+ */
23
+ async function updateComingMilestones ( updatedMilestone , transaction ) {
24
+ const comingMilestones = _ . sortBy ( await models . Milestone . findAll ( {
25
+ where : {
26
+ timelineId : updatedMilestone . timelineId ,
27
+ order : { $gt : updatedMilestone . order } ,
28
+ } ,
29
+ transaction,
30
+ } ) , 'order' ) ;
31
+ let startDate = moment . utc ( updatedMilestone . completionDate
32
+ ? updatedMilestone . completionDate
33
+ : updatedMilestone . endDate ) . add ( 1 , 'days' ) . toDate ( ) ;
34
+ const promises = _ . map ( comingMilestones , ( _milestone ) => {
35
+ const milestone = _milestone ;
36
+ if ( milestone . startDate . getTime ( ) !== startDate . getTime ( ) ) {
37
+ milestone . startDate = startDate ;
38
+ milestone . endDate = moment . utc ( startDate ) . add ( milestone . duration - 1 , 'days' ) . toDate ( ) ;
39
+ }
40
+ startDate = moment . utc ( milestone . completionDate
41
+ ? milestone . completionDate
42
+ : milestone . endDate ) . add ( 1 , 'days' ) . toDate ( ) ;
43
+ return milestone . save ( { transaction } ) ;
44
+ } ) ;
45
+ await Promise . all ( promises ) ;
46
+ }
47
+
16
48
const schema = {
17
49
params : {
18
50
timelineId : Joi . number ( ) . integer ( ) . positive ( ) . required ( ) ,
@@ -23,7 +55,7 @@ const schema = {
23
55
id : Joi . any ( ) . strip ( ) ,
24
56
name : Joi . string ( ) . max ( 255 ) . optional ( ) ,
25
57
description : Joi . string ( ) . max ( 255 ) ,
26
- duration : Joi . number ( ) . integer ( ) . optional ( ) ,
58
+ duration : Joi . number ( ) . integer ( ) . min ( 1 ) . optional ( ) ,
27
59
startDate : Joi . date ( ) . optional ( ) ,
28
60
endDate : Joi . date ( ) . allow ( null ) ,
29
61
completionDate : Joi . date ( ) . allow ( null ) ,
@@ -62,29 +94,10 @@ module.exports = [
62
94
timelineId : req . params . timelineId ,
63
95
} ) ;
64
96
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
- }
83
-
84
97
let original ;
85
98
let updated ;
86
99
87
- return models . sequelize . transaction ( ( ) =>
100
+ return models . sequelize . transaction ( transaction =>
88
101
// Find the milestone
89
102
models . Milestone . findOne ( { where } )
90
103
. then ( ( milestone ) => {
@@ -94,14 +107,34 @@ module.exports = [
94
107
apiErr . status = 404 ;
95
108
return Promise . reject ( apiErr ) ;
96
109
}
110
+ // if any of these keys was provided and is different from what's in the database, error
111
+ if ( [ 'startDate' , 'endDate' ]
112
+ . some ( key => entityToUpdate [ key ] && (
113
+ ! milestone [ key ] ||
114
+ ( milestone [ key ] && entityToUpdate [ key ] . getTime ( ) !== milestone [ key ] . getTime ( ) )
115
+ ) ) ) {
116
+ const apiErr = new Error ( 'Updating a milestone startDate or endDate is not allowed' ) ;
117
+ apiErr . status = 422 ;
118
+ return Promise . reject ( apiErr ) ;
119
+ }
120
+
121
+ if ( entityToUpdate . completionDate && entityToUpdate . completionDate < milestone . startDate ) {
122
+ const apiErr = new Error ( 'The milestone completionDate should be greater or equal than the startDate.' ) ;
123
+ apiErr . status = 422 ;
124
+ return Promise . reject ( apiErr ) ;
125
+ }
97
126
98
127
original = _ . omit ( milestone . toJSON ( ) , [ 'deletedAt' , 'deletedBy' ] ) ;
99
128
100
129
// Merge JSON fields
101
130
entityToUpdate . details = util . mergeJsonObjects ( milestone . details , entityToUpdate . details ) ;
102
131
132
+ if ( entityToUpdate . duration && entityToUpdate . duration !== milestone . duration ) {
133
+ entityToUpdate . endDate = moment . utc ( milestone . startDate ) . add ( entityToUpdate . duration - 1 , 'days' ) . toDate ( ) ;
134
+ }
135
+
103
136
// Update
104
- return milestone . update ( entityToUpdate ) ;
137
+ return milestone . update ( entityToUpdate , { transaction } ) ;
105
138
} )
106
139
. then ( ( updatedMilestone ) => {
107
140
// Omit deletedAt, deletedBy
@@ -118,6 +151,7 @@ module.exports = [
118
151
id : { $ne : updated . id } ,
119
152
order : updated . order ,
120
153
} ,
154
+ transaction,
121
155
} )
122
156
. then ( ( count ) => {
123
157
if ( count === 0 ) {
@@ -133,6 +167,7 @@ module.exports = [
133
167
id : { $ne : updated . id } ,
134
168
order : { $between : [ original . order + 1 , updated . order ] } ,
135
169
} ,
170
+ transaction,
136
171
} ) ;
137
172
}
138
173
@@ -144,8 +179,19 @@ module.exports = [
144
179
id : { $ne : updated . id } ,
145
180
order : { $between : [ updated . order , original . order - 1 ] } ,
146
181
} ,
182
+ transaction,
147
183
} ) ;
148
184
} ) ;
185
+ } )
186
+ . then ( ( ) => {
187
+ // Update dates of the other milestones only if the completionDate nor the duration changed
188
+ if ( ( ( ! original . completionDate && ! updated . completionDate ) ||
189
+ ( original . completionDate && updated . completionDate &&
190
+ original . completionDate . getTime ( ) === updated . completionDate . getTime ( ) ) ) &&
191
+ original . duration === updated . duration ) {
192
+ return Promise . resolve ( ) ;
193
+ }
194
+ return updateComingMilestones ( updated , transaction ) ;
149
195
} ) ,
150
196
)
151
197
. then ( ( ) => {
0 commit comments