@stamhoofd/backend 2.122.1 → 2.122.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/backend",
|
|
3
|
-
"version": "2.122.
|
|
3
|
+
"version": "2.122.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -57,20 +57,20 @@
|
|
|
57
57
|
"@simonbackx/simple-endpoints": "1.21.1",
|
|
58
58
|
"@simonbackx/simple-errors": "1.5.0",
|
|
59
59
|
"@simonbackx/simple-logging": "1.0.1",
|
|
60
|
-
"@stamhoofd/backend-env": "2.122.
|
|
61
|
-
"@stamhoofd/backend-i18n": "2.122.
|
|
62
|
-
"@stamhoofd/backend-middleware": "2.122.
|
|
63
|
-
"@stamhoofd/crons": "2.122.
|
|
64
|
-
"@stamhoofd/email": "2.122.
|
|
65
|
-
"@stamhoofd/excel-writer": "2.122.
|
|
66
|
-
"@stamhoofd/logging": "2.122.
|
|
67
|
-
"@stamhoofd/models": "2.122.
|
|
68
|
-
"@stamhoofd/object-differ": "2.122.
|
|
69
|
-
"@stamhoofd/queues": "2.122.
|
|
70
|
-
"@stamhoofd/sql": "2.122.
|
|
71
|
-
"@stamhoofd/structures": "2.122.
|
|
72
|
-
"@stamhoofd/types": "2.122.
|
|
73
|
-
"@stamhoofd/utility": "2.122.
|
|
60
|
+
"@stamhoofd/backend-env": "2.122.3",
|
|
61
|
+
"@stamhoofd/backend-i18n": "2.122.3",
|
|
62
|
+
"@stamhoofd/backend-middleware": "2.122.3",
|
|
63
|
+
"@stamhoofd/crons": "2.122.3",
|
|
64
|
+
"@stamhoofd/email": "2.122.3",
|
|
65
|
+
"@stamhoofd/excel-writer": "2.122.3",
|
|
66
|
+
"@stamhoofd/logging": "2.122.3",
|
|
67
|
+
"@stamhoofd/models": "2.122.3",
|
|
68
|
+
"@stamhoofd/object-differ": "2.122.3",
|
|
69
|
+
"@stamhoofd/queues": "2.122.3",
|
|
70
|
+
"@stamhoofd/sql": "2.122.3",
|
|
71
|
+
"@stamhoofd/structures": "2.122.3",
|
|
72
|
+
"@stamhoofd/types": "2.122.3",
|
|
73
|
+
"@stamhoofd/utility": "2.122.3",
|
|
74
74
|
"archiver": "7.0.1",
|
|
75
75
|
"axios": "1.16.0",
|
|
76
76
|
"base-x": "3.0.11",
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"stripe": "16.12.0"
|
|
92
92
|
},
|
|
93
93
|
"devDependencies": {
|
|
94
|
-
"@stamhoofd/test-utils": "2.122.
|
|
94
|
+
"@stamhoofd/test-utils": "2.122.3",
|
|
95
95
|
"@types/cookie": "0.6.0",
|
|
96
96
|
"@types/luxon": "3.7.1",
|
|
97
97
|
"@types/mailparser": "3.4.6",
|
|
@@ -107,5 +107,5 @@
|
|
|
107
107
|
"publishConfig": {
|
|
108
108
|
"access": "public"
|
|
109
109
|
},
|
|
110
|
-
"gitHead": "
|
|
110
|
+
"gitHead": "86bf4ada3c0296f6acb919ac2b40361931246bfa"
|
|
111
111
|
}
|
|
@@ -46,7 +46,8 @@ async function start(dryRun: boolean) {
|
|
|
46
46
|
// sort groups by start date
|
|
47
47
|
groups.sort((a, b) => a.settings.startDate.getTime() - b.settings.startDate.getTime());
|
|
48
48
|
|
|
49
|
-
const
|
|
49
|
+
const activeGroups = groups.filter(g => g.status !== GroupStatus.Archived && g.deletedAt === null);
|
|
50
|
+
const bestCurrentPeriodSpan = await calculateBestCurrentPeriodSpan(activeGroups);
|
|
50
51
|
|
|
51
52
|
const allGroups: Group[] = await migrateGroups({ groups, organization, periodSpan: bestCurrentPeriodSpan }, dryRun);
|
|
52
53
|
|
|
@@ -435,6 +436,7 @@ async function migrateRegistrations({ organization, period, originalGroup, newGr
|
|
|
435
436
|
newWaitingList.periodId = period.id;
|
|
436
437
|
newWaitingList.settings = GroupSettings.create({
|
|
437
438
|
name: TranslatedString.create($t(`%yh`) + ' ' + newGroup.settings.name.toString()),
|
|
439
|
+
period: period.getBaseStructure(),
|
|
438
440
|
});
|
|
439
441
|
|
|
440
442
|
const migrationData = new V1WaitingListMigrationData();
|
|
@@ -679,34 +681,9 @@ async function calculateBestCurrentPeriodSpan(groups: Group[]): Promise<{ startD
|
|
|
679
681
|
}
|
|
680
682
|
periodSpanCounts.set(bestPeriod, groups);
|
|
681
683
|
}
|
|
682
|
-
// #endregion
|
|
683
|
-
|
|
684
|
-
// #region find the best year
|
|
685
|
-
let topYear = defaultYear;
|
|
686
|
-
const groupsInTopPeriodSpan = periodSpanCounts.get(topPeriodSpan) ?? [];
|
|
687
|
-
|
|
688
|
-
// calculate the number of groups in each year
|
|
689
|
-
const yearMap = new Map<number, Group[]>();
|
|
690
|
-
for (const { group, year } of groupsInTopPeriodSpan) {
|
|
691
|
-
const groups = yearMap.get(year) ?? [];
|
|
692
|
-
groups.push(group);
|
|
693
|
-
yearMap.set(year, groups);
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
// get the year with the most groups
|
|
697
|
-
let topYearCount = 0;
|
|
698
|
-
for (const [year, groups] of yearMap.entries()) {
|
|
699
|
-
if (groups.length > topYearCount) {
|
|
700
|
-
topYearCount = groups.length;
|
|
701
|
-
topYear = year;
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
// #endregion
|
|
705
|
-
|
|
706
|
-
const startDate = new Date(topYear, topPeriodSpan.startMonth, 1);
|
|
707
|
-
const endDate = new Date((new Date(topYear, topPeriodSpan.startMonth + topPeriodSpan.span)).getTime() - 1);
|
|
708
684
|
|
|
709
|
-
|
|
685
|
+
const topYear = calculateTopYear(topPeriodSpan, groups);
|
|
686
|
+
return getTimeSpan(topYear, topPeriodSpan);
|
|
710
687
|
}
|
|
711
688
|
|
|
712
689
|
// #region helpers
|
|
@@ -831,9 +808,151 @@ function createPreviousGroup({ originalGroup, period, cycleInformation, index }:
|
|
|
831
808
|
startDate,
|
|
832
809
|
endDate,
|
|
833
810
|
hasCustomDates: true,
|
|
811
|
+
period: period.getBaseStructure(),
|
|
834
812
|
});
|
|
835
813
|
newGroup.type = originalGroup.type;
|
|
836
814
|
|
|
837
815
|
return newGroup;
|
|
838
816
|
}
|
|
839
817
|
// #endregion
|
|
818
|
+
|
|
819
|
+
function calculateTopYear(periodSpan: PeriodSpan, groups: Group[]): number {
|
|
820
|
+
let topYear = periodSpan.startMonth > 6 ? 2025 : 2026;
|
|
821
|
+
|
|
822
|
+
if (groups.length === 0) {
|
|
823
|
+
return topYear;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
const calculateTopYearHelper = (groups: Group[]): number => {
|
|
827
|
+
// most recent groups first (in case of ties)
|
|
828
|
+
const sortedGroups = [...groups].sort((a, b) => b.settings.startDate.getTime() - a.settings.startDate.getTime());
|
|
829
|
+
|
|
830
|
+
for (const group of sortedGroups) {
|
|
831
|
+
const year = getBestGroupYear(periodSpan, group);
|
|
832
|
+
if (year === null) {
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
const count = yearCount.get(year) ?? 0;
|
|
836
|
+
yearCount.set(year, count + 1);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
let topYearCount = 0;
|
|
840
|
+
|
|
841
|
+
for (const [year, count] of yearCount.entries()) {
|
|
842
|
+
if (count > topYearCount) {
|
|
843
|
+
topYearCount = count;
|
|
844
|
+
topYear = year;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
return topYear;
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
const yearCount = new Map<number, number>();
|
|
852
|
+
|
|
853
|
+
const areAllGroupsPossibleEvents: boolean = groups.every(group => isGroupShorterThanMonth(group));
|
|
854
|
+
|
|
855
|
+
if (areAllGroupsPossibleEvents) {
|
|
856
|
+
return calculateTopYearHelper(groups);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
const ifEventsExcluded = calculateTopYearHelper(groups.filter(g => !isGroupShorterThanMonth(g)));
|
|
860
|
+
const ifEventsIncluded = calculateTopYearHelper(groups);
|
|
861
|
+
const difference = Math.abs(ifEventsIncluded - ifEventsExcluded);
|
|
862
|
+
|
|
863
|
+
// the difference should never be greater than 1
|
|
864
|
+
if (difference > 1) {
|
|
865
|
+
const max = Math.max(ifEventsIncluded, ifEventsExcluded);
|
|
866
|
+
// year cannot be bigger than 2026
|
|
867
|
+
if (max > 2026) {
|
|
868
|
+
return Math.min(ifEventsIncluded, ifEventsExcluded, 2026);
|
|
869
|
+
}
|
|
870
|
+
return max;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// year cannot be bigger than 2026
|
|
874
|
+
if (ifEventsExcluded > 2026 && ifEventsIncluded < 2026) {
|
|
875
|
+
return ifEventsIncluded;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
return Math.min(ifEventsExcluded, 2026);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
function isGroupShorterThanMonth(group: Group) {
|
|
882
|
+
const diffMs = group.settings.endDate.getTime() - group.settings.startDate.getTime();
|
|
883
|
+
const msPerDay = 24 * 60 * 60 * 1000;
|
|
884
|
+
const diffDays = diffMs / msPerDay;
|
|
885
|
+
|
|
886
|
+
return diffDays < 30;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function getTimeSpan(year: number, periodSpan: PeriodSpan): { startDate: Date; endDate: Date } {
|
|
890
|
+
const startDate = new Date(year, periodSpan.startMonth, 1);
|
|
891
|
+
const endDate = new Date((new Date(year, periodSpan.startMonth + periodSpan.span)).getTime() - 1);
|
|
892
|
+
return { startDate, endDate };
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function getBestGroupYear(periodSpan: PeriodSpan, group: Group): number | null {
|
|
896
|
+
const startDate = group.settings.startDate;
|
|
897
|
+
const endDate = group.settings.endDate;
|
|
898
|
+
const groupTimeSpan = { startDate, endDate };
|
|
899
|
+
|
|
900
|
+
// ignore groups longer than 2 years
|
|
901
|
+
if (!isYearDifferenceLessThanTwo(startDate, endDate)) {
|
|
902
|
+
return null;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
const startYear = startDate.getFullYear();
|
|
906
|
+
|
|
907
|
+
const possibleYears: number[] = [startYear + 1, startYear, startYear - 1];
|
|
908
|
+
let topDaysOverlap = 0;
|
|
909
|
+
let topYear = periodSpan.startMonth > 6 ? 2025 : 2026;
|
|
910
|
+
|
|
911
|
+
for (const possibleYear of possibleYears) {
|
|
912
|
+
const possibleTimeSpan = getTimeSpan(possibleYear, periodSpan);
|
|
913
|
+
const result = getDaysMissingAndOverlap(possibleTimeSpan, groupTimeSpan);
|
|
914
|
+
if (result.daysOverlapping > topDaysOverlap) {
|
|
915
|
+
topDaysOverlap = result.daysOverlapping;
|
|
916
|
+
topYear = possibleYear;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
return topYear;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* Checks if the difference in year between two dates is less than 2.
|
|
925
|
+
* @param {Date} date1
|
|
926
|
+
* @param {Date} date2
|
|
927
|
+
* @returns {boolean}
|
|
928
|
+
*/
|
|
929
|
+
function isYearDifferenceLessThanTwo(date1: Date, date2: Date): boolean {
|
|
930
|
+
const d1 = new Date(date1);
|
|
931
|
+
const d2 = new Date(date2);
|
|
932
|
+
|
|
933
|
+
// Get the base year difference
|
|
934
|
+
const yearDiff = d2.getFullYear() - d1.getFullYear();
|
|
935
|
+
|
|
936
|
+
// If the absolute difference is 0 or 1, it's definitely less than 2
|
|
937
|
+
if (Math.abs(yearDiff) < 2) {
|
|
938
|
+
return true;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// If the difference is exactly 2, we need to check the exact time elapsed.
|
|
942
|
+
// Example: Jan 1, 2022 to Jan 1, 2024 is exactly 2 years (should be false).
|
|
943
|
+
// Example: Jan 1, 2022 to Dec 31, 2023 is almost 2 years (true, handled above by diff=1 logic? No, 2023-2022=1).
|
|
944
|
+
|
|
945
|
+
// Actually, let's re-evaluate:
|
|
946
|
+
// If d1 = 2022-06-01, d2 = 2024-05-31 -> Year diff = 2. But actual time < 2 years.
|
|
947
|
+
// If d1 = 2022-06-01, d2 = 2024-06-01 -> Year diff = 2. Actual time = 2 years (not less than).
|
|
948
|
+
|
|
949
|
+
// The simplest way to check "less than 2 years" accurately including leap years and days:
|
|
950
|
+
// Calculate the difference in milliseconds and compare against 2 years worth of ms.
|
|
951
|
+
|
|
952
|
+
const MS_PER_YEAR = 365.25 * 24 * 60 * 60 * 1000; // Approximate average year length
|
|
953
|
+
const LIMIT = 2 * MS_PER_YEAR;
|
|
954
|
+
|
|
955
|
+
const diffTime = Math.abs(d2.getTime() - d1.getTime());
|
|
956
|
+
|
|
957
|
+
return diffTime < LIMIT;
|
|
958
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Migration } from '@simonbackx/simple-database';
|
|
2
|
+
import { Group, RegistrationPeriod } from '@stamhoofd/models';
|
|
3
|
+
import { SeedTools } from '../helpers/SeedTools.js';
|
|
4
|
+
|
|
5
|
+
export default new Migration(async () => {
|
|
6
|
+
if (STAMHOOFD.environment === 'test') {
|
|
7
|
+
console.log('skipped in tests');
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (STAMHOOFD.platformName.toLowerCase() !== 'stamhoofd') {
|
|
12
|
+
console.log('skipped for platform (only runs for Stamhoofd): ' + STAMHOOFD.platformName);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const dryRun = false;
|
|
17
|
+
await start(dryRun);
|
|
18
|
+
|
|
19
|
+
if (dryRun) {
|
|
20
|
+
throw new Error('Migration did not finish because of dryRun');
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Groups in v1 have no cached period. This migration adds cached periods to groups that have no cached period yet.
|
|
26
|
+
* @param dryRun
|
|
27
|
+
*/
|
|
28
|
+
async function start(dryRun: boolean) {
|
|
29
|
+
await SeedTools.loop({
|
|
30
|
+
query: Group.select(),
|
|
31
|
+
batchSize: 50,
|
|
32
|
+
useTransactionPerBatch: true,
|
|
33
|
+
action: async (group: Group) => {
|
|
34
|
+
if (group.settings.period !== null) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const period = await RegistrationPeriod.getByID(group.periodId);
|
|
39
|
+
if (period) {
|
|
40
|
+
group.settings.period = period.getBaseStructure();
|
|
41
|
+
if (!dryRun) {
|
|
42
|
+
await group.save();
|
|
43
|
+
}
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
throw new Error(`No period found for group ${group.id} (periodId: ${group.periodId})`);
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
});
|
|
51
|
+
}
|