@stamhoofd/backend 2.117.1 → 2.118.1
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 +10 -10
- package/src/endpoints/global/events/PatchEventsEndpoint.ts +96 -28
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +31 -4
- package/src/seeds/1752848561-groups-registration-periods.test.ts +87 -0
- package/src/seeds/1752848561-groups-registration-periods.ts +20 -0
- package/src/services/PlatformMembershipService.ts +60 -26
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/backend",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.118.1",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -55,14 +55,14 @@
|
|
|
55
55
|
"@simonbackx/simple-encoding": "2.23.1",
|
|
56
56
|
"@simonbackx/simple-endpoints": "1.20.1",
|
|
57
57
|
"@simonbackx/simple-logging": "^1.0.1",
|
|
58
|
-
"@stamhoofd/backend-i18n": "2.
|
|
59
|
-
"@stamhoofd/backend-middleware": "2.
|
|
60
|
-
"@stamhoofd/email": "2.
|
|
61
|
-
"@stamhoofd/models": "2.
|
|
62
|
-
"@stamhoofd/queues": "2.
|
|
63
|
-
"@stamhoofd/sql": "2.
|
|
64
|
-
"@stamhoofd/structures": "2.
|
|
65
|
-
"@stamhoofd/utility": "2.
|
|
58
|
+
"@stamhoofd/backend-i18n": "2.118.1",
|
|
59
|
+
"@stamhoofd/backend-middleware": "2.118.1",
|
|
60
|
+
"@stamhoofd/email": "2.118.1",
|
|
61
|
+
"@stamhoofd/models": "2.118.1",
|
|
62
|
+
"@stamhoofd/queues": "2.118.1",
|
|
63
|
+
"@stamhoofd/sql": "2.118.1",
|
|
64
|
+
"@stamhoofd/structures": "2.118.1",
|
|
65
|
+
"@stamhoofd/utility": "2.118.1",
|
|
66
66
|
"archiver": "^7.0.1",
|
|
67
67
|
"axios": "^1.13.2",
|
|
68
68
|
"cookie": "^0.7.0",
|
|
@@ -80,5 +80,5 @@
|
|
|
80
80
|
"publishConfig": {
|
|
81
81
|
"access": "public"
|
|
82
82
|
},
|
|
83
|
-
"gitHead": "
|
|
83
|
+
"gitHead": "7461e7ca17f68233be8a8acf1943f2d7882244fd"
|
|
84
84
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, patchObject, StringDecoder } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
3
|
-
import { Event, Group, Platform, RegistrationPeriod, Webshop } from '@stamhoofd/models';
|
|
3
|
+
import { Event, Group, OrganizationRegistrationPeriod, Platform, RegistrationPeriod, Webshop } from '@stamhoofd/models';
|
|
4
4
|
import { AuditLogSource, Event as EventStruct, Group as GroupStruct, GroupType, NamedObject, PermissionLevel } from '@stamhoofd/structures';
|
|
5
5
|
|
|
6
6
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
@@ -134,26 +134,18 @@ export class PatchEventsEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
134
134
|
event.name = put.name;
|
|
135
135
|
event.startDate = put.startDate;
|
|
136
136
|
event.endDate = put.endDate;
|
|
137
|
+
event.typeId = put.typeId;
|
|
137
138
|
|
|
138
|
-
const type = await PatchEventsEndpoint.getEventType(put.typeId);
|
|
139
|
-
event.typeId = type.id;
|
|
140
139
|
event.meta.organizationCache = eventOrganization ? NamedObject.create({ id: eventOrganization.id, name: eventOrganization.name }) : null;
|
|
141
140
|
await PatchEventsEndpoint.checkEventLimits(event);
|
|
142
141
|
|
|
143
142
|
if (put.group) {
|
|
144
|
-
|
|
145
|
-
await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
|
|
146
|
-
await event.syncGroupRequirements(group);
|
|
147
|
-
});
|
|
148
|
-
event.groupId = group.id;
|
|
143
|
+
await this.saveEventAndLinkExistingGroup(event, put.group);
|
|
149
144
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
PatchEventsEndpoint.throwIfAddressIsMissing(event);
|
|
145
|
+
else {
|
|
146
|
+
await event.save();
|
|
153
147
|
}
|
|
154
148
|
|
|
155
|
-
await event.save();
|
|
156
|
-
|
|
157
149
|
events.push(event);
|
|
158
150
|
}
|
|
159
151
|
|
|
@@ -228,10 +220,8 @@ export class PatchEventsEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
228
220
|
event.startDate = patch.startDate ?? event.startDate;
|
|
229
221
|
event.endDate = patch.endDate ?? event.endDate;
|
|
230
222
|
|
|
231
|
-
const type = await PatchEventsEndpoint.getEventType(patch.typeId ?? event.typeId);
|
|
232
|
-
|
|
233
223
|
if (patch.typeId) {
|
|
234
|
-
event.typeId =
|
|
224
|
+
event.typeId = patch.typeId;
|
|
235
225
|
}
|
|
236
226
|
|
|
237
227
|
await PatchEventsEndpoint.checkEventLimits(event);
|
|
@@ -317,10 +307,6 @@ export class PatchEventsEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
317
307
|
}
|
|
318
308
|
}
|
|
319
309
|
|
|
320
|
-
if (type.isLocationRequired === true) {
|
|
321
|
-
PatchEventsEndpoint.throwIfAddressIsMissing(event);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
310
|
if (patch.webshopId !== undefined) {
|
|
325
311
|
if (patch.webshopId === null) {
|
|
326
312
|
event.webshopId = null;
|
|
@@ -399,6 +385,21 @@ export class PatchEventsEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
399
385
|
static async checkEventLimits(event: Event) {
|
|
400
386
|
const type = await this.getEventType(event.typeId);
|
|
401
387
|
|
|
388
|
+
if (type.isLocationRequired) {
|
|
389
|
+
const address = event.meta.location?.address;
|
|
390
|
+
|
|
391
|
+
if (!address) {
|
|
392
|
+
throw new SimpleError({
|
|
393
|
+
code: 'invalid_field',
|
|
394
|
+
message: 'Empty number',
|
|
395
|
+
human: $t(`6b72f8bd-cd5b-423f-a556-be102d3c22e9`),
|
|
396
|
+
field: 'event_required',
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
address.throwIfIncomplete();
|
|
401
|
+
}
|
|
402
|
+
|
|
402
403
|
if (event.name.length < 2) {
|
|
403
404
|
throw new SimpleError({
|
|
404
405
|
code: 'invalid_field',
|
|
@@ -484,18 +485,85 @@ export class PatchEventsEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
484
485
|
}
|
|
485
486
|
}
|
|
486
487
|
|
|
487
|
-
private
|
|
488
|
-
const
|
|
488
|
+
private async saveEventAndLinkExistingGroup(event: Event, putGroup: GroupStruct): Promise<void> {
|
|
489
|
+
const existingGroup = await Group.getByID(putGroup.id);
|
|
489
490
|
|
|
490
|
-
if (!
|
|
491
|
+
if (!existingGroup) {
|
|
491
492
|
throw new SimpleError({
|
|
492
|
-
code: '
|
|
493
|
-
message:
|
|
494
|
-
|
|
495
|
-
|
|
493
|
+
code: 'group_not_found',
|
|
494
|
+
message: `Group with id ${putGroup.id} does not exist`,
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// keep track of original data for reset in case of failure
|
|
499
|
+
const originalGroupType = existingGroup.type;
|
|
500
|
+
|
|
501
|
+
if (!await Context.auth.canAccessGroup(existingGroup)) {
|
|
502
|
+
throw Context.auth.error($t(`9cb60490-97be-4d11-beaf-4bdcf8b02d53`));
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (event.organizationId !== existingGroup.organizationId) {
|
|
506
|
+
throw new SimpleError({
|
|
507
|
+
code: 'invalid_group',
|
|
508
|
+
message: 'Group has different organization id',
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (existingGroup.settings.eventId !== null && existingGroup.settings.eventId !== event.id) {
|
|
513
|
+
throw new SimpleError({
|
|
514
|
+
code: 'invalid_group',
|
|
515
|
+
message: 'Group is already linked to another event',
|
|
496
516
|
});
|
|
497
517
|
}
|
|
498
518
|
|
|
499
|
-
|
|
519
|
+
if (existingGroup.type !== GroupType.EventRegistration) {
|
|
520
|
+
if (existingGroup.type !== GroupType.Membership) {
|
|
521
|
+
throw new SimpleError({
|
|
522
|
+
code: 'invalid_group',
|
|
523
|
+
message: 'Can only link a group of type EventRegistration or Membership',
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
existingGroup.type = GroupType.EventRegistration;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
existingGroup.settings.eventId = event.id;
|
|
530
|
+
|
|
531
|
+
// update group categories
|
|
532
|
+
const period = await RegistrationPeriod.getByID(existingGroup.periodId);
|
|
533
|
+
if (!period) {
|
|
534
|
+
throw new SimpleError({
|
|
535
|
+
code: 'not_found',
|
|
536
|
+
message: 'No period found for group',
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const organizationPeriod = await OrganizationRegistrationPeriod.select().where('organizationId', existingGroup.organizationId).where('periodId', period.id).first(true);
|
|
541
|
+
if (!organizationPeriod) {
|
|
542
|
+
throw new SimpleError({
|
|
543
|
+
code: 'not_found',
|
|
544
|
+
message: 'No organization period found for group',
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
event.groupId = existingGroup.id;
|
|
548
|
+
await event.save();
|
|
549
|
+
|
|
550
|
+
try {
|
|
551
|
+
await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
|
|
552
|
+
await event.syncGroupRequirements(existingGroup);
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
catch (e) {
|
|
556
|
+
// reset the group
|
|
557
|
+
existingGroup.type = originalGroupType;
|
|
558
|
+
existingGroup.settings.eventId = null;
|
|
559
|
+
await existingGroup.save();
|
|
560
|
+
|
|
561
|
+
// delete the event again if failed to link the group
|
|
562
|
+
await event.delete();
|
|
563
|
+
throw e;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const allGroups = await Group.getAll(organizationPeriod.organizationId, organizationPeriod.periodId);
|
|
567
|
+
await organizationPeriod.cleanCategories(allGroups);
|
|
500
568
|
}
|
|
501
569
|
}
|
|
@@ -3,7 +3,7 @@ import { AutoEncoderPatchType, ConvertArrayToPatchableArray, Decoder, isEmptyPat
|
|
|
3
3
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
4
4
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
5
5
|
import { AuditLog, BalanceItem, Document, Group, Member, MemberFactory, MemberPlatformMembership, MemberResponsibilityRecord, MemberWithRegistrations, MemberWithUsersAndRegistrations, MemberWithUsersRegistrationsAndGroups, mergeTwoMembers, Organization, Platform, RateLimiter, Registration, RegistrationPeriod, User } from '@stamhoofd/models';
|
|
6
|
-
import { AuditLogReplacement, AuditLogReplacementType, AuditLogSource, AuditLogType, EmergencyContact, GroupType, MemberDetails, MemberResponsibility, MembersBlob, MemberWithRegistrationsBlob, Parent, PermissionLevel, SetupStepType } from '@stamhoofd/structures';
|
|
6
|
+
import { AuditLogReplacement, AuditLogReplacementType, AuditLogSource, AuditLogType, EmergencyContact, GroupType, MemberDetails, MemberResponsibility, MembersBlob, MemberWithRegistrationsBlob, Parent, PermissionLevel, PlatformMembershipTypeBehaviour, SetupStepType } from '@stamhoofd/structures';
|
|
7
7
|
import { Formatter } from '@stamhoofd/utility';
|
|
8
8
|
|
|
9
9
|
import { Email } from '@stamhoofd/email';
|
|
@@ -535,14 +535,41 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
535
535
|
)
|
|
536
536
|
.first(false);
|
|
537
537
|
|
|
538
|
-
if (existing) {
|
|
538
|
+
if (existing && (membershipType.behaviour === PlatformMembershipTypeBehaviour.Days || !existing.generated || existing.locked || existing.price < membership.price)) {
|
|
539
|
+
if (membershipType.behaviour === PlatformMembershipTypeBehaviour.Days) {
|
|
540
|
+
throw new SimpleError({
|
|
541
|
+
code: 'invalid_field',
|
|
542
|
+
field: 'startDate',
|
|
543
|
+
message: 'Overlapping memberships',
|
|
544
|
+
human: $t(`84ef7751-ddf9-4591-ba5b-53371c67bdc3`),
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
if (existing.locked) {
|
|
548
|
+
throw new SimpleError({
|
|
549
|
+
code: 'invalid_field',
|
|
550
|
+
field: 'startDate',
|
|
551
|
+
message: 'Overlapping memberships',
|
|
552
|
+
human: $t(`723af14b-38b8-4f33-a645-ed5087fc7461`),
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
if (!existing.generated) {
|
|
556
|
+
throw new SimpleError({
|
|
557
|
+
code: 'invalid_field',
|
|
558
|
+
field: 'startDate',
|
|
559
|
+
message: 'Overlapping memberships',
|
|
560
|
+
human: $t(`b0280152-295f-48af-94e0-111c92dbda32`),
|
|
561
|
+
});
|
|
562
|
+
}
|
|
539
563
|
throw new SimpleError({
|
|
540
564
|
code: 'invalid_field',
|
|
541
565
|
field: 'startDate',
|
|
542
|
-
message: '
|
|
543
|
-
human: $t(`
|
|
566
|
+
message: 'Overlapping memberships',
|
|
567
|
+
human: $t(`1c0d9219-ef24-46ad-9b9c-f09b8d093fc8`),
|
|
544
568
|
});
|
|
545
569
|
}
|
|
570
|
+
else if (existing) {
|
|
571
|
+
await existing.doDelete();
|
|
572
|
+
}
|
|
546
573
|
|
|
547
574
|
// Save if okay
|
|
548
575
|
await membership.save();
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { GroupFactory, OrganizationFactory, RegistrationPeriodFactory } from '@stamhoofd/models';
|
|
2
|
+
import { checkShouldSetCustomDates } from './1752848561-groups-registration-periods.js';
|
|
3
|
+
|
|
4
|
+
describe('migration.groupsRegistrationPeriods', () => {
|
|
5
|
+
describe('checkShouldSetCustomDate', () => {
|
|
6
|
+
const cases: { group: { startDate: Date; endDate: Date }; period: { startDate: Date; endDate: Date }; expected: boolean }[] = [
|
|
7
|
+
// case 1
|
|
8
|
+
{
|
|
9
|
+
group: {
|
|
10
|
+
startDate: new Date(2023, 0, 1),
|
|
11
|
+
endDate: new Date(2023, 11, 31),
|
|
12
|
+
},
|
|
13
|
+
period: {
|
|
14
|
+
startDate: new Date(2023, 0, 1),
|
|
15
|
+
endDate: new Date(2023, 11, 31),
|
|
16
|
+
},
|
|
17
|
+
expected: false,
|
|
18
|
+
},
|
|
19
|
+
// case 2
|
|
20
|
+
{
|
|
21
|
+
group: {
|
|
22
|
+
startDate: new Date(2023, 0, 1),
|
|
23
|
+
endDate: new Date(2023, 11, 31),
|
|
24
|
+
},
|
|
25
|
+
period: {
|
|
26
|
+
startDate: new Date(2023, 0, 29),
|
|
27
|
+
endDate: new Date(2023, 11, 31),
|
|
28
|
+
},
|
|
29
|
+
expected: false,
|
|
30
|
+
},
|
|
31
|
+
// case 3
|
|
32
|
+
{
|
|
33
|
+
group: {
|
|
34
|
+
startDate: new Date(2023, 0, 1),
|
|
35
|
+
endDate: new Date(2023, 11, 31),
|
|
36
|
+
},
|
|
37
|
+
period: {
|
|
38
|
+
startDate: new Date(2023, 0, 1),
|
|
39
|
+
endDate: new Date(2024, 0, 28),
|
|
40
|
+
},
|
|
41
|
+
expected: false,
|
|
42
|
+
},
|
|
43
|
+
// case 4
|
|
44
|
+
{
|
|
45
|
+
group: {
|
|
46
|
+
startDate: new Date(2023, 0, 1),
|
|
47
|
+
endDate: new Date(2023, 11, 31),
|
|
48
|
+
},
|
|
49
|
+
period: {
|
|
50
|
+
startDate: new Date(2023, 1, 1),
|
|
51
|
+
endDate: new Date(2023, 11, 31),
|
|
52
|
+
},
|
|
53
|
+
expected: true,
|
|
54
|
+
},
|
|
55
|
+
// case 5
|
|
56
|
+
{
|
|
57
|
+
group: {
|
|
58
|
+
startDate: new Date(2023, 0, 1),
|
|
59
|
+
endDate: new Date(2023, 11, 31),
|
|
60
|
+
},
|
|
61
|
+
period: {
|
|
62
|
+
startDate: new Date(2023, 0, 1),
|
|
63
|
+
endDate: new Date(2023, 10, 30),
|
|
64
|
+
},
|
|
65
|
+
expected: true,
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
cases.forEach((testCase, index) => {
|
|
70
|
+
it(`Case ${index + 1}`, async () => {
|
|
71
|
+
// arrange
|
|
72
|
+
const period = await new RegistrationPeriodFactory(testCase.period).create();
|
|
73
|
+
const organization = await new OrganizationFactory({ period }).create();
|
|
74
|
+
period.organizationId = organization.id;
|
|
75
|
+
await period.save();
|
|
76
|
+
const group = await new GroupFactory({ organization, period }).create();
|
|
77
|
+
group.settings.startDate = testCase.group.startDate;
|
|
78
|
+
group.settings.endDate = testCase.group.endDate;
|
|
79
|
+
await group.save();
|
|
80
|
+
|
|
81
|
+
// act
|
|
82
|
+
const result = checkShouldSetCustomDates(group, period);
|
|
83
|
+
expect(result).toBe(testCase.expected);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -119,6 +119,7 @@ async function migrateGroups({ groups, organization, periodSpan }: { groups: Gro
|
|
|
119
119
|
const currentCycle = originalGroup.cycle;
|
|
120
120
|
const originalGroupId: string = originalGroup.id;
|
|
121
121
|
originalGroup.periodId = currentGroupPeriod.id;
|
|
122
|
+
originalGroup.settings.hasCustomDates = checkShouldSetCustomDates(originalGroup, currentGroupPeriod);
|
|
122
123
|
|
|
123
124
|
// first migrate registrations for the current cycle
|
|
124
125
|
await migrateRegistrations({ organization, period: currentGroupPeriod, originalGroup, newGroup: originalGroup, cycle: currentCycle }, dryRun);
|
|
@@ -360,6 +361,24 @@ async function migrateGroups({ groups, organization, periodSpan }: { groups: Gro
|
|
|
360
361
|
return result;
|
|
361
362
|
}
|
|
362
363
|
|
|
364
|
+
export function checkShouldSetCustomDates(group: Group, period: RegistrationPeriod): boolean {
|
|
365
|
+
// if difference between period start and group start, or between period and group end is bigger than 1 month
|
|
366
|
+
return isDifferenceBiggerThanMonth(period.startDate, group.settings.startDate) || isDifferenceBiggerThanMonth(period.endDate, group.settings.endDate);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* True if difference larger than 30 days.
|
|
371
|
+
* @param date1
|
|
372
|
+
* @param date2
|
|
373
|
+
*/
|
|
374
|
+
function isDifferenceBiggerThanMonth(date1: Date, date2: Date): boolean {
|
|
375
|
+
const diffMs = Math.abs(date1.getTime() - date2.getTime());
|
|
376
|
+
const msPerDay = 24 * 60 * 60 * 1000;
|
|
377
|
+
const diffDays = diffMs / msPerDay;
|
|
378
|
+
|
|
379
|
+
return diffDays > 30;
|
|
380
|
+
}
|
|
381
|
+
|
|
363
382
|
async function migrateRegistrations({ organization, period, originalGroup, newGroup, cycle }: { organization: Organization; period: RegistrationPeriod; originalGroup: Group; newGroup: Group; cycle: number }, dryRun: boolean) {
|
|
364
383
|
// what for waiting lists of archive groups (previous cycles)?
|
|
365
384
|
let waitingList: Group | null = null;
|
|
@@ -742,6 +761,7 @@ function createPreviousGroup({ originalGroup, period, cycleInformation, index }:
|
|
|
742
761
|
name: new TranslatedString(`${originalSettings.name.toString()} (${extraName})`),
|
|
743
762
|
startDate,
|
|
744
763
|
endDate,
|
|
764
|
+
hasCustomDates: true,
|
|
745
765
|
});
|
|
746
766
|
newGroup.type = originalGroup.type;
|
|
747
767
|
|
|
@@ -251,22 +251,40 @@ export class PlatformMembershipService {
|
|
|
251
251
|
const cheapestMembership = defaultMembershipsWithOrganization.sort((a, b) => {
|
|
252
252
|
const tagIdsA = a.organization?.meta.tags ?? [];
|
|
253
253
|
const tagIdsB = b.organization?.meta.tags ?? [];
|
|
254
|
-
|
|
254
|
+
let aPrice = a.membership.getPrice(
|
|
255
255
|
period.id,
|
|
256
256
|
a.registration.startDate ?? a.registration.registeredAt ?? a.registration.createdAt,
|
|
257
257
|
tagIdsA,
|
|
258
258
|
shouldApplyReducedPrice,
|
|
259
259
|
) ?? 10000000;
|
|
260
|
-
|
|
260
|
+
let bPrice = b.membership.getPrice(
|
|
261
261
|
period.id,
|
|
262
262
|
b.registration.startDate ?? b.registration.registeredAt ?? b.registration.createdAt,
|
|
263
263
|
tagIdsB,
|
|
264
264
|
shouldApplyReducedPrice,
|
|
265
265
|
) ?? 10000000;
|
|
266
266
|
|
|
267
|
+
const chargeVia = platform.membershipOrganizationId;
|
|
268
|
+
if (a.registration.organizationId === chargeVia) {
|
|
269
|
+
aPrice = 0;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (b.registration.organizationId === chargeVia) {
|
|
273
|
+
bPrice = 0;
|
|
274
|
+
}
|
|
275
|
+
|
|
267
276
|
const diff = aPrice - bPrice;
|
|
268
277
|
if (diff === 0) {
|
|
269
|
-
return Sorter.
|
|
278
|
+
return Sorter.stack(
|
|
279
|
+
Sorter.byDateValue(
|
|
280
|
+
b.registration.startDate ?? b.registration.registeredAt ?? b.registration.createdAt,
|
|
281
|
+
a.registration.startDate ?? a.registration.registeredAt ?? a.registration.createdAt,
|
|
282
|
+
),
|
|
283
|
+
Sorter.byDateValue(
|
|
284
|
+
b.registration.createdAt,
|
|
285
|
+
a.registration.createdAt,
|
|
286
|
+
),
|
|
287
|
+
);
|
|
270
288
|
}
|
|
271
289
|
return diff;
|
|
272
290
|
})[0];
|
|
@@ -291,10 +309,47 @@ export class PlatformMembershipService {
|
|
|
291
309
|
}
|
|
292
310
|
}
|
|
293
311
|
|
|
312
|
+
const membership = new MemberPlatformMembership();
|
|
313
|
+
|
|
314
|
+
if (!didFind) {
|
|
315
|
+
// Calculate price - do not save yet
|
|
316
|
+
const periodConfig = cheapestMembership.membership.periods.get(period.id);
|
|
317
|
+
if (!periodConfig) {
|
|
318
|
+
console.error('Missing membership prices for membership type ' + cheapestMembership.membership.id + ' and period ' + period.id);
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Pre calculate the price
|
|
323
|
+
membership.memberId = me.id;
|
|
324
|
+
membership.membershipTypeId = cheapestMembership.membership.id;
|
|
325
|
+
membership.organizationId = cheapestMembership.registration.organizationId;
|
|
326
|
+
membership.periodId = period.id;
|
|
327
|
+
|
|
328
|
+
// Note: the dates will get modified in the price calculation
|
|
329
|
+
membership.startDate = periodConfig.startDate;
|
|
330
|
+
membership.endDate = periodConfig.endDate;
|
|
331
|
+
membership.expireDate = periodConfig.expireDate;
|
|
332
|
+
membership.generated = true;
|
|
333
|
+
await membership.calculatePrice(me, cheapestMembership.registration);
|
|
334
|
+
|
|
335
|
+
// Check if we have a not-locked but non-generated one that is cheaper
|
|
336
|
+
// if so, we stop and don't create a new one (but still delete others if required)
|
|
337
|
+
for (const m of activeMemberships) {
|
|
338
|
+
if (m.membershipTypeId === cheapestMembership.membership.id && !m.generated) {
|
|
339
|
+
// is this cheaper?
|
|
340
|
+
if (m.price <= membership.price) { // todo expand with more details price comparison
|
|
341
|
+
// Cheaper
|
|
342
|
+
didFind = m;
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
294
349
|
// Then update all memberships from the same organization for the selected registration date range
|
|
295
350
|
for (const m of activeMemberships) {
|
|
296
351
|
if (m.membershipTypeId === cheapestMembership.membership.id && m.organizationId === cheapestMembership.registration.organizationId) {
|
|
297
|
-
if (!m.locked) {
|
|
352
|
+
if (!m.locked && m.generated) {
|
|
298
353
|
// Update the price and dates of this active membership (could have changed)
|
|
299
354
|
try {
|
|
300
355
|
await m.calculatePrice(me, cheapestMembership.registration);
|
|
@@ -323,7 +378,7 @@ export class PlatformMembershipService {
|
|
|
323
378
|
}
|
|
324
379
|
await m.doDelete();
|
|
325
380
|
}
|
|
326
|
-
else {
|
|
381
|
+
else if (m.membershipTypeId === cheapestMembership.membership.id) {
|
|
327
382
|
// Update price
|
|
328
383
|
if (!m.locked) {
|
|
329
384
|
try {
|
|
@@ -346,31 +401,11 @@ export class PlatformMembershipService {
|
|
|
346
401
|
continue;
|
|
347
402
|
}
|
|
348
403
|
|
|
349
|
-
// Otherwise make sure we create a new membership
|
|
350
|
-
|
|
351
|
-
const periodConfig = cheapestMembership.membership.periods.get(period.id);
|
|
352
|
-
if (!periodConfig) {
|
|
353
|
-
console.error('Missing membership prices for membership type ' + cheapestMembership.membership.id + ' and period ' + period.id);
|
|
354
|
-
continue;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
404
|
// Can we revive an earlier deleted membership?
|
|
358
405
|
if (!silent) {
|
|
359
406
|
console.log('Creating automatic membership for: ' + me.id + ' - membership type ' + cheapestMembership.membership.id);
|
|
360
407
|
}
|
|
361
408
|
|
|
362
|
-
const membership = new MemberPlatformMembership();
|
|
363
|
-
membership.memberId = me.id;
|
|
364
|
-
membership.membershipTypeId = cheapestMembership.membership.id;
|
|
365
|
-
membership.organizationId = cheapestMembership.registration.organizationId;
|
|
366
|
-
membership.periodId = period.id;
|
|
367
|
-
|
|
368
|
-
// Note: the dates will get modified in the price calculation
|
|
369
|
-
membership.startDate = periodConfig.startDate;
|
|
370
|
-
membership.endDate = periodConfig.endDate;
|
|
371
|
-
membership.expireDate = periodConfig.expireDate;
|
|
372
|
-
membership.generated = true;
|
|
373
|
-
|
|
374
409
|
if (me.details.memberNumber === null) {
|
|
375
410
|
try {
|
|
376
411
|
await MemberNumberService.assignMemberNumber(me, membership);
|
|
@@ -382,7 +417,6 @@ export class PlatformMembershipService {
|
|
|
382
417
|
}
|
|
383
418
|
}
|
|
384
419
|
|
|
385
|
-
await membership.calculatePrice(me, cheapestMembership.registration);
|
|
386
420
|
await membership.save();
|
|
387
421
|
}
|
|
388
422
|
});
|