UNPKG

18.1 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 getStartOfSchoolYearIncludingSummerGap(stringDate) {
151 'use strict';
152 const date = parse(stringDate) || now || parse(getNow());
153 var ret = null;
154 if (date.getMonth() < 6) {
155 ret = toString(new Date(date.getFullYear() - 1, 8, 1));
156 } else {
157 ret = toString(new Date(date.getFullYear(), 8, 1));
158 }
159 return ret;
160}
161
162function getClosestSchoolYearSwitch (stringDate) {
163 const date = parse(stringDate) || now || parse(getNow());
164 if(date.getMonth() < 2) {
165 return toString(new Date(date.getFullYear() - 1, 8, 1));
166 } else {
167 return toString(new Date(date.getFullYear(), 8, 1));
168 }
169};
170
171function getNextDay(date, nbOfDays = 1) {
172 if (!date) {
173 return date;
174 }
175 let nextDay = parse(date);
176 nextDay.setDate(nextDay.getDate() + nbOfDays);
177 return toString(nextDay);
178}
179
180function getPreviousDay(date, nbOfDays = 1) {
181 if (!date) {
182 return date;
183 }
184 let previousDay = parse(date);
185 previousDay.setDate(previousDay.getDate() - nbOfDays);
186 return toString(previousDay);
187}
188
189function getPreviousMonth(date, nbOfMonths = 1) {
190 if(!date) {
191 return date;
192 }
193 let previousMonth = parse(date);
194 previousMonth.setMonth(previousMonth.getMonth() - nbOfMonths);
195 return toString(previousMonth);
196}
197
198function getNextMonth(date, nbOfMonths = 1) {
199 if(!date) {
200 return date;
201 }
202 let nextYear = parse(date);
203 nextYear.setMonth(nextYear.getMonth() + nbOfMonths);
204 return toString(nextYear);
205}
206
207function getPreviousYear(date, nbOfYears = 1) {
208 if(!date) {
209 return date;
210 }
211 let previousYear = parse(date);
212 previousYear.setFullYear(previousYear.getFullYear() - nbOfYears);
213 return toString(previousYear);
214}
215
216function getNextYear(date, nbOfYears = 1) {
217 if(!date) {
218 return date;
219 }
220 let nextYear = parse(date);
221 nextYear.setFullYear(nextYear.getFullYear() + nbOfYears);
222 return toString(nextYear);
223}
224
225function getActiveResources(array, referenceDate = getNow()) {
226 return array.filter(resource => {
227 if(resource.$$expanded) {
228 resource = resource.$$expanded;
229 }
230 return resource.startDate <= referenceDate && isAfter(resource.endDate, referenceDate);
231 });
232};
233
234function getNonAbolishedResources(array, referenceDate = getNow()) {
235 return array.filter(resource => {
236 if(resource.$$expanded) {
237 resource = resource.$$expanded;
238 }
239 return isAfter(resource.endDate, referenceDate);
240 });
241};
242
243function getAbolishedResources(array, referenceDate = getNow()) {
244 return array.filter(resource => {
245 if(resource.$$expanded) {
246 resource = resource.$$expanded;
247 }
248 return isBeforeOrEqual(resource.endDate, referenceDate);
249 });
250};
251
252/*function onEndDateSet(newEndDate, oldEndDate, dependencies, batch) {
253 if(newEndDate === oldEndDate) {
254 return;
255 }
256 for(let dependency of dependencies) {
257 if(dependency.endDate === oldEndDate) {
258 const index = _.findIndex(batch, elem => elem.href === dependency.$$meta.permalink);
259 if(index > -1) {
260 batch[index].body.endDate = newEndDate;
261 } else {
262 dependency.endDate = newEndDate;
263 batch.push({
264 href: dependency.$$meta.permalink,
265 verb: 'PUT',
266 body: dependency
267 });
268 }
269 }
270 }
271}
272
273function onStartDateSet(newStartDate, oldStartDate, dependencies, batch) {
274 if(newStartDate === oldStartDate) {
275 return;
276 }
277 for(let dependency of dependencies) {
278 if(dependency.startDate === oldStartDate) {
279 const index = _.findIndex(batch, elem => elem.href === dependency.$$meta.permalink);
280 if(index > -1) {
281 batch[index].body.startDate = newStartDate;
282 } else {
283 dependency.startDate = newStartDate;
284 batch.push({
285 href: dependency.$$meta.permalink,
286 verb: 'PUT',
287 body: dependency
288 });
289 }
290 }
291 }
292}*/
293
294class DateError {
295 constructor(message, body) {
296 this.message = message;
297 this.body = body;
298 }
299}
300
301const adaptPeriod = function(resource, options, periodic, referenceOptions) {
302 const onlyEnlargePeriod = referenceOptions && referenceOptions.onlyEnlargePeriod;
303 const onlyShortenPeriod = referenceOptions && referenceOptions.onlyShortenPeriod;
304 const intermediateStrategy = referenceOptions && referenceOptions.intermediateStrategy ? referenceOptions.intermediateStrategy : options.intermediateStrategy;
305 const startDateChanged = options.oldStartDate && (
306 (!onlyEnlargePeriod && !onlyShortenPeriod && options.oldStartDate !== resource.startDate) ||
307 (onlyEnlargePeriod && !onlyShortenPeriod && isBefore(resource.startDate, options.oldStartDate)) ||
308 (onlyShortenPeriod && !onlyEnlargePeriod && isAfter(resource.startDate, options.oldStartDate))
309 );
310 const endDateChanged =
311 (!onlyEnlargePeriod && !onlyShortenPeriod && options.oldEndDate !== resource.endDate) ||
312 (onlyEnlargePeriod && !onlyShortenPeriod && isAfter(resource.endDate, options.oldEndDate)) ||
313 (onlyShortenPeriod && !onlyEnlargePeriod && isBefore(resource.endDate, options.oldEndDate));
314
315 let ret = false;
316
317 if(endDateChanged) {
318 if(intermediateStrategy !== 'NONE' && isAfterOrEqual(periodic.startDate, resource.endDate)) {
319 throw new DateError((periodic.$$meta ? periodic.$$meta.permalink : JSON.stringify(periodic)) + ' starts after the new endDate, ' + resource.endDate, {
320 resource: resource,
321 periodic: periodic,
322 property: 'endDate',
323 code: 'starts.after.new.end'
324 });
325 }
326 if(intermediateStrategy !== 'NONE' && isAfter(periodic.endDate, resource.endDate) && isBefore(periodic.endDate, options.oldEndDate)) {
327 if(intermediateStrategy === 'FORCE') {
328 periodic.endDate = resource.endDate;
329 ret = true;
330 } else if(intermediateStrategy === 'ERROR') {
331 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.', {
332 resource: resource,
333 periodic: periodic,
334 property: 'endDate',
335 code: 'ends.inbetween'
336 });
337 }
338 } else if(periodic.endDate === options.oldEndDate) {
339 periodic.endDate = resource.endDate;
340 ret = true;
341 }
342 }
343 if(startDateChanged) {
344 if(intermediateStrategy !== 'NONE' && isBeforeOrEqual(periodic.endDate, resource.startDate)) {
345 throw new DateError(periodic.$$meta ? periodic.$$meta.permalink : JSON.stringify(periodic) + ' ends before the new startDate, ' + resource.startDate, {
346 resource: resource,
347 periodic: periodic,
348 property: 'startDate',
349 code: 'ends.before.new.start'
350 });
351 }
352 if(intermediateStrategy !== 'NONE' && periodic.startDate !== options.startDate && isAfter(periodic.startDate, options.oldStartDate) && isBefore(periodic.startDate, resource.startDate)) {
353 if(intermediateStrategy === 'FORCE') {
354 periodic.startDate = resource.startDate;
355 ret = true;
356 } else if(intermediateStrategy === 'ERROR') {
357 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.', {
358 resource: resource,
359 periodic: periodic,
360 property: 'startDate',
361 code: 'starts.inbetween'
362 });
363 }
364 } else if(periodic.startDate === options.oldStartDate) {
365 periodic.startDate = resource.startDate;
366 ret = true;
367 }
368 }
369 return ret;
370};
371
372const getDependenciesForReference = async function(resource, reference, api) {
373 reference.parameters = reference.parameters || {};
374 if(reference.subResources) {
375 reference.parameters.expand = reference.parameters.expand || '';
376 reference.subResources.forEach(subResource => {
377 if(reference.parameters.expand !== '') {
378 reference.parameters.expand += ',';
379 }
380 reference.parameters.expand += 'results.'+subResource;
381 });
382 }
383 if(reference.property) {
384 reference.parameters[reference.property] = resource.$$meta.permalink;
385 } else if(reference.commonReference) {
386 reference.parameters[reference.commonReference] = resource[reference.commonReference].href;
387 // } else if(reference.listOfHrefs) {
388 // reference.parameters[reference.listOfHrefs.parameterName] = reference.listOfHrefs.hrefs.join(',');
389 } else {
390 throw new Error('You either have to add a reference, a commonProperty or a listOfHrefs to the configuration for references.');
391 }
392 //reference.options = {logging: 'debug'}
393 let dependencies = await api.getAll(reference.href, reference.parameters, reference.options);
394 if(reference.filter) {
395 dependencies = dependencies.filter(reference.filter);
396 }
397 return dependencies;
398};
399
400const manageDateChanges = async function(resource, options, api) {
401 options.intermediateStrategy = options.intermediateStrategy || 'ERROR';
402 const startDateChanged = options.oldStartDate && options.oldStartDate !== resource.startDate;
403 const endDateChanged = options.oldEndDate !== resource.endDate;
404
405 if(!startDateChanged && !endDateChanged) {
406 return null;
407 }
408
409 if(options.properties) {
410 for(let property of options.properties) {
411 if(Array.isArray(resource[property])) {
412 for(let elem of resource[property]) {
413 adaptPeriod(resource, options, elem);
414 }
415 } else {
416 adaptPeriod(resource, options, resource[property]);
417 }
418 }
419 }
420
421 const ret = {};
422
423 if(options.references) {
424 if(!Array.isArray(options.references)) {
425 options.references = [options.references];
426 }
427
428 const errors = [];
429 for(let reference of options.references) {
430 reference.parameters = reference.parameters || {};
431 if(startDateChanged && !endDateChanged && !options.intermediateStrategy) {
432 reference.parameters.startDate = options.oldStartDate;
433 }
434 const dependencies = await getDependenciesForReference(resource, reference, api);
435 const changes = [];
436 dependencies.forEach( (dependency, $index) => {
437 const batchIndex = options.batch ? _.findIndex(options.batch, elem => elem.href === dependency.$$meta.permalink) : -1;
438 const body = batchIndex === -1 ? dependency : options.batch[batchIndex].body;
439
440 try {
441 const changed = adaptPeriod(resource, options, body, reference);
442 if(changed) {
443 changes.push(body);
444 if(options.batch && batchIndex === -1) {
445 options.batch.push({
446 href: body.$$meta.permalink,
447 verb: 'PUT',
448 body: body
449 });
450 }
451 if(reference.subResources) {
452 reference.subResources.forEach(subResource => {
453 const subResourceChanged = adaptPeriod(resource, options, body[subResource].$$expanded, reference);
454 if(subResourceChanged && options.batch && batchIndex === -1) {
455 options.batch.push({
456 href: body[subResource].href,
457 verb: 'PUT',
458 body: body[subResource].$$expanded
459 });
460 }
461 });
462 }
463 }
464 } catch (error) {
465 if(error instanceof DateError) {
466 // If strategy is FORCE and there is a dependency that starts after the new endDate than it has to be deleted.
467 const intermediateStrategy = reference.intermediateStrategy ? reference.intermediateStrategy : options.intermediateStrategy;
468 if(intermediateStrategy === 'FORCE' && error.body.code === 'starts.after.new.end' && options.batch) {
469 options.batch.push({
470 href: body.$$meta.permalink,
471 verb: 'DELETE'
472 });
473 } else {
474 errors.push(error);
475 }
476 } else {
477 throw error;
478 }
479 }
480 });
481 if(reference.alias) {
482 ret[reference.alias] = changes;
483 }
484 }
485 if(errors.length > 0) {
486 throw new DateError('There are references with conflicting periods that can not be adapted.', errors);
487 }
488 }
489
490 return ret;
491};
492
493const manageDeletes = async function(resource, options, api) {
494 const ret = {};
495
496 if(options.references) {
497 if(!Array.isArray(options.references)) {
498 options.references = [options.references];
499 }
500
501 for(let reference of options.references) {
502 const dependencies = await getDependenciesForReference(resource, reference, api);
503 dependencies.forEach( (dependency, $index) => {
504 const batchIndex = options.batch ? _.findIndex(options.batch, elem => elem.href === dependency.$$meta.permalink) : -1;
505 options.batch.push({
506 href: dependency.$$meta.permalink,
507 verb: 'DELETE'
508 });
509 if(batchIndex > -1) {
510 options.batch.splice(batchIndex, 1);
511 }
512 if(reference.subResources) {
513 reference.subResources.forEach(subResource => {
514 options.batch.push({
515 href: dependency[subResource].href,
516 verb: 'DELETE'
517 });
518 });
519 }
520 });
521 if(reference.alias) {
522 ret[reference.alias] = dependencies;
523 }
524 }
525 }
526
527 return ret;
528};
529
530module.exports = {
531 getNow: getNow,
532 setNow: setNow,
533 stripTime: stripTime,
534 toString: toString,
535 printDate: printDate,
536 printFutureForPeriodic: printFutureForPeriodic,
537 parse: parse,
538 isBeforeOrEqual: isBeforeOrEqual,
539 isAfterOrEqual: isAfterOrEqual,
540 isBefore: isBefore,
541 isAfter: isAfter,
542 getFirst: getFirst,
543 getLast: getLast,
544 isOverlapping: isOverlapping,
545 isCovering: isCovering,
546 isConsecutive: isConsecutive,
547 isConsecutiveWithOneDayInBetween: isConsecutiveWithOneDayInBetween,
548 getStartOfSchoolYear: getStartofSchoolYear,
549 getEndOfSchoolYear: getEndofSchoolYear,
550 getStartOfSchoolYearIncludingSummerGap: getStartOfSchoolYearIncludingSummerGap,
551 getClosestSchoolYearSwitch: getClosestSchoolYearSwitch,
552 getPreviousDay: getPreviousDay,
553 getNextDay: getNextDay,
554 getPreviousMonth: getPreviousMonth,
555 getNextMonth: getNextMonth,
556 getPreviousYear: getPreviousYear,
557 getNextYear: getNextYear,
558 getActiveResources: getActiveResources,
559 getNonAbolishedResources: getNonAbolishedResources,
560 getAbolishedResources: getAbolishedResources,
561 //onStartDateSet: onStartDateSet,
562 //onEndDateSet: onEndDateSet,
563 manageDateChanges: manageDateChanges,
564 adaptPeriod: adaptPeriod,
565 manageDeletes: manageDeletes,
566 DateError: DateError
567};