UNPKG

17.7 kBJavaScriptView Raw
1const _ = {};
2_.remove = require('lodash/remove');
3_.findIndex = require('lodash/findIndex');
4_.filter = require('lodash/filter');
5
6let now = null;
7
8function appendZero(number) {
9 'use strict';
10 return number > 9 ? number : 0 + '' + number;
11}
12function toString(date) {
13 'use strict';
14 if(!date) {
15 return date;
16 }
17 return date.getFullYear() + '-' + appendZero(date.getMonth() + 1) + '-' + appendZero(date.getDate());
18}
19
20function parse(stringDate) {
21 'use strict';
22 if(!stringDate) {
23 return undefined;
24 }
25 var parts = stringDate.split('-');
26 return new Date(parseInt(parts[0], 10), appendZero(parseInt(parts[1], 10) - 1), appendZero(parseInt(parts[2], 10)));
27}
28
29function getNow() {
30 'use strict';
31 if(now) {
32 return toString(now);
33 }
34 return toString(new Date());
35}
36
37function setNow(newNow) {
38 'use strict';
39 now = parse(newNow);
40}
41
42function stripTime(isoDateStr) {
43 'use strict';
44 return isoDateStr ? isoDateStr.split('T')[0] : isoDateStr;
45}
46
47function isBeforeOrEqual(a, b) {
48 'use strict';
49 return a === b || !b || (a !== null && a < b);
50}
51
52function isAfter(a, b) {
53 'use strict';
54 return !isBeforeOrEqual(a, b);
55}
56
57function isAfterOrEqual(a, b) {
58 'use strict';
59 return a === b || !a || (b !== null && a > b);
60}
61
62function isBefore(a, b) {
63 'use strict';
64 return !isAfterOrEqual(a, b);
65}
66
67function getFirst(array) {
68 'use strict';
69 _.remove(array, function (x) {
70 return !x;
71 });
72 const sorted = array.sort(function (a, b) {
73 return a < b ? -1 : 1;
74 });
75 return sorted[0];
76}
77
78function getLast(array) {
79 'use strict';
80 const index = _.findIndex(array, function (x) {
81 return !x;
82 });
83 if (index > -1) {
84 return null;
85 }
86 const sorted = array.sort(function (a, b) {
87 return a < b ? -1 : 1;
88 });
89 return sorted[sorted.length - 1];
90}
91
92function isOverlapping(a, b) {
93 'use strict';
94 return isBefore(a.startDate, b.endDate) && isAfter(a.endDate, b.startDate);
95}
96
97function isCovering(a, b) {
98 'use strict';
99 return isBeforeOrEqual(a.startDate, b.startDate) && isAfterOrEqual(a.endDate, b.endDate);
100}
101
102function isConsecutive(a, b) {
103 return (a.endDate !== null && a.endDate === b.startDate) || (b.endDate !== null && b.endDate === a.startDate);
104}
105
106function isConsecutiveWithOneDayInBetween(a, b) {
107 return (a.endDate !== null && getNextDay(a.endDate) === b.startDate) || (b.endDate !== null && getNextDay(b.endDate) === a.startDate);
108}
109
110function printDate(dateString) {
111 return dateString.split('-').reverse().join('/');
112}
113
114const printFutureForPeriodic = (periodic) => {
115 let ret = '';
116 if (isAfter(periodic.startDate, getNow()) && periodic.endDate && isAfter(periodic.endDate, getNow())) {
117 ret += ' (van ' + printDate(periodic.startDate) + ' tot ' + printDate(periodic.endDate) + ')';
118 } else if (isAfter(periodic.startDate, getNow())) {
119 ret += ' (vanaf ' + printDate(periodic.startDate) + ')';
120 } else if (periodic.endDate && isAfter(periodic.endDate, getNow())) {
121 ret += ' (tot ' + printDate(periodic.endDate) + ')';
122 }
123 return ret;
124};
125
126function getEndofSchoolYear(stringDate) {
127 'use strict';
128 const date = parse(stringDate) || now || parse(getNow());
129 var ret = null;
130 if (date.getMonth() < 8) {
131 ret = toString(new Date(date.getFullYear(), 8, 1));
132 } else {
133 ret = toString(new Date(date.getFullYear() + 1, 8, 1));
134 }
135 return ret;
136}
137
138function getStartofSchoolYear(stringDate) {
139 'use strict';
140 const date = parse(stringDate) || now || parse(getNow());
141 var ret = null;
142 if (date.getMonth() < 8) {
143 ret = toString(new Date(date.getFullYear() - 1, 8, 1));
144 } else {
145 ret = toString(new Date(date.getFullYear(), 8, 1));
146 }
147 return ret;
148}
149
150function getClosestSchoolYearSwitch (stringDate) {
151 const date = parse(stringDate) || now || parse(getNow());
152 if(date.getMonth() < 2) {
153 return toString(new Date(date.getFullYear() - 1, 8, 1));
154 } else {
155 return toString(new Date(date.getFullYear(), 8, 1));
156 }
157};
158
159function getNextDay(date, nbOfDays = 1) {
160 if (!date) {
161 return date;
162 }
163 let nextDay = parse(date);
164 nextDay.setDate(nextDay.getDate() + nbOfDays);
165 return toString(nextDay);
166}
167
168function getPreviousDay(date, nbOfDays = 1) {
169 if (!date) {
170 return date;
171 }
172 let previousDay = parse(date);
173 previousDay.setDate(previousDay.getDate() - nbOfDays);
174 return toString(previousDay);
175}
176
177function getPreviousMonth(date, nbOfMonths = 1) {
178 if(!date) {
179 return date;
180 }
181 let previousMonth = parse(date);
182 previousMonth.setMonth(previousMonth.getMonth() - nbOfMonths);
183 return toString(previousMonth);
184}
185
186function getNextMonth(date, nbOfMonths = 1) {
187 if(!date) {
188 return date;
189 }
190 let nextYear = parse(date);
191 nextYear.setMonth(nextYear.getMonth() + nbOfMonths);
192 return toString(nextYear);
193}
194
195function getPreviousYear(date, nbOfYears = 1) {
196 if(!date) {
197 return date;
198 }
199 let previousYear = parse(date);
200 previousYear.setFullYear(previousYear.getFullYear() - nbOfYears);
201 return toString(previousYear);
202}
203
204function getNextYear(date, nbOfYears = 1) {
205 if(!date) {
206 return date;
207 }
208 let nextYear = parse(date);
209 nextYear.setFullYear(nextYear.getFullYear() + nbOfYears);
210 return toString(nextYear);
211}
212
213function getActiveResources(array, referenceDate = getNow()) {
214 return array.filter(resource => {
215 if(resource.$$expanded) {
216 resource = resource.$$expanded;
217 }
218 return resource.startDate <= referenceDate && isAfter(resource.endDate, referenceDate);
219 });
220};
221
222function getNonAbolishedResources(array, referenceDate = getNow()) {
223 return array.filter(resource => {
224 if(resource.$$expanded) {
225 resource = resource.$$expanded;
226 }
227 return isAfter(resource.endDate, referenceDate);
228 });
229};
230
231function getAbolishedResources(array, referenceDate = getNow()) {
232 return array.filter(resource => {
233 if(resource.$$expanded) {
234 resource = resource.$$expanded;
235 }
236 return isBeforeOrEqual(resource.endDate, referenceDate);
237 });
238};
239
240/*function onEndDateSet(newEndDate, oldEndDate, dependencies, batch) {
241 if(newEndDate === oldEndDate) {
242 return;
243 }
244 for(let dependency of dependencies) {
245 if(dependency.endDate === oldEndDate) {
246 const index = _.findIndex(batch, elem => elem.href === dependency.$$meta.permalink);
247 if(index > -1) {
248 batch[index].body.endDate = newEndDate;
249 } else {
250 dependency.endDate = newEndDate;
251 batch.push({
252 href: dependency.$$meta.permalink,
253 verb: 'PUT',
254 body: dependency
255 });
256 }
257 }
258 }
259}
260
261function onStartDateSet(newStartDate, oldStartDate, dependencies, batch) {
262 if(newStartDate === oldStartDate) {
263 return;
264 }
265 for(let dependency of dependencies) {
266 if(dependency.startDate === oldStartDate) {
267 const index = _.findIndex(batch, elem => elem.href === dependency.$$meta.permalink);
268 if(index > -1) {
269 batch[index].body.startDate = newStartDate;
270 } else {
271 dependency.startDate = newStartDate;
272 batch.push({
273 href: dependency.$$meta.permalink,
274 verb: 'PUT',
275 body: dependency
276 });
277 }
278 }
279 }
280}*/
281
282class DateError {
283 constructor(message, body) {
284 this.message = message;
285 this.body = body;
286 }
287}
288
289const adaptPeriod = function(resource, options, periodic, referenceOptions) {
290 const onlyEnlargePeriod = referenceOptions && referenceOptions.onlyEnlargePeriod;
291 const onlyShortenPeriod = referenceOptions && referenceOptions.onlyShortenPeriod;
292 const intermediateStrategy = referenceOptions && referenceOptions.intermediateStrategy ? referenceOptions.intermediateStrategy : options.intermediateStrategy;
293 const startDateChanged = options.oldStartDate && (
294 (!onlyEnlargePeriod && !onlyShortenPeriod && options.oldStartDate !== resource.startDate) ||
295 (onlyEnlargePeriod && !onlyShortenPeriod && isBefore(resource.startDate, options.oldStartDate)) ||
296 (onlyShortenPeriod && !onlyEnlargePeriod && isAfter(resource.startDate, options.oldStartDate))
297 );
298 const endDateChanged =
299 (!onlyEnlargePeriod && !onlyShortenPeriod && options.oldEndDate !== resource.endDate) ||
300 (onlyEnlargePeriod && !onlyShortenPeriod && isAfter(resource.endDate, options.oldEndDate)) ||
301 (onlyShortenPeriod && !onlyEnlargePeriod && isBefore(resource.endDate, options.oldEndDate));
302
303 let ret = false;
304
305 if(endDateChanged) {
306 if(intermediateStrategy !== 'NONE' && isAfterOrEqual(periodic.startDate, resource.endDate)) {
307 throw new DateError((periodic.$$meta ? periodic.$$meta.permalink : JSON.stringify(periodic)) + ' starts after the new endDate, ' + resource.endDate, {
308 resource: resource,
309 periodic: periodic,
310 property: 'endDate',
311 code: 'starts.after.new.end'
312 });
313 }
314 if(intermediateStrategy !== 'NONE' && isAfter(periodic.endDate, resource.endDate) && isBefore(periodic.endDate, options.oldEndDate)) {
315 if(intermediateStrategy === 'FORCE') {
316 periodic.endDate = resource.endDate;
317 ret = true;
318 } else if(intermediateStrategy === 'ERROR') {
319 throw new DateError(periodic.$$meta ? periodic.$$meta.permalink : JSON.stringify(periodic) + ' has an endDate ('+periodic.endDate+') in between the new endDate and the old endDate.', {
320 resource: resource,
321 periodic: periodic,
322 property: 'endDate',
323 code: 'ends.inbetween'
324 });
325 }
326 } else if(periodic.endDate === options.oldEndDate) {
327 periodic.endDate = resource.endDate;
328 ret = true;
329 }
330 }
331 if(startDateChanged) {
332 if(intermediateStrategy !== 'NONE' && isBeforeOrEqual(periodic.endDate, resource.startDate)) {
333 throw new DateError(periodic.$$meta ? periodic.$$meta.permalink : JSON.stringify(periodic) + ' ends before the new startDate, ' + resource.startDate, {
334 resource: resource,
335 periodic: periodic,
336 property: 'startDate',
337 code: 'ends.before.new.start'
338 });
339 }
340 if(intermediateStrategy !== 'NONE' && periodic.startDate !== options.startDate && isAfter(periodic.startDate, options.oldStartDate) && isBefore(periodic.startDate, resource.startDate)) {
341 if(intermediateStrategy === 'FORCE') {
342 periodic.startDate = resource.startDate;
343 ret = true;
344 } else if(intermediateStrategy === 'ERROR') {
345 throw new DateError(periodic.$$meta ? periodic.$$meta.permalink : JSON.stringify(periodic) + ' has a startDate ('+periodic.startDate+') in between the old startDate and the new startDate.', {
346 resource: resource,
347 periodic: periodic,
348 property: 'startDate',
349 code: 'starts.inbetween'
350 });
351 }
352 } else if(periodic.startDate === options.oldStartDate) {
353 periodic.startDate = resource.startDate;
354 ret = true;
355 }
356 }
357 return ret;
358};
359
360const getDependenciesForReference = async function(resource, reference, api) {
361 reference.parameters = reference.parameters || {};
362 if(reference.subResources) {
363 reference.parameters.expand = reference.parameters.expand || '';
364 reference.subResources.forEach(subResource => {
365 if(reference.parameters.expand !== '') {
366 reference.parameters.expand += ',';
367 }
368 reference.parameters.expand += 'results.'+subResource;
369 });
370 }
371 if(reference.property) {
372 reference.parameters[reference.property] = resource.$$meta.permalink;
373 } else if(reference.commonReference) {
374 reference.parameters[reference.commonReference] = resource[reference.commonReference].href;
375 // } else if(reference.listOfHrefs) {
376 // reference.parameters[reference.listOfHrefs.parameterName] = reference.listOfHrefs.hrefs.join(',');
377 } else {
378 throw new Error('You either have to add a reference, a commonProperty or a listOfHrefs to the configuration for references.');
379 }
380 //reference.options = {logging: 'debug'}
381 let dependencies = await api.getAll(reference.href, reference.parameters, reference.options);
382 if(reference.filter) {
383 dependencies = dependencies.filter(reference.filter);
384 }
385 return dependencies;
386};
387
388const manageDateChanges = async function(resource, options, api) {
389 options.intermediateStrategy = options.intermediateStrategy || 'ERROR';
390 const startDateChanged = options.oldStartDate && options.oldStartDate !== resource.startDate;
391 const endDateChanged = options.oldEndDate !== resource.endDate;
392
393 if(!startDateChanged && !endDateChanged) {
394 return null;
395 }
396
397 if(options.properties) {
398 for(let property of options.properties) {
399 if(Array.isArray(resource[property])) {
400 for(let elem of resource[property]) {
401 adaptPeriod(resource, options, elem);
402 }
403 } else {
404 adaptPeriod(resource, options, resource[property]);
405 }
406 }
407 }
408
409 const ret = {};
410
411 if(options.references) {
412 if(!Array.isArray(options.references)) {
413 options.references = [options.references];
414 }
415
416 const errors = [];
417 for(let reference of options.references) {
418 reference.parameters = reference.parameters || {};
419 if(startDateChanged && !endDateChanged && !options.intermediateStrategy) {
420 reference.parameters.startDate = options.oldStartDate;
421 }
422 const dependencies = await getDependenciesForReference(resource, reference, api);
423 const changes = [];
424 dependencies.forEach( (dependency, $index) => {
425 const batchIndex = options.batch ? _.findIndex(options.batch, elem => elem.href === dependency.$$meta.permalink) : -1;
426 const body = batchIndex === -1 ? dependency : options.batch[batchIndex].body;
427
428 try {
429 const changed = adaptPeriod(resource, options, body, reference);
430 if(changed) {
431 changes.push(body);
432 if(options.batch && batchIndex === -1) {
433 options.batch.push({
434 href: body.$$meta.permalink,
435 verb: 'PUT',
436 body: body
437 });
438 }
439 if(reference.subResources) {
440 reference.subResources.forEach(subResource => {
441 const subResourceChanged = adaptPeriod(resource, options, body[subResource].$$expanded, reference);
442 if(subResourceChanged && options.batch && batchIndex === -1) {
443 options.batch.push({
444 href: body[subResource].href,
445 verb: 'PUT',
446 body: body[subResource].$$expanded
447 });
448 }
449 });
450 }
451 }
452 } catch (error) {
453 if(error instanceof DateError) {
454 // If strategy is FORCE and there is a dependency that starts after the new endDate than it has to be deleted.
455 const intermediateStrategy = reference.intermediateStrategy ? reference.intermediateStrategy : options.intermediateStrategy;
456 if(intermediateStrategy === 'FORCE' && error.body.code === 'starts.after.new.end' && options.batch) {
457 options.batch.push({
458 href: body.$$meta.permalink,
459 verb: 'DELETE'
460 });
461 } else {
462 errors.push(error);
463 }
464 } else {
465 throw error;
466 }
467 }
468 });
469 if(reference.alias) {
470 ret[reference.alias] = changes;
471 }
472 }
473 if(errors.length > 0) {
474 throw new DateError('There are references with conflicting periods that can not be adapted.', errors);
475 }
476 }
477
478 return ret;
479};
480
481const manageDeletes = async function(resource, options, api) {
482 const ret = {};
483
484 if(options.references) {
485 if(!Array.isArray(options.references)) {
486 options.references = [options.references];
487 }
488
489 for(let reference of options.references) {
490 const dependencies = await getDependenciesForReference(resource, reference, api);
491 dependencies.forEach( (dependency, $index) => {
492 const batchIndex = options.batch ? _.findIndex(options.batch, elem => elem.href === dependency.$$meta.permalink) : -1;
493 options.batch.push({
494 href: dependency.$$meta.permalink,
495 verb: 'DELETE'
496 });
497 if(batchIndex > -1) {
498 options.batch.splice(batchIndex, 1);
499 }
500 if(reference.subResources) {
501 reference.subResources.forEach(subResource => {
502 options.batch.push({
503 href: dependency[subResource].href,
504 verb: 'DELETE'
505 });
506 });
507 }
508 });
509 if(reference.alias) {
510 ret[reference.alias] = dependencies;
511 }
512 }
513 }
514
515 return ret;
516};
517
518module.exports = {
519 getNow: getNow,
520 setNow: setNow,
521 stripTime: stripTime,
522 toString: toString,
523 printDate: printDate,
524 printFutureForPeriodic: printFutureForPeriodic,
525 parse: parse,
526 isBeforeOrEqual: isBeforeOrEqual,
527 isAfterOrEqual: isAfterOrEqual,
528 isBefore: isBefore,
529 isAfter: isAfter,
530 getFirst: getFirst,
531 getLast: getLast,
532 isOverlapping: isOverlapping,
533 isCovering: isCovering,
534 isConsecutive: isConsecutive,
535 isConsecutiveWithOneDayInBetween: isConsecutiveWithOneDayInBetween,
536 getStartOfSchoolYear: getStartofSchoolYear,
537 getEndOfSchoolYear: getEndofSchoolYear,
538 getClosestSchoolYearSwitch: getClosestSchoolYearSwitch,
539 getPreviousDay: getPreviousDay,
540 getNextDay: getNextDay,
541 getPreviousMonth: getPreviousMonth,
542 getNextMonth: getNextMonth,
543 getPreviousYear: getPreviousYear,
544 getNextYear: getNextYear,
545 getActiveResources: getActiveResources,
546 getNonAbolishedResources: getNonAbolishedResources,
547 getAbolishedResources: getAbolishedResources,
548 //onStartDateSet: onStartDateSet,
549 //onEndDateSet: onEndDateSet,
550 manageDateChanges: manageDateChanges,
551 adaptPeriod: adaptPeriod,
552 manageDeletes: manageDeletes,
553 DateError: DateError
554};