@stamhoofd/backend 2.105.0 → 2.106.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.
Files changed (28) hide show
  1. package/package.json +10 -10
  2. package/src/crons.ts +39 -5
  3. package/src/endpoints/global/members/GetMembersEndpoint.test.ts +953 -47
  4. package/src/endpoints/global/members/GetMembersEndpoint.ts +1 -1
  5. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +142 -0
  6. package/src/endpoints/global/registration/GetRegistrationsEndpoint.ts +1 -1
  7. package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +163 -8
  8. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +2 -0
  9. package/src/endpoints/organization/dashboard/billing/GetPackagesEndpoint.test.ts +108 -0
  10. package/src/endpoints/organization/dashboard/billing/GetPackagesEndpoint.ts +40 -0
  11. package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +8 -1
  12. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +8 -1
  13. package/src/helpers/AdminPermissionChecker.ts +30 -6
  14. package/src/helpers/AuthenticatedStructures.ts +2 -2
  15. package/src/helpers/MemberUserSyncer.test.ts +400 -1
  16. package/src/helpers/MemberUserSyncer.ts +15 -10
  17. package/src/helpers/ServiceFeeHelper.ts +63 -0
  18. package/src/helpers/StripeHelper.ts +7 -4
  19. package/src/helpers/StripePayoutChecker.ts +1 -1
  20. package/src/seeds/0000000001-development-user.ts +2 -2
  21. package/src/seeds/0000000004-single-organization.ts +60 -0
  22. package/src/seeds/1754560914-groups-prices.test.ts +3023 -0
  23. package/src/seeds/1754560914-groups-prices.ts +408 -0
  24. package/src/seeds/{1722344162-sync-member-users.ts → 1761665607-sync-member-users.ts} +1 -1
  25. package/src/sql-filters/members.ts +1 -1
  26. package/tests/init/initAdmin.ts +19 -5
  27. package/tests/init/initPermissionRole.ts +14 -4
  28. package/tests/init/initPlatformRecordCategory.ts +8 -0
@@ -0,0 +1,3023 @@
1
+ import { Group, GroupFactory, Organization, OrganizationFactory, OrganizationRegistrationPeriod, OrganizationRegistrationPeriodFactory, RegistrationPeriod, RegistrationPeriodFactory } from '@stamhoofd/models';
2
+ import { GroupCategory, GroupCategorySettings, GroupPriceDiscountType, GroupStatus, OldGroupPrice, OldGroupPrices, TranslatedString } from '@stamhoofd/structures';
3
+ import { migratePrices } from './1754560914-groups-prices';
4
+
5
+ describe('migration.migratePrices', () => {
6
+ /**
7
+ * Test case 1 description:
8
+ * An organization with 3 groups. Group 1 and 2 are in the same category. Only group 1 has prices set with a discount if family members are in the same category.
9
+ * The tests checks if the prices and bundle discounts for each group are migrated correctly. See each test for a more detailed description.
10
+ */
11
+ describe('Case 1 - family members in category', () => {
12
+ let period: RegistrationPeriod;
13
+ let organization: Organization;
14
+ let organizationPeriod: OrganizationRegistrationPeriod;
15
+ let group1: Group;
16
+ let group2: Group;
17
+ let group3: Group;
18
+
19
+ beforeAll(async () => {
20
+ const startDate = new Date(2025, 0, 1);
21
+ const endDate = new Date(2025, 11, 31);
22
+ period = await new RegistrationPeriodFactory({ startDate, endDate }).create();
23
+ organization = await new OrganizationFactory({ period }).create();
24
+ period.organizationId = organization.id;
25
+ await period.save();
26
+ organizationPeriod = await new OrganizationRegistrationPeriodFactory({ organization, period }).create();
27
+
28
+ group1 = await new GroupFactory({ organization, period }).create();
29
+ group1.settings.prices = [];
30
+ group1.settings.name = new TranslatedString('group1');
31
+
32
+ // initial price with discount if family members in same category
33
+ const oldPrices1 = OldGroupPrices.create({
34
+ startDate: null,
35
+ sameMemberOnlyDiscount: false,
36
+ onlySameGroup: false,
37
+ prices: [
38
+ OldGroupPrice.create({
39
+ price: 30,
40
+ reducedPrice: 20,
41
+ }),
42
+ OldGroupPrice.create({
43
+ price: 25,
44
+ reducedPrice: 15,
45
+ }),
46
+ OldGroupPrice.create({
47
+ price: 20,
48
+ reducedPrice: 10,
49
+ }),
50
+ ],
51
+ });
52
+
53
+ // price with discount after startDate 2025-03-01 if family members in same category
54
+ const oldPrices2 = OldGroupPrices.create({
55
+ startDate: new Date(2025, 2, 1),
56
+ sameMemberOnlyDiscount: false,
57
+ onlySameGroup: false,
58
+ prices: [
59
+ OldGroupPrice.create({
60
+ price: 300,
61
+ reducedPrice: 200,
62
+ }),
63
+ OldGroupPrice.create({
64
+ price: 200,
65
+ reducedPrice: 100,
66
+ }),
67
+ ],
68
+ });
69
+
70
+ // add both prices with discounts to group 1
71
+ group1.settings.oldPrices = [oldPrices2, oldPrices1];
72
+
73
+ await group1.save();
74
+
75
+ group2 = await new GroupFactory({ organization, period }).create();
76
+ group2.settings.prices = [];
77
+ // do not set old prices for group 2 (should be set to 0 automatically in migration)
78
+ group2.settings.oldPrices = [];
79
+ group2.settings.name = new TranslatedString('group2');
80
+ await group2.save();
81
+
82
+ group3 = await new GroupFactory({ organization, period }).create();
83
+ group3.settings.prices = [];
84
+ // do not set old prices for group 3 (should be set to 0 automatically in migration)
85
+ group3.settings.oldPrices = [];
86
+ group3.settings.name = new TranslatedString('group3');
87
+ await group3.save();
88
+
89
+ // add group 1 and to 2 to same category, add group 3 to different category
90
+ organizationPeriod.settings.categories = [
91
+ GroupCategory.create({
92
+ settings: GroupCategorySettings.create({ name: 'category1' }),
93
+ groupIds: [group1.id, group2.id],
94
+ }),
95
+ GroupCategory.create({
96
+ settings: GroupCategorySettings.create({ name: 'category2' }),
97
+ groupIds: [group3.id],
98
+ }),
99
+ ];
100
+
101
+ await organizationPeriod.save();
102
+
103
+ await migratePrices();
104
+ });
105
+
106
+ afterAll(async () => {
107
+ await group1.delete();
108
+ await group2.delete();
109
+ await group3.delete();
110
+
111
+ await organizationPeriod.delete();
112
+ period.organizationId = null;
113
+ await period.save();
114
+
115
+ await organization.delete();
116
+ await period.delete();
117
+ });
118
+
119
+ test('organization period', async () => {
120
+ // check organization registration period
121
+ const orgPeriod = await OrganizationRegistrationPeriod.getByID(organizationPeriod.id);
122
+
123
+ // the organization period should have 1 bundle discount for family members in category 1
124
+ expect(orgPeriod!.settings.bundleDiscounts).toHaveLength(1);
125
+
126
+ expect(orgPeriod!.settings.bundleDiscounts).toEqual(
127
+ expect.arrayContaining([
128
+ expect.objectContaining({
129
+ // the discount should be for family members in category 1
130
+ countWholeFamily: true,
131
+ countPerGroup: false,
132
+ // should contain the differences for oldPrices1
133
+ discounts: expect.arrayContaining([
134
+ expect.objectContaining({
135
+ type: GroupPriceDiscountType.Fixed,
136
+ value: expect.objectContaining({
137
+ price: 5,
138
+ reducedPrice: 5,
139
+ }),
140
+ }),
141
+ expect.objectContaining({
142
+ type: GroupPriceDiscountType.Fixed,
143
+ value: expect.objectContaining({
144
+ price: 10,
145
+ reducedPrice: 10,
146
+ }),
147
+ }),
148
+ ]) }),
149
+ ]),
150
+ );
151
+ });
152
+
153
+ test('group 1', async () => {
154
+ const g1 = await Group.getByID(group1.id);
155
+
156
+ // check prices (should be equal to old prices)
157
+ expect(g1!.settings.prices).toHaveLength(2);
158
+ expect(g1!.settings.prices[0].price.price).toBe(30);
159
+ expect(g1!.settings.prices[0].price.reducedPrice).toBe(20);
160
+ expect(g1!.settings.prices[1].price.price).toBe(300);
161
+ expect(g1!.settings.prices[1].price.reducedPrice).toBe(200);
162
+
163
+ // check bundle discounts (each price should have 1 bundle discount for family members in same category)
164
+ expect(g1!.settings.prices[0].bundleDiscounts.size).toBe(1);
165
+ expect(g1!.settings.prices[1].bundleDiscounts.size).toBe(1);
166
+
167
+ // custom discount for bundle discount of the first price should be null because the discounts are not different than the discounts for the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices1)
168
+ expect([...g1!.settings.prices[0].bundleDiscounts.values()]).toEqual(
169
+ expect.arrayContaining([
170
+ expect.objectContaining({ customDiscounts: null }),
171
+ ]),
172
+ );
173
+
174
+ // custom discount for bundle discount of second price should not be null because the discounts are different than the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices1), it should contain the discount for oldPrices2
175
+ expect([...g1!.settings.prices[1].bundleDiscounts.values()]).toEqual(
176
+ expect.arrayContaining([
177
+ expect.objectContaining({ customDiscounts: expect.arrayContaining([
178
+ expect.objectContaining({
179
+ type: GroupPriceDiscountType.Fixed,
180
+ value: expect.objectContaining({
181
+ price: 100,
182
+ reducedPrice: 100,
183
+ }),
184
+ }),
185
+ ]) }),
186
+ ]),
187
+ );
188
+ });
189
+
190
+ test('group 2', async () => {
191
+ // group 2
192
+ const g2 = await Group.getByID(group2.id);
193
+
194
+ // check prices (the price should be 0 because there were no old prices configured)
195
+ expect(g2!.settings.prices).toHaveLength(1);
196
+ expect(g2!.settings.prices[0].price.price).toBe(0);
197
+ expect(g2!.settings.prices[0].price.reducedPrice).toBeNull();
198
+
199
+ // check bundle discounts (each price should have 1 bundle discount for family members in same category)
200
+ expect(g2!.settings.prices[0].bundleDiscounts.size).toBe(1);
201
+
202
+ // The custom discounts should be 0 because there were no prices for the group. It should only be linked to the bundle discount because the discount for group 1 should be applied if a member inscribes for group 2.
203
+ expect([...g2!.settings.prices[0].bundleDiscounts.values()]).toEqual(
204
+ expect.arrayContaining([
205
+ expect.objectContaining({ customDiscounts: expect.arrayContaining([
206
+ expect.objectContaining({
207
+ type: GroupPriceDiscountType.Fixed,
208
+ value: expect.objectContaining({
209
+ price: 0,
210
+ reducedPrice: null,
211
+ }),
212
+ }),
213
+ ]) }),
214
+ ]),
215
+ );
216
+ });
217
+
218
+ test('group 3', async () => {
219
+ // group 3
220
+ const g3 = await Group.getByID(group3.id);
221
+
222
+ // check prices (the price should be 0 because there were no old prices configured)
223
+ expect(g3!.settings.oldPrices).toHaveLength(0);
224
+ expect(g3!.settings.prices).toHaveLength(1);
225
+ expect(g3!.settings.prices[0].price.price).toBe(0);
226
+ expect(g3!.settings.prices[0].price.reducedPrice).toBeNull();
227
+
228
+ // check bundle discounts (should have no bundle discount because the group is in another category)
229
+ expect(g3!.settings.prices[0].bundleDiscounts.size).toBe(0);
230
+ });
231
+ });
232
+
233
+ /**
234
+ * Test case 2 description:
235
+ * An organization with 3 groups. Group 1 and 2 are in the same category. Only group 1 has prices set with a discount if same members are in the same category.
236
+ * The tests checks if the prices and bundle discounts for each group are migrated correctly. See each test for a more detailed description.
237
+ */
238
+ describe('Case 2 - same members in category', () => {
239
+ let period: RegistrationPeriod;
240
+ let organization: Organization;
241
+ let organizationPeriod: OrganizationRegistrationPeriod;
242
+ let group1: Group;
243
+ let group2: Group;
244
+ let group3: Group;
245
+
246
+ beforeAll(async () => {
247
+ const startDate = new Date(2025, 0, 1);
248
+ const endDate = new Date(2025, 11, 31);
249
+ period = await new RegistrationPeriodFactory({ startDate, endDate }).create();
250
+ organization = await new OrganizationFactory({ period }).create();
251
+ period.organizationId = organization.id;
252
+ await period.save();
253
+ organizationPeriod = await new OrganizationRegistrationPeriodFactory({ organization, period }).create();
254
+
255
+ group1 = await new GroupFactory({ organization, period }).create();
256
+ group1.settings.prices = [];
257
+ group1.settings.name = new TranslatedString('group1');
258
+
259
+ // initial price with discount if same members in same category
260
+ const oldPrices1 = OldGroupPrices.create({
261
+ startDate: null,
262
+ sameMemberOnlyDiscount: true,
263
+ onlySameGroup: false,
264
+ prices: [
265
+ OldGroupPrice.create({
266
+ price: 30,
267
+ reducedPrice: 20,
268
+ }),
269
+ OldGroupPrice.create({
270
+ price: 25,
271
+ reducedPrice: 15,
272
+ }),
273
+ OldGroupPrice.create({
274
+ price: 20,
275
+ reducedPrice: 10,
276
+ }),
277
+ ],
278
+ });
279
+
280
+ // price with discount after startDate 2025-03-01 if same members in same category
281
+ const oldPrices2 = OldGroupPrices.create({
282
+ startDate: new Date(2025, 2, 1),
283
+ sameMemberOnlyDiscount: true,
284
+ onlySameGroup: false,
285
+ prices: [
286
+ OldGroupPrice.create({
287
+ price: 300,
288
+ reducedPrice: 200,
289
+ }),
290
+ OldGroupPrice.create({
291
+ price: 200,
292
+ reducedPrice: 100,
293
+ }),
294
+ ],
295
+ });
296
+
297
+ group1.settings.oldPrices = [oldPrices2, oldPrices1];
298
+
299
+ await group1.save();
300
+
301
+ group2 = await new GroupFactory({ organization, period }).create();
302
+ group2.settings.prices = [];
303
+ group2.settings.oldPrices = [];
304
+ group2.settings.name = new TranslatedString('group2');
305
+ await group2.save();
306
+
307
+ group3 = await new GroupFactory({ organization, period }).create();
308
+ group3.settings.prices = [];
309
+ group3.settings.oldPrices = [];
310
+ group3.settings.name = new TranslatedString('group3');
311
+ await group3.save();
312
+
313
+ organizationPeriod.settings.categories = [
314
+ GroupCategory.create({
315
+ settings: GroupCategorySettings.create({ name: 'category1' }),
316
+ groupIds: [group1.id, group2.id],
317
+ }),
318
+ GroupCategory.create({
319
+ settings: GroupCategorySettings.create({ name: 'category2' }),
320
+ groupIds: [group3.id],
321
+ }),
322
+ ];
323
+
324
+ await organizationPeriod.save();
325
+
326
+ await migratePrices();
327
+ });
328
+
329
+ afterAll(async () => {
330
+ await group1.delete();
331
+ await group2.delete();
332
+ await group3.delete();
333
+
334
+ await organizationPeriod.delete();
335
+ period.organizationId = null;
336
+ await period.save();
337
+
338
+ await organization.delete();
339
+ await period.delete();
340
+ });
341
+
342
+ test('organization period', async () => {
343
+ // check organization registration period
344
+ const orgPeriod = await OrganizationRegistrationPeriod.getByID(organizationPeriod.id);
345
+
346
+ // the organization period should have 1 bundle discount for same members in category 1
347
+ expect(orgPeriod!.settings.bundleDiscounts).toHaveLength(1);
348
+
349
+ expect(orgPeriod!.settings.bundleDiscounts).toEqual(
350
+ expect.arrayContaining([
351
+ expect.objectContaining({
352
+ // the discount should be for same members in category 1
353
+ countWholeFamily: false,
354
+ countPerGroup: false,
355
+ discounts: expect.arrayContaining([
356
+ // should contain the differences for oldPrices1
357
+ expect.objectContaining({
358
+ type: GroupPriceDiscountType.Fixed,
359
+ value: expect.objectContaining({
360
+ price: 5,
361
+ reducedPrice: 5,
362
+ }),
363
+ }),
364
+ expect.objectContaining({
365
+ type: GroupPriceDiscountType.Fixed,
366
+ value: expect.objectContaining({
367
+ price: 10,
368
+ reducedPrice: 10,
369
+ }),
370
+ }),
371
+ ]) }),
372
+ ]),
373
+ );
374
+ });
375
+
376
+ test('group 1', async () => {
377
+ const g1 = await Group.getByID(group1.id);
378
+
379
+ // check prices (should be equal to old prices)
380
+ expect(g1!.settings.prices).toHaveLength(2);
381
+ expect(g1!.settings.prices[0].price.price).toBe(30);
382
+ expect(g1!.settings.prices[0].price.reducedPrice).toBe(20);
383
+ expect(g1!.settings.prices[1].price.price).toBe(300);
384
+ expect(g1!.settings.prices[1].price.reducedPrice).toBe(200);
385
+
386
+ // check bundle discounts (each price should have 1 bundle discount for same members in same category)
387
+ expect(g1!.settings.prices[0].bundleDiscounts.size).toBe(1);
388
+ expect(g1!.settings.prices[1].bundleDiscounts.size).toBe(1);
389
+
390
+ // custom discount for bundle discount of the first price should be null because the discounts are not different than the discounts for the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices1)
391
+ expect([...g1!.settings.prices[0].bundleDiscounts.values()]).toEqual(
392
+ expect.arrayContaining([
393
+ expect.objectContaining({ customDiscounts: null }),
394
+ ]),
395
+ );
396
+
397
+ // custom discount for bundle discount of second price should not be null because the discounts are different than the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices1), it should contain the discount for oldPrices2
398
+ expect([...g1!.settings.prices[1].bundleDiscounts.values()]).toEqual(
399
+ expect.arrayContaining([
400
+ expect.objectContaining({ customDiscounts: expect.arrayContaining([
401
+ expect.objectContaining({
402
+ type: GroupPriceDiscountType.Fixed,
403
+ value: expect.objectContaining({
404
+ price: 100,
405
+ reducedPrice: 100,
406
+ }),
407
+ }),
408
+ ]) }),
409
+ ]),
410
+ );
411
+ });
412
+
413
+ test('group 2', async () => {
414
+ // group 2
415
+ const g2 = await Group.getByID(group2.id);
416
+
417
+ // check prices (the price should be 0 because there were no old prices configured)
418
+ expect(g2!.settings.prices).toHaveLength(1);
419
+ expect(g2!.settings.prices[0].price.price).toBe(0);
420
+ expect(g2!.settings.prices[0].price.reducedPrice).toBeNull();
421
+
422
+ // check bundle discounts (each price should have 1 bundle discount for same members in same category)
423
+ expect(g2!.settings.prices[0].bundleDiscounts.size).toBe(1);
424
+
425
+ // The custom discounts should be 0 because there were no prices for the group. It should only be linked to the bundle discount because the discount for group 1 should be applied if a member inscribes for group 2.
426
+ expect([...g2!.settings.prices[0].bundleDiscounts.values()]).toEqual(
427
+ expect.arrayContaining([
428
+ expect.objectContaining({ customDiscounts: expect.arrayContaining([
429
+ expect.objectContaining({
430
+ type: GroupPriceDiscountType.Fixed,
431
+ value: expect.objectContaining({
432
+ price: 0,
433
+ reducedPrice: null,
434
+ }),
435
+ }),
436
+ ]) }),
437
+ ]),
438
+ );
439
+ });
440
+
441
+ test('group 3', async () => {
442
+ // group 3
443
+ const g3 = await Group.getByID(group3.id);
444
+
445
+ // check prices (the price should be 0 because there were no old prices configured)
446
+ expect(g3!.settings.oldPrices).toHaveLength(0);
447
+ expect(g3!.settings.prices).toHaveLength(1);
448
+ expect(g3!.settings.prices[0].price.price).toBe(0);
449
+ expect(g3!.settings.prices[0].price.reducedPrice).toBeNull();
450
+
451
+ // check bundle discounts (should have no bundle discount because the group is in another category)
452
+ expect(g3!.settings.prices[0].bundleDiscounts.size).toBe(0);
453
+ });
454
+ });
455
+
456
+ /**
457
+ * Test case 3 description:
458
+ * An organization with 3 groups. Group 1 and 2 are in the same category. Only group 1 has prices set with a discount if same members are in the same category and a discount if family members are in the same category (after 2025-03-01).
459
+ * The tests checks if the prices and bundle discounts for each group are migrated correctly. See each test for a more detailed description.
460
+ */
461
+ describe('Case 3 - combination of family members in category and same members in category', () => {
462
+ let period: RegistrationPeriod;
463
+ let organization: Organization;
464
+ let organizationPeriod: OrganizationRegistrationPeriod;
465
+ let group1: Group;
466
+ let group2: Group;
467
+ let group3: Group;
468
+
469
+ beforeAll(async () => {
470
+ const startDate = new Date(2025, 0, 1);
471
+ const endDate = new Date(2025, 11, 31);
472
+ period = await new RegistrationPeriodFactory({ startDate, endDate }).create();
473
+ organization = await new OrganizationFactory({ period }).create();
474
+ period.organizationId = organization.id;
475
+ await period.save();
476
+ organizationPeriod = await new OrganizationRegistrationPeriodFactory({ organization, period }).create();
477
+
478
+ group1 = await new GroupFactory({ organization, period }).create();
479
+ group1.settings.prices = [];
480
+ group1.settings.name = new TranslatedString('group1');
481
+
482
+ // initial price with discount if same members in same category
483
+ const oldPrices1 = OldGroupPrices.create({
484
+ startDate: null,
485
+ sameMemberOnlyDiscount: true,
486
+ onlySameGroup: false,
487
+ prices: [
488
+ OldGroupPrice.create({
489
+ price: 30,
490
+ reducedPrice: 20,
491
+ }),
492
+ OldGroupPrice.create({
493
+ price: 25,
494
+ reducedPrice: 15,
495
+ }),
496
+ OldGroupPrice.create({
497
+ price: 20,
498
+ reducedPrice: 10,
499
+ }),
500
+ ],
501
+ });
502
+
503
+ // price with discount after startDate 2025-03-01 if family members in same category
504
+ const oldPrices2 = OldGroupPrices.create({
505
+ startDate: new Date(2025, 2, 1),
506
+ sameMemberOnlyDiscount: false,
507
+ onlySameGroup: false,
508
+ prices: [
509
+ OldGroupPrice.create({
510
+ price: 300,
511
+ reducedPrice: 200,
512
+ }),
513
+ OldGroupPrice.create({
514
+ price: 200,
515
+ reducedPrice: 100,
516
+ }),
517
+ ],
518
+ });
519
+
520
+ group1.settings.oldPrices = [oldPrices2, oldPrices1];
521
+
522
+ await group1.save();
523
+
524
+ group2 = await new GroupFactory({ organization, period }).create();
525
+ group2.settings.prices = [];
526
+ group2.settings.oldPrices = [];
527
+ group2.settings.name = new TranslatedString('group2');
528
+ await group2.save();
529
+
530
+ group3 = await new GroupFactory({ organization, period }).create();
531
+ group3.settings.prices = [];
532
+ group3.settings.oldPrices = [];
533
+ group3.settings.name = new TranslatedString('group3');
534
+ await group3.save();
535
+
536
+ organizationPeriod.settings.categories = [
537
+ GroupCategory.create({
538
+ settings: GroupCategorySettings.create({ name: 'category1' }),
539
+ groupIds: [group1.id, group2.id],
540
+ }),
541
+ GroupCategory.create({
542
+ settings: GroupCategorySettings.create({ name: 'category2' }),
543
+ groupIds: [group3.id],
544
+ }),
545
+ ];
546
+
547
+ await organizationPeriod.save();
548
+
549
+ await migratePrices();
550
+ });
551
+
552
+ afterAll(async () => {
553
+ await group1.delete();
554
+ await group2.delete();
555
+ await group3.delete();
556
+
557
+ await organizationPeriod.delete();
558
+ period.organizationId = null;
559
+ await period.save();
560
+
561
+ await organization.delete();
562
+ await period.delete();
563
+ });
564
+
565
+ test('organization period', async () => {
566
+ // check organization registration period
567
+ const orgPeriod = await OrganizationRegistrationPeriod.getByID(organizationPeriod.id);
568
+
569
+ // the organization period should have 2 bundle discounts: 1 for family members in category 1 and 1 for same members in category 1
570
+ expect(orgPeriod!.settings.bundleDiscounts).toHaveLength(2);
571
+
572
+ expect(orgPeriod!.settings.bundleDiscounts).toEqual(
573
+ expect.arrayContaining([
574
+ expect.objectContaining({
575
+ // the discount should be for same members in category 1
576
+ countWholeFamily: false,
577
+ countPerGroup: false,
578
+ // should contain the differences for oldPrices1
579
+ discounts: expect.arrayContaining([
580
+ expect.objectContaining({
581
+ type: GroupPriceDiscountType.Fixed,
582
+ value: expect.objectContaining({
583
+ price: 5,
584
+ reducedPrice: 5,
585
+ }),
586
+ }),
587
+ expect.objectContaining({
588
+ type: GroupPriceDiscountType.Fixed,
589
+ value: expect.objectContaining({
590
+ price: 10,
591
+ reducedPrice: 10,
592
+ }),
593
+ }),
594
+ ]) }),
595
+ expect.objectContaining({
596
+ // the discount should be for family members in category 1
597
+ countWholeFamily: true,
598
+ countPerGroup: false,
599
+ discounts: expect.arrayContaining([
600
+ // should contain the differences for oldPrices2
601
+ expect.objectContaining({
602
+ type: GroupPriceDiscountType.Fixed,
603
+ value: expect.objectContaining({
604
+ price: 100,
605
+ reducedPrice: 100,
606
+ }),
607
+ }),
608
+ ]) }),
609
+ ]),
610
+ );
611
+ });
612
+
613
+ test('group 1', async () => {
614
+ const g1 = await Group.getByID(group1.id);
615
+
616
+ // check prices (should be equal to old prices)
617
+ expect(g1!.settings.prices).toHaveLength(2);
618
+ expect(g1!.settings.prices[0].price.price).toBe(30);
619
+ expect(g1!.settings.prices[0].price.reducedPrice).toBe(20);
620
+ expect(g1!.settings.prices[1].price.price).toBe(300);
621
+ expect(g1!.settings.prices[1].price.reducedPrice).toBe(200);
622
+
623
+ // check bundle discounts
624
+ expect(g1!.settings.prices[0].bundleDiscounts.size).toBe(2);
625
+ expect([...g1!.settings.prices[0].bundleDiscounts.values()]).toEqual(
626
+ expect.arrayContaining([
627
+ // is default discount for category
628
+ expect.objectContaining({ customDiscounts: null }),
629
+ // is connected to family discount, but zero discount
630
+ expect.objectContaining({ customDiscounts: expect.arrayContaining([
631
+ expect.objectContaining({
632
+ type: GroupPriceDiscountType.Fixed,
633
+ value: expect.objectContaining({
634
+ price: 0,
635
+ reducedPrice: null,
636
+ }),
637
+ }),
638
+ ]) }),
639
+ ]),
640
+ );
641
+ expect(g1!.settings.prices[1].bundleDiscounts.size).toBe(2);
642
+ expect([...g1!.settings.prices[1].bundleDiscounts.values()]).toEqual(
643
+ expect.arrayContaining([
644
+ // is default discount for category
645
+ expect.objectContaining({ customDiscounts: null }),
646
+ // is connected to member only discount, but zero discount
647
+ expect.objectContaining({ customDiscounts: expect.arrayContaining([
648
+ expect.objectContaining({
649
+ type: GroupPriceDiscountType.Fixed,
650
+ value: expect.objectContaining({
651
+ price: 0,
652
+ reducedPrice: null,
653
+ }),
654
+ }),
655
+ ]) }),
656
+ ]),
657
+ );
658
+ });
659
+
660
+ test('group 2', async () => {
661
+ // group 2
662
+ const g2 = await Group.getByID(group2.id);
663
+
664
+ // check prices (the price should be 0 because there were no old prices configured)
665
+ expect(g2!.settings.prices).toHaveLength(1);
666
+ expect(g2!.settings.prices[0].price.price).toBe(0);
667
+ expect(g2!.settings.prices[0].price.reducedPrice).toBeNull();
668
+
669
+ // check bundle discounts (each price should have 2 bundle discounts for same members in same category and for family members in same category)
670
+ expect(g2!.settings.prices[0].bundleDiscounts.size).toBe(2);
671
+
672
+ // The custom discounts should be 0 because there were no prices for the group. It should only be linked to the 2 bundle discounts because the discount for group 1 should be applied if a member inscribes for group 2.
673
+ expect([...g2!.settings.prices[0].bundleDiscounts.values()]).toEqual(
674
+ expect.arrayContaining([
675
+ expect.objectContaining({ customDiscounts: expect.arrayContaining([
676
+ expect.objectContaining({
677
+ type: GroupPriceDiscountType.Fixed,
678
+ value: expect.objectContaining({
679
+ price: 0,
680
+ reducedPrice: null,
681
+ }),
682
+ }),
683
+ ]) }),
684
+ expect.objectContaining({ customDiscounts: expect.arrayContaining([
685
+ expect.objectContaining({
686
+ type: GroupPriceDiscountType.Fixed,
687
+ value: expect.objectContaining({
688
+ price: 0,
689
+ reducedPrice: null,
690
+ }),
691
+ }),
692
+ ]) }),
693
+ ]),
694
+ );
695
+ });
696
+
697
+ test('group 3', async () => {
698
+ // group 3
699
+ const g3 = await Group.getByID(group3.id);
700
+
701
+ // check prices (the price should be 0 because there were no old prices configured)
702
+ expect(g3!.settings.oldPrices).toHaveLength(0);
703
+ expect(g3!.settings.prices).toHaveLength(1);
704
+ expect(g3!.settings.prices[0].price.price).toBe(0);
705
+ expect(g3!.settings.prices[0].price.reducedPrice).toBeNull();
706
+
707
+ // check bundle discounts (should have no bundle discount because the group is in another category)
708
+ expect(g3!.settings.prices[0].bundleDiscounts.size).toBe(0);
709
+ });
710
+ });
711
+
712
+ /**
713
+ * Test case 4 description:
714
+ * An organization with 4 groups. Group 1 and 2 are in the same category. Group 3 and 4 are also in the same category. Group 1, group 2 and group 3 have prices set (a combination of different discounts). Group 1 also has a discount only for the same group. Group 4 has no prices set.
715
+ * The tests checks if the prices and bundle discounts for each group are migrated correctly. See each test for a more detailed description.
716
+ */
717
+ describe('Case 4 - combination of different discounts and multiple group categories', () => {
718
+ let period: RegistrationPeriod;
719
+ let organization: Organization;
720
+ let organizationPeriod: OrganizationRegistrationPeriod;
721
+ let group1: Group;
722
+ let group2: Group;
723
+ let group3: Group;
724
+ let group4: Group;
725
+
726
+ beforeAll(async () => {
727
+ const startDate = new Date(2025, 0, 1);
728
+ const endDate = new Date(2025, 11, 31);
729
+ period = await new RegistrationPeriodFactory({ startDate, endDate }).create();
730
+ organization = await new OrganizationFactory({ period }).create();
731
+ period.organizationId = organization.id;
732
+ await period.save();
733
+ organizationPeriod = await new OrganizationRegistrationPeriodFactory({ organization, period }).create();
734
+
735
+ group1 = await new GroupFactory({ organization, period }).create();
736
+ group1.settings.prices = [];
737
+ group1.settings.name = new TranslatedString('group1');
738
+
739
+ // price with discount if same members in same category
740
+ const oldPrices1 = OldGroupPrices.create({
741
+ startDate: null,
742
+ sameMemberOnlyDiscount: true,
743
+ onlySameGroup: false,
744
+ prices: [
745
+ OldGroupPrice.create({
746
+ price: 30,
747
+ reducedPrice: 20,
748
+ }),
749
+ OldGroupPrice.create({
750
+ price: 25,
751
+ reducedPrice: 15,
752
+ }),
753
+ OldGroupPrice.create({
754
+ price: 20,
755
+ reducedPrice: 10,
756
+ }),
757
+ ],
758
+ });
759
+
760
+ // price with discount after startDate 2025-03-01 if family members in same category
761
+ const oldPrices2 = OldGroupPrices.create({
762
+ startDate: new Date(2025, 2, 1),
763
+ sameMemberOnlyDiscount: false,
764
+ onlySameGroup: false,
765
+ prices: [
766
+ OldGroupPrice.create({
767
+ price: 300,
768
+ reducedPrice: 200,
769
+ }),
770
+ OldGroupPrice.create({
771
+ price: 200,
772
+ reducedPrice: 100,
773
+ }),
774
+ ],
775
+ });
776
+
777
+ // price with discount after startDate 2025-07-05 if same members in same GROUP
778
+ const oldPrices3 = OldGroupPrices.create({
779
+ startDate: new Date(2025, 6, 5),
780
+ sameMemberOnlyDiscount: true,
781
+ onlySameGroup: true,
782
+ prices: [
783
+ OldGroupPrice.create({
784
+ price: 32,
785
+ reducedPrice: 21,
786
+ }),
787
+ OldGroupPrice.create({
788
+ price: 26,
789
+ reducedPrice: 16,
790
+ }),
791
+ OldGroupPrice.create({
792
+ price: 21,
793
+ reducedPrice: 11,
794
+ }),
795
+ ],
796
+ });
797
+
798
+ group1.settings.oldPrices = [oldPrices2, oldPrices1, oldPrices3];
799
+
800
+ await group1.save();
801
+
802
+ group2 = await new GroupFactory({ organization, period }).create();
803
+ group2.settings.prices = [];
804
+
805
+ // price with discount if family members in same category
806
+ const oldPrices_g2_1 = OldGroupPrices.create({
807
+ startDate: null,
808
+ sameMemberOnlyDiscount: false,
809
+ onlySameGroup: false,
810
+ prices: [
811
+ OldGroupPrice.create({
812
+ price: 32,
813
+ reducedPrice: 22,
814
+ }),
815
+ OldGroupPrice.create({
816
+ price: 27,
817
+ reducedPrice: 17,
818
+ }),
819
+ OldGroupPrice.create({
820
+ price: 22,
821
+ reducedPrice: 12,
822
+ }),
823
+ ],
824
+ });
825
+
826
+ // price with discount after startDate 2025-08-02 if family members in same category
827
+ const oldPrices_g2_2 = OldGroupPrices.create({
828
+ startDate: new Date(2025, 7, 2),
829
+ sameMemberOnlyDiscount: false,
830
+ onlySameGroup: false,
831
+ prices: [
832
+ OldGroupPrice.create({
833
+ price: 33,
834
+ reducedPrice: 23,
835
+ }),
836
+ OldGroupPrice.create({
837
+ price: 23,
838
+ reducedPrice: 13,
839
+ }),
840
+ ],
841
+ });
842
+ group2.settings.oldPrices = [oldPrices_g2_1, oldPrices_g2_2];
843
+ group2.settings.name = new TranslatedString('group2');
844
+ await group2.save();
845
+
846
+ group3 = await new GroupFactory({ organization, period }).create();
847
+ group3.settings.prices = [];
848
+
849
+ // price with discount if same members in same category
850
+ const oldPrices_g3_1 = OldGroupPrices.create({
851
+ startDate: null,
852
+ sameMemberOnlyDiscount: true,
853
+ onlySameGroup: false,
854
+ prices: [
855
+ OldGroupPrice.create({
856
+ price: 29,
857
+ reducedPrice: 19,
858
+ }),
859
+ OldGroupPrice.create({
860
+ price: 28,
861
+ reducedPrice: 18,
862
+ }),
863
+ ],
864
+ });
865
+ group3.settings.oldPrices = [oldPrices_g3_1];
866
+ group3.settings.name = new TranslatedString('group3');
867
+ await group3.save();
868
+
869
+ group4 = await new GroupFactory({ organization, period }).create();
870
+ group4.settings.prices = [];
871
+ group4.settings.oldPrices = [];
872
+ group4.settings.name = new TranslatedString('group4');
873
+ await group4.save();
874
+
875
+ organizationPeriod.settings.categories = [
876
+ GroupCategory.create({
877
+ settings: GroupCategorySettings.create({ name: 'category1' }),
878
+ groupIds: [group1.id, group2.id],
879
+ }),
880
+ GroupCategory.create({
881
+ settings: GroupCategorySettings.create({ name: 'category2' }),
882
+ groupIds: [group3.id, group4.id],
883
+ }),
884
+ ];
885
+
886
+ await organizationPeriod.save();
887
+
888
+ await migratePrices();
889
+ });
890
+
891
+ afterAll(async () => {
892
+ await group1.delete();
893
+ await group2.delete();
894
+ await group3.delete();
895
+ await group4.delete();
896
+
897
+ await organizationPeriod.delete();
898
+ period.organizationId = null;
899
+ await period.save();
900
+
901
+ await organization.delete();
902
+ await period.delete();
903
+ });
904
+
905
+ test('organization period', async () => {
906
+ // check organization registration period
907
+ const orgPeriod = await OrganizationRegistrationPeriod.getByID(organizationPeriod.id);
908
+ expect(orgPeriod!.settings.bundleDiscounts).toHaveLength(4);
909
+
910
+ // should have 4 different bundle discounts:
911
+ // - oldPrices1
912
+ // - oldPrices2
913
+ // - oldPrices3
914
+ // - oldPrices_g3_1
915
+ expect([...orgPeriod!.settings.bundleDiscounts.values()]).toEqual(
916
+ expect.arrayContaining([
917
+ expect.objectContaining({ countWholeFamily: false, countPerGroup: false,
918
+ // should contain discounts for oldPrices1
919
+ discounts: expect.arrayContaining([
920
+ expect.objectContaining({
921
+ type: GroupPriceDiscountType.Fixed,
922
+ value: expect.objectContaining({
923
+ price: 5,
924
+ reducedPrice: 5,
925
+ }),
926
+ }),
927
+ expect.objectContaining({
928
+ type: GroupPriceDiscountType.Fixed,
929
+ value: expect.objectContaining({
930
+ price: 10,
931
+ reducedPrice: 10,
932
+ }),
933
+ }),
934
+ ]),
935
+ }),
936
+ expect.objectContaining({ countWholeFamily: true, countPerGroup: false,
937
+ // should contain discounts for oldPrices2
938
+ discounts: expect.arrayContaining([
939
+ expect.objectContaining({
940
+ type: GroupPriceDiscountType.Fixed,
941
+ value: expect.objectContaining({
942
+ price: 100,
943
+ reducedPrice: 100,
944
+ }),
945
+ }),
946
+ ]),
947
+ }),
948
+ expect.objectContaining({ countWholeFamily: false, countPerGroup: true,
949
+ // should contain discounts for oldPrices3
950
+ discounts: expect.arrayContaining([
951
+ expect.objectContaining({
952
+ type: GroupPriceDiscountType.Fixed,
953
+ value: expect.objectContaining({
954
+ price: 6,
955
+ reducedPrice: 5,
956
+ }),
957
+ }),
958
+ expect.objectContaining({
959
+ type: GroupPriceDiscountType.Fixed,
960
+ value: expect.objectContaining({
961
+ price: 11,
962
+ reducedPrice: 10,
963
+ }),
964
+ }),
965
+ ]),
966
+ }),
967
+ expect.objectContaining({ countWholeFamily: false, countPerGroup: false,
968
+ // should contain discounts for oldPrices_g3_1
969
+ discounts: expect.arrayContaining([
970
+ expect.objectContaining({
971
+ type: GroupPriceDiscountType.Fixed,
972
+ value: expect.objectContaining({
973
+ price: 1,
974
+ reducedPrice: 1,
975
+ }),
976
+ }),
977
+ ]),
978
+ }),
979
+ // see test case 5 for a case where countWholeFamily is true and countPerGroup is true
980
+ ]),
981
+ );
982
+ });
983
+
984
+ test('group 1', async () => {
985
+ const g1 = await Group.getByID(group1.id);
986
+
987
+ // check prices (should be equal to old prices)
988
+ expect(g1!.settings.prices).toHaveLength(3);
989
+ expect(g1!.settings.prices[0].price.price).toBe(30);
990
+ expect(g1!.settings.prices[0].price.reducedPrice).toBe(20);
991
+ expect(g1!.settings.prices[1].price.price).toBe(300);
992
+ expect(g1!.settings.prices[1].price.reducedPrice).toBe(200);
993
+ expect(g1!.settings.prices[2].price.price).toBe(32);
994
+ expect(g1!.settings.prices[2].price.reducedPrice).toBe(21);
995
+
996
+ // // check bundle discounts
997
+ expect(g1!.settings.prices[0].bundleDiscounts.size).toBe(2);
998
+ expect([...g1!.settings.prices[0].bundleDiscounts.values()]).toEqual(
999
+ expect.arrayContaining([
1000
+ // is default discount for category
1001
+ expect.objectContaining({ customDiscounts: null }),
1002
+ // is connected to family discount, but zero discount
1003
+ expect.objectContaining({ customDiscounts: expect.arrayContaining([
1004
+ expect.objectContaining({
1005
+ type: GroupPriceDiscountType.Fixed,
1006
+ value: expect.objectContaining({
1007
+ price: 0,
1008
+ reducedPrice: null,
1009
+ }),
1010
+ }),
1011
+ ]) }),
1012
+ ]),
1013
+ );
1014
+ expect(g1!.settings.prices[1].bundleDiscounts.size).toBe(2);
1015
+ expect([...g1!.settings.prices[1].bundleDiscounts.values()]).toEqual(
1016
+ expect.arrayContaining([
1017
+ // is default discount for category
1018
+ expect.objectContaining({ customDiscounts: null }),
1019
+ // is connected to member only discount, but zero discount
1020
+ expect.objectContaining({ customDiscounts: expect.arrayContaining([
1021
+ expect.objectContaining({
1022
+ type: GroupPriceDiscountType.Fixed,
1023
+ value: expect.objectContaining({
1024
+ price: 0,
1025
+ reducedPrice: null,
1026
+ }),
1027
+ }),
1028
+ ]) }),
1029
+ ]),
1030
+ );
1031
+
1032
+ expect(g1!.settings.prices[2].bundleDiscounts.size).toBe(3);
1033
+ expect([...g1!.settings.prices[2].bundleDiscounts.values()]).toEqual(
1034
+ expect.arrayContaining([
1035
+ // is default discount for category
1036
+ expect.objectContaining({ customDiscounts: null }),
1037
+ // is connected to member only discount, but zero discount
1038
+ expect.objectContaining({ customDiscounts: expect.arrayContaining([
1039
+ expect.objectContaining({
1040
+ type: GroupPriceDiscountType.Fixed,
1041
+ value: expect.objectContaining({
1042
+ price: 0,
1043
+ reducedPrice: null,
1044
+ }),
1045
+ }),
1046
+ ]) }),
1047
+ // is default discount for member in group
1048
+ expect.objectContaining({ customDiscounts: null }),
1049
+ ]),
1050
+ );
1051
+ });
1052
+
1053
+ test('group 2', async () => {
1054
+ // group 2
1055
+ const g2 = await Group.getByID(group2.id);
1056
+
1057
+ // check prices (should be equal to old prices)
1058
+ expect(g2!.settings.prices).toHaveLength(2);
1059
+ expect(g2!.settings.prices[0].price.price).toBe(32);
1060
+ expect(g2!.settings.prices[0].price.reducedPrice).toBe(22);
1061
+ expect(g2!.settings.prices[1].price.price).toBe(33);
1062
+ expect(g2!.settings.prices[1].price.reducedPrice).toBe(23);
1063
+
1064
+ // check bundle discounts (each price should have 2 bundle discounts for same members in same category and for family members in same category)
1065
+ expect(g2!.settings.prices[0].bundleDiscounts.size).toBe(2);
1066
+
1067
+ // There should be 2 custom discounts because the old prices of group 2 are different than the prices of the bundle discounts in the settings of the period.
1068
+ expect([...g2!.settings.prices[0].bundleDiscounts.values()]).toEqual(
1069
+ expect.arrayContaining([
1070
+ // is connected to family discount, but different discount than group 1
1071
+ expect.objectContaining({ customDiscounts: expect.arrayContaining([
1072
+ expect.objectContaining({
1073
+ type: GroupPriceDiscountType.Fixed,
1074
+ value: expect.objectContaining({
1075
+ price: 5,
1076
+ reducedPrice: 5,
1077
+ }),
1078
+ }),
1079
+ ]) }),
1080
+ // is connected to family discount, but different discount than group 1
1081
+ expect.objectContaining({ customDiscounts: expect.arrayContaining([
1082
+ expect.objectContaining({
1083
+ type: GroupPriceDiscountType.Fixed,
1084
+ value: expect.objectContaining({
1085
+ price: 10,
1086
+ reducedPrice: 10,
1087
+ }),
1088
+ }),
1089
+ ]) }),
1090
+ ]),
1091
+ );
1092
+ });
1093
+
1094
+ test('group 3', async () => {
1095
+ // group 3
1096
+ const g3 = await Group.getByID(group3.id);
1097
+
1098
+ // check prices (should be equal to old prices)
1099
+ expect(g3!.settings.prices).toHaveLength(1);
1100
+ expect(g3!.settings.prices[0].price.price).toBe(29);
1101
+ expect(g3!.settings.prices[0].price.reducedPrice).toBe(19);
1102
+
1103
+ // check bundle discounts (should be only 1 bundle discount because the group is in another category and group 3 only has 1 discount configured in the old prices)
1104
+ expect(g3!.settings.prices[0].bundleDiscounts.size).toBe(1);
1105
+
1106
+ // There should be no custom discounts because the discounts are the same as default
1107
+ expect([...g3!.settings.prices[0].bundleDiscounts.values()]).toEqual(
1108
+ expect.arrayContaining([
1109
+ expect.objectContaining({ customDiscounts: null }),
1110
+ ]),
1111
+ );
1112
+ });
1113
+
1114
+ test('group 4', async () => {
1115
+ // group 4
1116
+ const g4 = await Group.getByID(group4.id);
1117
+
1118
+ // check prices (should be equal to old prices)
1119
+ expect(g4!.settings.prices).toHaveLength(1);
1120
+ expect(g4!.settings.prices[0].price.price).toBe(0);
1121
+ expect(g4!.settings.prices[0].price.reducedPrice).toBeNull();
1122
+
1123
+ // Should have 1 bundle discount because group 4 is in the same category as group 3 and group 3 has a discount for family members in the same category. The custom discounts should be 0 because there is no discount for this group, it is only linked.
1124
+ expect(g4!.settings.prices[0].bundleDiscounts.size).toBe(1);
1125
+ expect([...g4!.settings.prices[0].bundleDiscounts.values()]).toEqual(
1126
+ expect.arrayContaining([
1127
+ expect.objectContaining({ customDiscounts: expect.arrayContaining([
1128
+ expect.objectContaining({
1129
+ type: GroupPriceDiscountType.Fixed,
1130
+ value: expect.objectContaining({
1131
+ price: 0,
1132
+ reducedPrice: null,
1133
+ }),
1134
+ }),
1135
+ ]) }),
1136
+ ]),
1137
+ );
1138
+ });
1139
+ });
1140
+
1141
+ /**
1142
+ * Test case 5 description:
1143
+ * An organization with 3 groups. Group 1 and 2 are in the same category. Only group 1 has prices set with a discount if family members are in the same category and a discount if family members are in the same group.
1144
+ * The tests checks if the prices and bundle discounts for each group are migrated correctly. See each test for a more detailed description.
1145
+ */
1146
+ describe('Case 5 - family members in same group', () => {
1147
+ let period: RegistrationPeriod;
1148
+ let organization: Organization;
1149
+ let organizationPeriod: OrganizationRegistrationPeriod;
1150
+ let group1: Group;
1151
+ let group2: Group;
1152
+ let group3: Group;
1153
+
1154
+ beforeAll(async () => {
1155
+ const startDate = new Date(2025, 0, 1);
1156
+ const endDate = new Date(2025, 11, 31);
1157
+ period = await new RegistrationPeriodFactory({ startDate, endDate }).create();
1158
+ organization = await new OrganizationFactory({ period }).create();
1159
+ period.organizationId = organization.id;
1160
+ await period.save();
1161
+ organizationPeriod = await new OrganizationRegistrationPeriodFactory({ organization, period }).create();
1162
+
1163
+ group1 = await new GroupFactory({ organization, period }).create();
1164
+ group1.settings.prices = [];
1165
+ group1.settings.name = new TranslatedString('group1');
1166
+
1167
+ // initial price with discount if family members in same category
1168
+ const oldPrices1 = OldGroupPrices.create({
1169
+ startDate: null,
1170
+ sameMemberOnlyDiscount: false,
1171
+ onlySameGroup: true,
1172
+ prices: [
1173
+ OldGroupPrice.create({
1174
+ price: 30,
1175
+ reducedPrice: 20,
1176
+ }),
1177
+ OldGroupPrice.create({
1178
+ price: 25,
1179
+ reducedPrice: 15,
1180
+ }),
1181
+ OldGroupPrice.create({
1182
+ price: 20,
1183
+ reducedPrice: 10,
1184
+ }),
1185
+ ],
1186
+ });
1187
+
1188
+ // price with discount after startDate 2025-03-01 if family members in same category
1189
+ const oldPrices2 = OldGroupPrices.create({
1190
+ startDate: new Date(2025, 2, 1),
1191
+ sameMemberOnlyDiscount: false,
1192
+ onlySameGroup: false,
1193
+ prices: [
1194
+ OldGroupPrice.create({
1195
+ price: 300,
1196
+ reducedPrice: 200,
1197
+ }),
1198
+ OldGroupPrice.create({
1199
+ price: 200,
1200
+ reducedPrice: 100,
1201
+ }),
1202
+ ],
1203
+ });
1204
+
1205
+ // add both prices with discounts to group 1
1206
+ group1.settings.oldPrices = [oldPrices2, oldPrices1];
1207
+
1208
+ await group1.save();
1209
+
1210
+ group2 = await new GroupFactory({ organization, period }).create();
1211
+ group2.settings.prices = [];
1212
+ // do not set old prices for group 2 (should be set to 0 automatically in migration)
1213
+ group2.settings.oldPrices = [];
1214
+ group2.settings.name = new TranslatedString('group2');
1215
+ await group2.save();
1216
+
1217
+ group3 = await new GroupFactory({ organization, period }).create();
1218
+ group3.settings.prices = [];
1219
+ // do not set old prices for group 3 (should be set to 0 automatically in migration)
1220
+ group3.settings.oldPrices = [];
1221
+ group3.settings.name = new TranslatedString('group3');
1222
+ await group3.save();
1223
+
1224
+ // add group 1 and to 2 to same category, add group 3 to different category
1225
+ organizationPeriod.settings.categories = [
1226
+ GroupCategory.create({
1227
+ settings: GroupCategorySettings.create({ name: 'category1' }),
1228
+ groupIds: [group1.id, group2.id],
1229
+ }),
1230
+ GroupCategory.create({
1231
+ settings: GroupCategorySettings.create({ name: 'category2' }),
1232
+ groupIds: [group3.id],
1233
+ }),
1234
+ ];
1235
+
1236
+ await organizationPeriod.save();
1237
+
1238
+ await migratePrices();
1239
+ });
1240
+
1241
+ afterAll(async () => {
1242
+ await group1.delete();
1243
+ await group2.delete();
1244
+ await group3.delete();
1245
+
1246
+ await organizationPeriod.delete();
1247
+ period.organizationId = null;
1248
+ await period.save();
1249
+
1250
+ await organization.delete();
1251
+ await period.delete();
1252
+ });
1253
+
1254
+ test('organization period', async () => {
1255
+ // check organization registration period
1256
+ const orgPeriod = await OrganizationRegistrationPeriod.getByID(organizationPeriod.id);
1257
+
1258
+ // the organization period should have 1 bundle discount for family members in category 1
1259
+ expect(orgPeriod!.settings.bundleDiscounts).toHaveLength(2);
1260
+
1261
+ expect(orgPeriod!.settings.bundleDiscounts).toEqual(
1262
+ expect.arrayContaining([
1263
+ expect.objectContaining({
1264
+ // the discount should be for family members in group 1
1265
+ countWholeFamily: true,
1266
+ countPerGroup: true,
1267
+ // should contain the differences for oldPrices1
1268
+ discounts: expect.arrayContaining([
1269
+ expect.objectContaining({
1270
+ type: GroupPriceDiscountType.Fixed,
1271
+ value: expect.objectContaining({
1272
+ price: 5,
1273
+ reducedPrice: 5,
1274
+ }),
1275
+ }),
1276
+ expect.objectContaining({
1277
+ type: GroupPriceDiscountType.Fixed,
1278
+ value: expect.objectContaining({
1279
+ price: 10,
1280
+ reducedPrice: 10,
1281
+ }),
1282
+ }),
1283
+ ]) }),
1284
+ expect.objectContaining({
1285
+ // the discount should be for family members in category 1
1286
+ countWholeFamily: true,
1287
+ countPerGroup: false,
1288
+ // should contain the differences for oldPrices1
1289
+ discounts: expect.arrayContaining([
1290
+ expect.objectContaining({
1291
+ type: GroupPriceDiscountType.Fixed,
1292
+ value: expect.objectContaining({
1293
+ price: 100,
1294
+ reducedPrice: 100,
1295
+ }),
1296
+ }),
1297
+ ]) }),
1298
+ ]),
1299
+ );
1300
+ });
1301
+
1302
+ test('group 1', async () => {
1303
+ const g1 = await Group.getByID(group1.id);
1304
+
1305
+ // check prices (should be equal to old prices)
1306
+ expect(g1!.settings.prices).toHaveLength(2);
1307
+ expect(g1!.settings.prices[0].price.price).toBe(30);
1308
+ expect(g1!.settings.prices[0].price.reducedPrice).toBe(20);
1309
+ expect(g1!.settings.prices[1].price.price).toBe(300);
1310
+ expect(g1!.settings.prices[1].price.reducedPrice).toBe(200);
1311
+
1312
+ // check bundle discounts
1313
+ // a bundle discount for family members in the same category with 0 discount (only linked) and a bundle discount for family members in the same group
1314
+ expect(g1!.settings.prices[0].bundleDiscounts.size).toBe(2);
1315
+ // a bundle discount for family members in the same category
1316
+ expect(g1!.settings.prices[1].bundleDiscounts.size).toBe(1);
1317
+
1318
+ // custom discount for bundle discount of the first price should be null because the discounts are not different than the discounts for the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices1)
1319
+ expect([...g1!.settings.prices[0].bundleDiscounts.values()]).toEqual(
1320
+ expect.arrayContaining([
1321
+ // a bundle discount for family members in the same category with 0 discount (only linked)
1322
+ expect.objectContaining({ customDiscounts:
1323
+ expect.arrayContaining([
1324
+ expect.objectContaining({
1325
+ type: GroupPriceDiscountType.Fixed,
1326
+ value: expect.objectContaining({
1327
+ price: 0,
1328
+ reducedPrice: null,
1329
+ }),
1330
+ }),
1331
+ ]),
1332
+ }),
1333
+ // A bundle discount for family members in the same group. No custom discount because the discount is the same as the bundle discount on the organization period.
1334
+ expect.objectContaining({ customDiscounts: null }),
1335
+ ]),
1336
+ );
1337
+
1338
+ // custom discount for bundle discount of second price should be null because the discounts are the same as the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices2)
1339
+ expect([...g1!.settings.prices[1].bundleDiscounts.values()]).toEqual(
1340
+ expect.arrayContaining([
1341
+ expect.objectContaining({ customDiscounts: null }),
1342
+ ]),
1343
+ );
1344
+ });
1345
+
1346
+ test('group 2', async () => {
1347
+ // group 2
1348
+ const g2 = await Group.getByID(group2.id);
1349
+
1350
+ // check prices (the price should be 0 because there were no old prices configured)
1351
+ expect(g2!.settings.prices).toHaveLength(1);
1352
+ expect(g2!.settings.prices[0].price.price).toBe(0);
1353
+ expect(g2!.settings.prices[0].price.reducedPrice).toBeNull();
1354
+
1355
+ // check bundle discounts (each price should have 1 bundle discount for family members in same category)
1356
+ expect(g2!.settings.prices[0].bundleDiscounts.size).toBe(1);
1357
+
1358
+ // The custom discounts should be 0 because there were no prices for the group. It should only be linked to the bundle discount because the discount for group 1 should be applied if a member inscribes for group 2.
1359
+ expect([...g2!.settings.prices[0].bundleDiscounts.values()]).toEqual(
1360
+ expect.arrayContaining([
1361
+ expect.objectContaining({ customDiscounts: expect.arrayContaining([
1362
+ expect.objectContaining({
1363
+ type: GroupPriceDiscountType.Fixed,
1364
+ value: expect.objectContaining({
1365
+ price: 0,
1366
+ reducedPrice: null,
1367
+ }),
1368
+ }),
1369
+ ]) }),
1370
+ ]),
1371
+ );
1372
+ });
1373
+
1374
+ test('group 3', async () => {
1375
+ // group 3
1376
+ const g3 = await Group.getByID(group3.id);
1377
+
1378
+ // check prices (the price should be 0 because there were no old prices configured)
1379
+ expect(g3!.settings.prices).toHaveLength(1);
1380
+ expect(g3!.settings.prices[0].price.price).toBe(0);
1381
+ expect(g3!.settings.prices[0].price.reducedPrice).toBeNull();
1382
+
1383
+ // check bundle discounts (should have no bundle discount because the group is in another category)
1384
+ expect(g3!.settings.prices[0].bundleDiscounts.size).toBe(0);
1385
+ });
1386
+ });
1387
+
1388
+ describe('Old group price with single price should not result in an empty custom discount but in a custom discount of 0', () => {
1389
+ // The main purpose of this test is to check that the custom discount is not an array without items because the calculation of the bundle discount would fail.
1390
+
1391
+ let period: RegistrationPeriod;
1392
+ let organization: Organization;
1393
+ let organizationPeriod: OrganizationRegistrationPeriod;
1394
+ let group1: Group;
1395
+
1396
+ afterEach(async () => {
1397
+ if (group1) {
1398
+ await group1.delete();
1399
+ }
1400
+
1401
+ if (organizationPeriod) {
1402
+ await organizationPeriod.delete();
1403
+ }
1404
+
1405
+ if (period) {
1406
+ period.organizationId = null;
1407
+ await period.save();
1408
+ }
1409
+
1410
+ if (organization) {
1411
+ await organization.delete();
1412
+ }
1413
+
1414
+ if (period) {
1415
+ await period.delete();
1416
+ }
1417
+ });
1418
+
1419
+ // case where the discount is for family members in the same category
1420
+ test('family member discount for category', async () => {
1421
+ // arrange
1422
+ const startDate = new Date(2025, 0, 1);
1423
+ const endDate = new Date(2025, 11, 31);
1424
+ period = await new RegistrationPeriodFactory({ startDate, endDate }).create();
1425
+ organization = await new OrganizationFactory({ period }).create();
1426
+ period.organizationId = organization.id;
1427
+ await period.save();
1428
+ organizationPeriod = await new OrganizationRegistrationPeriodFactory({ organization, period }).create();
1429
+
1430
+ group1 = await new GroupFactory({ organization, period }).create();
1431
+ group1.settings.prices = [];
1432
+ group1.settings.name = new TranslatedString('group1');
1433
+
1434
+ // initial price with discount if family members in same category
1435
+ const oldPrices1 = OldGroupPrices.create({
1436
+ startDate: null,
1437
+ sameMemberOnlyDiscount: false,
1438
+ onlySameGroup: false,
1439
+ prices: [
1440
+ OldGroupPrice.create({
1441
+ price: 30,
1442
+ reducedPrice: 20,
1443
+ }),
1444
+ OldGroupPrice.create({
1445
+ price: 25,
1446
+ reducedPrice: 15,
1447
+ }),
1448
+ OldGroupPrice.create({
1449
+ price: 20,
1450
+ reducedPrice: 10,
1451
+ }),
1452
+ ],
1453
+ });
1454
+
1455
+ // price with discount after startDate 2025-03-01 if family members in same category
1456
+ const oldPrices2 = OldGroupPrices.create({
1457
+ startDate: new Date(2025, 2, 1),
1458
+ sameMemberOnlyDiscount: false,
1459
+ onlySameGroup: false,
1460
+ prices: [
1461
+ OldGroupPrice.create({
1462
+ price: 300,
1463
+ reducedPrice: 200,
1464
+ }),
1465
+ ],
1466
+ });
1467
+
1468
+ // add both prices with discounts to group 1
1469
+ group1.settings.oldPrices = [oldPrices2, oldPrices1];
1470
+
1471
+ await group1.save();
1472
+
1473
+ // add group 1 and to 2 to same category, add group 3 to different category
1474
+ organizationPeriod.settings.categories = [
1475
+ GroupCategory.create({
1476
+ settings: GroupCategorySettings.create({ name: 'category1' }),
1477
+ groupIds: [group1.id],
1478
+ }),
1479
+ ];
1480
+
1481
+ await organizationPeriod.save();
1482
+
1483
+ // act
1484
+ await migratePrices();
1485
+ const g1 = await Group.getByID(group1.id);
1486
+
1487
+ // check prices (should be equal to old prices)
1488
+ expect(g1!.settings.prices).toHaveLength(2);
1489
+ expect(g1!.settings.prices[0].price.price).toBe(30);
1490
+ expect(g1!.settings.prices[0].price.reducedPrice).toBe(20);
1491
+ expect(g1!.settings.prices[1].price.price).toBe(300);
1492
+ expect(g1!.settings.prices[1].price.reducedPrice).toBe(200);
1493
+
1494
+ // check bundle discounts (each price should have 1 bundle discount for family members in same category)
1495
+ expect(g1!.settings.prices[0].bundleDiscounts.size).toBe(1);
1496
+ expect(g1!.settings.prices[1].bundleDiscounts.size).toBe(1);
1497
+
1498
+ // custom discount for bundle discount of the first price should be null because the discounts are not different than the discounts for the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices1)
1499
+ expect([...g1!.settings.prices[0].bundleDiscounts.values()]).toEqual(
1500
+ expect.arrayContaining([
1501
+ expect.objectContaining({ customDiscounts: null }),
1502
+ ]),
1503
+ );
1504
+
1505
+ // custom discount for bundle discount of second price should not be null because the discounts are different than the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices1), it should contain the discount for oldPrices2 which is 0 because there is only one price
1506
+ expect([...g1!.settings.prices[1].bundleDiscounts.values()]).toEqual(
1507
+ expect.arrayContaining([
1508
+ expect.objectContaining({ customDiscounts: expect.arrayContaining([
1509
+ expect.objectContaining({
1510
+ type: GroupPriceDiscountType.Fixed,
1511
+ value: expect.objectContaining({
1512
+ price: 0,
1513
+ reducedPrice: null,
1514
+ }),
1515
+ }),
1516
+ ]) }),
1517
+ ]),
1518
+ );
1519
+ });
1520
+
1521
+ // case where the discount is for same members in the same category
1522
+ test('same member discount for category', async () => {
1523
+ // arrange
1524
+ const startDate = new Date(2025, 0, 1);
1525
+ const endDate = new Date(2025, 11, 31);
1526
+ period = await new RegistrationPeriodFactory({ startDate, endDate }).create();
1527
+ organization = await new OrganizationFactory({ period }).create();
1528
+ period.organizationId = organization.id;
1529
+ await period.save();
1530
+ organizationPeriod = await new OrganizationRegistrationPeriodFactory({ organization, period }).create();
1531
+
1532
+ group1 = await new GroupFactory({ organization, period }).create();
1533
+ group1.settings.prices = [];
1534
+ group1.settings.name = new TranslatedString('group1');
1535
+
1536
+ // initial price with discount if same members in same category
1537
+ const oldPrices1 = OldGroupPrices.create({
1538
+ startDate: null,
1539
+ sameMemberOnlyDiscount: true,
1540
+ onlySameGroup: false,
1541
+ prices: [
1542
+ OldGroupPrice.create({
1543
+ price: 30,
1544
+ reducedPrice: 20,
1545
+ }),
1546
+ OldGroupPrice.create({
1547
+ price: 25,
1548
+ reducedPrice: 15,
1549
+ }),
1550
+ OldGroupPrice.create({
1551
+ price: 20,
1552
+ reducedPrice: 10,
1553
+ }),
1554
+ ],
1555
+ });
1556
+
1557
+ // price with discount after startDate 2025-03-01 if same members in same category
1558
+ const oldPrices2 = OldGroupPrices.create({
1559
+ startDate: new Date(2025, 2, 1),
1560
+ sameMemberOnlyDiscount: true,
1561
+ onlySameGroup: false,
1562
+ prices: [
1563
+ OldGroupPrice.create({
1564
+ price: 300,
1565
+ reducedPrice: 200,
1566
+ }),
1567
+ ],
1568
+ });
1569
+
1570
+ // add both prices with discounts to group 1
1571
+ group1.settings.oldPrices = [oldPrices2, oldPrices1];
1572
+
1573
+ await group1.save();
1574
+
1575
+ // add group 1 and to 2 to same category, add group 3 to different category
1576
+ organizationPeriod.settings.categories = [
1577
+ GroupCategory.create({
1578
+ settings: GroupCategorySettings.create({ name: 'category1' }),
1579
+ groupIds: [group1.id],
1580
+ }),
1581
+ ];
1582
+
1583
+ await organizationPeriod.save();
1584
+
1585
+ // act
1586
+ await migratePrices();
1587
+ const g1 = await Group.getByID(group1.id);
1588
+
1589
+ // check prices (should be equal to old prices)
1590
+ expect(g1!.settings.prices).toHaveLength(2);
1591
+ expect(g1!.settings.prices[0].price.price).toBe(30);
1592
+ expect(g1!.settings.prices[0].price.reducedPrice).toBe(20);
1593
+ expect(g1!.settings.prices[1].price.price).toBe(300);
1594
+ expect(g1!.settings.prices[1].price.reducedPrice).toBe(200);
1595
+
1596
+ // check bundle discounts (each price should have 1 bundle discount for family members in same category)
1597
+ expect(g1!.settings.prices[0].bundleDiscounts.size).toBe(1);
1598
+ expect(g1!.settings.prices[1].bundleDiscounts.size).toBe(1);
1599
+
1600
+ // custom discount for bundle discount of the first price should be null because the discounts are not different than the discounts for the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices1)
1601
+ expect([...g1!.settings.prices[0].bundleDiscounts.values()]).toEqual(
1602
+ expect.arrayContaining([
1603
+ expect.objectContaining({ customDiscounts: null }),
1604
+ ]),
1605
+ );
1606
+
1607
+ // custom discount for bundle discount of second price should not be null because the discounts are different than the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices1), it should contain the discount for oldPrices2 which is 0 because there is only one price
1608
+ expect([...g1!.settings.prices[1].bundleDiscounts.values()]).toEqual(
1609
+ expect.arrayContaining([
1610
+ expect.objectContaining({ customDiscounts: expect.arrayContaining([
1611
+ expect.objectContaining({
1612
+ type: GroupPriceDiscountType.Fixed,
1613
+ value: expect.objectContaining({
1614
+ price: 0,
1615
+ reducedPrice: null,
1616
+ }),
1617
+ }),
1618
+ ]) }),
1619
+ ]),
1620
+ );
1621
+ });
1622
+
1623
+ // case where the discount is for family members in the same group
1624
+ test('family member discount for group', async () => {
1625
+ // arrange
1626
+ const startDate = new Date(2025, 0, 1);
1627
+ const endDate = new Date(2025, 11, 31);
1628
+ period = await new RegistrationPeriodFactory({ startDate, endDate }).create();
1629
+ organization = await new OrganizationFactory({ period }).create();
1630
+ period.organizationId = organization.id;
1631
+ await period.save();
1632
+ organizationPeriod = await new OrganizationRegistrationPeriodFactory({ organization, period }).create();
1633
+
1634
+ group1 = await new GroupFactory({ organization, period }).create();
1635
+ group1.settings.prices = [];
1636
+ group1.settings.name = new TranslatedString('group1');
1637
+
1638
+ // initial price with discount if same members in same category
1639
+ const oldPrices1 = OldGroupPrices.create({
1640
+ startDate: null,
1641
+ sameMemberOnlyDiscount: true,
1642
+ onlySameGroup: true,
1643
+ prices: [
1644
+ OldGroupPrice.create({
1645
+ price: 30,
1646
+ reducedPrice: 20,
1647
+ }),
1648
+ OldGroupPrice.create({
1649
+ price: 25,
1650
+ reducedPrice: 15,
1651
+ }),
1652
+ OldGroupPrice.create({
1653
+ price: 20,
1654
+ reducedPrice: 10,
1655
+ }),
1656
+ ],
1657
+ });
1658
+
1659
+ // price with discount after startDate 2025-03-01 if same members in same category
1660
+ const oldPrices2 = OldGroupPrices.create({
1661
+ startDate: new Date(2025, 2, 1),
1662
+ sameMemberOnlyDiscount: true,
1663
+ onlySameGroup: true,
1664
+ prices: [
1665
+ OldGroupPrice.create({
1666
+ price: 300,
1667
+ reducedPrice: 200,
1668
+ }),
1669
+ ],
1670
+ });
1671
+
1672
+ // add both prices with discounts to group 1
1673
+ group1.settings.oldPrices = [oldPrices2, oldPrices1];
1674
+
1675
+ await group1.save();
1676
+
1677
+ // add group 1 and to 2 to same category, add group 3 to different category
1678
+ organizationPeriod.settings.categories = [
1679
+ GroupCategory.create({
1680
+ settings: GroupCategorySettings.create({ name: 'category1' }),
1681
+ groupIds: [group1.id],
1682
+ }),
1683
+ ];
1684
+
1685
+ await organizationPeriod.save();
1686
+
1687
+ // act
1688
+ await migratePrices();
1689
+ const g1 = await Group.getByID(group1.id);
1690
+
1691
+ // check prices (should be equal to old prices)
1692
+ expect(g1!.settings.prices).toHaveLength(2);
1693
+ expect(g1!.settings.prices[0].price.price).toBe(30);
1694
+ expect(g1!.settings.prices[0].price.reducedPrice).toBe(20);
1695
+ expect(g1!.settings.prices[1].price.price).toBe(300);
1696
+ expect(g1!.settings.prices[1].price.reducedPrice).toBe(200);
1697
+
1698
+ // check bundle discounts
1699
+ // first price should have 1 bundle discount for same members in same group
1700
+ expect(g1!.settings.prices[0].bundleDiscounts.size).toBe(1);
1701
+ // second price should not have bundle discount because the group price has only one price
1702
+ expect(g1!.settings.prices[1].bundleDiscounts.size).toBe(0);
1703
+
1704
+ // custom discount for bundle discount of the first price should be null because the discounts are not different than the discounts for the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices1)
1705
+ expect([...g1!.settings.prices[0].bundleDiscounts.values()]).toEqual(
1706
+ expect.arrayContaining([
1707
+ expect.objectContaining({ customDiscounts: null }),
1708
+ ]),
1709
+ );
1710
+ });
1711
+ });
1712
+
1713
+ describe('Groups with same discounts in old prices should have bundle discounts without custom discounts', () => {
1714
+ let period: RegistrationPeriod;
1715
+ let organization: Organization;
1716
+ let organizationPeriod: OrganizationRegistrationPeriod;
1717
+ let group1: Group;
1718
+ let group2: Group;
1719
+
1720
+ afterEach(async () => {
1721
+ if (group1) {
1722
+ await group1.delete();
1723
+ }
1724
+
1725
+ if (group2) {
1726
+ await group2.delete();
1727
+ }
1728
+
1729
+ if (organizationPeriod) {
1730
+ await organizationPeriod.delete();
1731
+ }
1732
+
1733
+ if (period) {
1734
+ period.organizationId = null;
1735
+ await period.save();
1736
+ }
1737
+
1738
+ if (organization) {
1739
+ await organization.delete();
1740
+ }
1741
+
1742
+ if (period) {
1743
+ await period.delete();
1744
+ }
1745
+ });
1746
+
1747
+ // case where the discount is for family members in the same category
1748
+ test('family member discount for category', async () => {
1749
+ // arrange
1750
+ const startDate = new Date(2025, 0, 1);
1751
+ const endDate = new Date(2025, 11, 31);
1752
+ period = await new RegistrationPeriodFactory({ startDate, endDate }).create();
1753
+ organization = await new OrganizationFactory({ period }).create();
1754
+ period.organizationId = organization.id;
1755
+ await period.save();
1756
+ organizationPeriod = await new OrganizationRegistrationPeriodFactory({ organization, period }).create();
1757
+
1758
+ group1 = await new GroupFactory({ organization, period }).create();
1759
+ group1.settings.prices = [];
1760
+ group1.settings.name = new TranslatedString('group1');
1761
+
1762
+ // initial price with discount if family members in same category
1763
+ const oldPrices1 = OldGroupPrices.create({
1764
+ startDate: null,
1765
+ sameMemberOnlyDiscount: false,
1766
+ onlySameGroup: false,
1767
+ prices: [
1768
+ OldGroupPrice.create({
1769
+ price: 30,
1770
+ reducedPrice: 20,
1771
+ }),
1772
+ OldGroupPrice.create({
1773
+ price: 25,
1774
+ reducedPrice: 15,
1775
+ }),
1776
+ OldGroupPrice.create({
1777
+ price: 20,
1778
+ reducedPrice: 10,
1779
+ }),
1780
+ ],
1781
+ });
1782
+
1783
+ group1.settings.oldPrices = [oldPrices1];
1784
+
1785
+ await group1.save();
1786
+
1787
+ group2 = await new GroupFactory({ organization, period }).create();
1788
+ group2.settings.prices = [];
1789
+ group2.settings.name = new TranslatedString('group2');
1790
+
1791
+ // initial price with discount if family members in same category (same discounts as oldPrices1)
1792
+ const oldPrices2 = OldGroupPrices.create({
1793
+ startDate: null,
1794
+ sameMemberOnlyDiscount: false,
1795
+ onlySameGroup: false,
1796
+ prices: [
1797
+ OldGroupPrice.create({
1798
+ price: 30,
1799
+ reducedPrice: 20,
1800
+ }),
1801
+ OldGroupPrice.create({
1802
+ price: 25,
1803
+ reducedPrice: 15,
1804
+ }),
1805
+ OldGroupPrice.create({
1806
+ price: 20,
1807
+ reducedPrice: 10,
1808
+ }),
1809
+ ],
1810
+ });
1811
+ group2.settings.oldPrices = [oldPrices2];
1812
+
1813
+ await group2.save();
1814
+
1815
+ // add group 1 and to 2 to same category
1816
+ organizationPeriod.settings.categories = [
1817
+ GroupCategory.create({
1818
+ settings: GroupCategorySettings.create({ name: 'category1' }),
1819
+ groupIds: [group1.id, group2.id],
1820
+ }),
1821
+ ];
1822
+
1823
+ await organizationPeriod.save();
1824
+
1825
+ // act
1826
+ await migratePrices();
1827
+ const g1 = await Group.getByID(group1.id);
1828
+
1829
+ // test group 1
1830
+
1831
+ // check prices (should be equal to old prices)
1832
+ expect(g1!.settings.prices).toHaveLength(1);
1833
+ expect(g1!.settings.prices[0].price.price).toBe(30);
1834
+ expect(g1!.settings.prices[0].price.reducedPrice).toBe(20);
1835
+
1836
+ // check bundle discounts (each price should have 1 bundle discount for family members in same category)
1837
+ expect(g1!.settings.prices[0].bundleDiscounts.size).toBe(1);
1838
+
1839
+ // custom discount for bundle discount of the first price should be null because the discounts are not different than the discounts for the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices1)
1840
+ expect([...g1!.settings.prices[0].bundleDiscounts.values()]).toEqual(
1841
+ expect.arrayContaining([
1842
+ expect.objectContaining({ customDiscounts: null }),
1843
+ ]),
1844
+ );
1845
+
1846
+ // test group 2
1847
+ const g2 = await Group.getByID(group2.id);
1848
+
1849
+ // check prices (should be equal to old prices)
1850
+ expect(g2!.settings.prices).toHaveLength(1);
1851
+ expect(g2!.settings.prices[0].price.price).toBe(30);
1852
+ expect(g2!.settings.prices[0].price.reducedPrice).toBe(20);
1853
+
1854
+ // check bundle discounts (each price should have 1 bundle discount for family members in same category)
1855
+ expect(g2!.settings.prices[0].bundleDiscounts.size).toBe(1);
1856
+
1857
+ // custom discount for bundle discount of the second price should be null because the discounts are not different than the discounts for the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices1)
1858
+ expect([...g2!.settings.prices[0].bundleDiscounts.values()]).toEqual(
1859
+ expect.arrayContaining([
1860
+ expect.objectContaining({ customDiscounts: null }),
1861
+ ]),
1862
+ );
1863
+ });
1864
+ });
1865
+
1866
+ describe('Groups with different discounts in old prices should have bundle discounts with custom discounts', () => {
1867
+ let period: RegistrationPeriod;
1868
+ let organization: Organization;
1869
+ let organizationPeriod: OrganizationRegistrationPeriod;
1870
+ let group1: Group;
1871
+ let group2: Group;
1872
+
1873
+ afterEach(async () => {
1874
+ if (group1) {
1875
+ await group1.delete();
1876
+ }
1877
+
1878
+ if (group2) {
1879
+ await group2.delete();
1880
+ }
1881
+
1882
+ if (organizationPeriod) {
1883
+ await organizationPeriod.delete();
1884
+ }
1885
+
1886
+ if (period) {
1887
+ period.organizationId = null;
1888
+ await period.save();
1889
+ }
1890
+
1891
+ if (organization) {
1892
+ await organization.delete();
1893
+ }
1894
+
1895
+ if (period) {
1896
+ await period.delete();
1897
+ }
1898
+ });
1899
+
1900
+ test('group 2 has same discount count but other price', async () => {
1901
+ // arrange
1902
+ const startDate = new Date(2025, 0, 1);
1903
+ const endDate = new Date(2025, 11, 31);
1904
+ period = await new RegistrationPeriodFactory({ startDate, endDate }).create();
1905
+ organization = await new OrganizationFactory({ period }).create();
1906
+ period.organizationId = organization.id;
1907
+ await period.save();
1908
+ organizationPeriod = await new OrganizationRegistrationPeriodFactory({ organization, period }).create();
1909
+
1910
+ group1 = await new GroupFactory({ organization, period }).create();
1911
+ group1.settings.prices = [];
1912
+ group1.settings.name = new TranslatedString('group1');
1913
+
1914
+ // initial price with discount if family members in same category
1915
+ const oldPrices1 = OldGroupPrices.create({
1916
+ startDate: null,
1917
+ sameMemberOnlyDiscount: false,
1918
+ onlySameGroup: false,
1919
+ prices: [
1920
+ OldGroupPrice.create({
1921
+ price: 30,
1922
+ reducedPrice: 20,
1923
+ }),
1924
+ OldGroupPrice.create({
1925
+ price: 25,
1926
+ reducedPrice: 15,
1927
+ }),
1928
+ OldGroupPrice.create({
1929
+ price: 20,
1930
+ reducedPrice: 10,
1931
+ }),
1932
+ ],
1933
+ });
1934
+
1935
+ group1.settings.oldPrices = [oldPrices1];
1936
+
1937
+ await group1.save();
1938
+
1939
+ group2 = await new GroupFactory({ organization, period }).create();
1940
+ group2.settings.prices = [];
1941
+ group2.settings.name = new TranslatedString('group2');
1942
+
1943
+ // initial price with discount if family members in same category (different discount as oldPrices1)
1944
+ const oldPrices2 = OldGroupPrices.create({
1945
+ startDate: null,
1946
+ sameMemberOnlyDiscount: false,
1947
+ onlySameGroup: false,
1948
+ prices: [
1949
+ OldGroupPrice.create({
1950
+ price: 30,
1951
+ reducedPrice: 20,
1952
+ }),
1953
+ OldGroupPrice.create({
1954
+ price: 25,
1955
+ reducedPrice: 15,
1956
+ }),
1957
+ OldGroupPrice.create({
1958
+ // the only difference with oldPrices1!!!
1959
+ price: 21,
1960
+ reducedPrice: 10,
1961
+ }),
1962
+ ],
1963
+ });
1964
+ group2.settings.oldPrices = [oldPrices2];
1965
+
1966
+ await group2.save();
1967
+
1968
+ // add group 1 and to 2 to same category
1969
+ organizationPeriod.settings.categories = [
1970
+ GroupCategory.create({
1971
+ settings: GroupCategorySettings.create({ name: 'category1' }),
1972
+ groupIds: [group1.id, group2.id],
1973
+ }),
1974
+ ];
1975
+
1976
+ await organizationPeriod.save();
1977
+
1978
+ // act
1979
+ await migratePrices();
1980
+ const g1 = await Group.getByID(group1.id);
1981
+
1982
+ // test group 1
1983
+
1984
+ // check prices (should be equal to old prices)
1985
+ expect(g1!.settings.prices).toHaveLength(1);
1986
+ expect(g1!.settings.prices[0].price.price).toBe(30);
1987
+ expect(g1!.settings.prices[0].price.reducedPrice).toBe(20);
1988
+
1989
+ // check bundle discounts (each price should have 1 bundle discount for family members in same category)
1990
+ expect(g1!.settings.prices[0].bundleDiscounts.size).toBe(1);
1991
+
1992
+ // custom discount for bundle discount of the first price should be null because the discounts are not different than the discounts for the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices1)
1993
+ expect([...g1!.settings.prices[0].bundleDiscounts.values()]).toEqual(
1994
+ expect.arrayContaining([
1995
+ expect.objectContaining({ customDiscounts: null }),
1996
+ ]),
1997
+ );
1998
+
1999
+ // test group 2
2000
+ const g2 = await Group.getByID(group2.id);
2001
+
2002
+ // check prices (should be equal to old prices)
2003
+ expect(g2!.settings.prices).toHaveLength(1);
2004
+ expect(g2!.settings.prices[0].price.price).toBe(30);
2005
+ expect(g2!.settings.prices[0].price.reducedPrice).toBe(20);
2006
+
2007
+ // check bundle discounts (each price should have 1 bundle discount for family members in same category)
2008
+ expect(g2!.settings.prices[0].bundleDiscounts.size).toBe(1);
2009
+
2010
+ // custom discount for bundle discount of the second price should not be null because the discounts are different than the discounts for the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices1)
2011
+ expect([...g2!.settings.prices[0].bundleDiscounts.values()]).toEqual(
2012
+ expect.arrayContaining([
2013
+ expect.objectContaining({ customDiscounts: expect.arrayContaining([
2014
+ expect.objectContaining({
2015
+ type: GroupPriceDiscountType.Fixed,
2016
+ value: expect.objectContaining({
2017
+ price: 5,
2018
+ reducedPrice: 5,
2019
+ }),
2020
+ }),
2021
+ expect.objectContaining({
2022
+ type: GroupPriceDiscountType.Fixed,
2023
+ value: expect.objectContaining({
2024
+ price: 9,
2025
+ reducedPrice: 10,
2026
+ }),
2027
+ }),
2028
+ ]) }),
2029
+ ]),
2030
+ );
2031
+ });
2032
+
2033
+ test('group 2 has extra discount', async () => {
2034
+ // arrange
2035
+ const startDate = new Date(2025, 0, 1);
2036
+ const endDate = new Date(2025, 11, 31);
2037
+ period = await new RegistrationPeriodFactory({ startDate, endDate }).create();
2038
+ organization = await new OrganizationFactory({ period }).create();
2039
+ period.organizationId = organization.id;
2040
+ await period.save();
2041
+ organizationPeriod = await new OrganizationRegistrationPeriodFactory({ organization, period }).create();
2042
+
2043
+ group1 = await new GroupFactory({ organization, period }).create();
2044
+ group1.settings.prices = [];
2045
+ group1.settings.name = new TranslatedString('group1');
2046
+
2047
+ // initial price with discount if family members in same category
2048
+ const oldPrices1 = OldGroupPrices.create({
2049
+ startDate: null,
2050
+ sameMemberOnlyDiscount: false,
2051
+ onlySameGroup: false,
2052
+ prices: [
2053
+ OldGroupPrice.create({
2054
+ price: 30,
2055
+ reducedPrice: 20,
2056
+ }),
2057
+ OldGroupPrice.create({
2058
+ price: 25,
2059
+ reducedPrice: 15,
2060
+ }),
2061
+ OldGroupPrice.create({
2062
+ price: 20,
2063
+ reducedPrice: 10,
2064
+ }),
2065
+ ],
2066
+ });
2067
+
2068
+ group1.settings.oldPrices = [oldPrices1];
2069
+
2070
+ await group1.save();
2071
+
2072
+ group2 = await new GroupFactory({ organization, period }).create();
2073
+ group2.settings.prices = [];
2074
+ group2.settings.name = new TranslatedString('group2');
2075
+
2076
+ // initial price with discount if family members in same category (different discount as oldPrices1)
2077
+ const oldPrices2 = OldGroupPrices.create({
2078
+ startDate: null,
2079
+ sameMemberOnlyDiscount: false,
2080
+ onlySameGroup: false,
2081
+ prices: [
2082
+ OldGroupPrice.create({
2083
+ price: 30,
2084
+ reducedPrice: 20,
2085
+ }),
2086
+ OldGroupPrice.create({
2087
+ price: 25,
2088
+ reducedPrice: 15,
2089
+ }),
2090
+ OldGroupPrice.create({
2091
+ price: 20,
2092
+ reducedPrice: 10,
2093
+ }),
2094
+ // extra discount
2095
+ OldGroupPrice.create({
2096
+ price: 19,
2097
+ reducedPrice: 9,
2098
+ }),
2099
+ ],
2100
+ });
2101
+ group2.settings.oldPrices = [oldPrices2];
2102
+
2103
+ await group2.save();
2104
+
2105
+ // add group 1 and to 2 to same category
2106
+ organizationPeriod.settings.categories = [
2107
+ GroupCategory.create({
2108
+ settings: GroupCategorySettings.create({ name: 'category1' }),
2109
+ groupIds: [group1.id, group2.id],
2110
+ }),
2111
+ ];
2112
+
2113
+ await organizationPeriod.save();
2114
+
2115
+ // act
2116
+ await migratePrices();
2117
+ const g1 = await Group.getByID(group1.id);
2118
+
2119
+ // test group 1
2120
+
2121
+ // check prices (should be equal to old prices)
2122
+ expect(g1!.settings.prices).toHaveLength(1);
2123
+ expect(g1!.settings.prices[0].price.price).toBe(30);
2124
+ expect(g1!.settings.prices[0].price.reducedPrice).toBe(20);
2125
+
2126
+ // check bundle discounts (each price should have 1 bundle discount for family members in same category)
2127
+ expect(g1!.settings.prices[0].bundleDiscounts.size).toBe(1);
2128
+
2129
+ // custom discount for bundle discount of the first price should be null because the discounts are not different than the discounts for the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices1)
2130
+ expect([...g1!.settings.prices[0].bundleDiscounts.values()]).toEqual(
2131
+ expect.arrayContaining([
2132
+ expect.objectContaining({ customDiscounts: null }),
2133
+ ]),
2134
+ );
2135
+
2136
+ // test group 2
2137
+ const g2 = await Group.getByID(group2.id);
2138
+
2139
+ // check prices (should be equal to old prices)
2140
+ expect(g2!.settings.prices).toHaveLength(1);
2141
+ expect(g2!.settings.prices[0].price.price).toBe(30);
2142
+ expect(g2!.settings.prices[0].price.reducedPrice).toBe(20);
2143
+
2144
+ // check bundle discounts (each price should have 1 bundle discount for family members in same category)
2145
+ expect(g2!.settings.prices[0].bundleDiscounts.size).toBe(1);
2146
+
2147
+ // custom discount for bundle discount of the second price should not be null because the discounts are different than the discounts for the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices1)
2148
+ expect([...g2!.settings.prices[0].bundleDiscounts.values()]).toEqual(
2149
+ expect.arrayContaining([
2150
+ expect.objectContaining({ customDiscounts: expect.arrayContaining([
2151
+ expect.objectContaining({
2152
+ type: GroupPriceDiscountType.Fixed,
2153
+ value: expect.objectContaining({
2154
+ price: 5,
2155
+ reducedPrice: 5,
2156
+ }),
2157
+ }),
2158
+ expect.objectContaining({
2159
+ type: GroupPriceDiscountType.Fixed,
2160
+ value: expect.objectContaining({
2161
+ price: 10,
2162
+ reducedPrice: 10,
2163
+ }),
2164
+ }),
2165
+ expect.objectContaining({
2166
+ type: GroupPriceDiscountType.Fixed,
2167
+ value: expect.objectContaining({
2168
+ price: 11,
2169
+ reducedPrice: 11,
2170
+ }),
2171
+ }),
2172
+ ]) }),
2173
+ ]),
2174
+ );
2175
+ });
2176
+
2177
+ test('group 2 has one less discount', async () => {
2178
+ // arrange
2179
+ const startDate = new Date(2025, 0, 1);
2180
+ const endDate = new Date(2025, 11, 31);
2181
+ period = await new RegistrationPeriodFactory({ startDate, endDate }).create();
2182
+ organization = await new OrganizationFactory({ period }).create();
2183
+ period.organizationId = organization.id;
2184
+ await period.save();
2185
+ organizationPeriod = await new OrganizationRegistrationPeriodFactory({ organization, period }).create();
2186
+
2187
+ group1 = await new GroupFactory({ organization, period }).create();
2188
+ group1.settings.prices = [];
2189
+ group1.settings.name = new TranslatedString('group1');
2190
+
2191
+ // initial price with discount if family members in same category
2192
+ const oldPrices1 = OldGroupPrices.create({
2193
+ startDate: null,
2194
+ sameMemberOnlyDiscount: false,
2195
+ onlySameGroup: false,
2196
+ prices: [
2197
+ OldGroupPrice.create({
2198
+ price: 30,
2199
+ reducedPrice: 20,
2200
+ }),
2201
+ OldGroupPrice.create({
2202
+ price: 25,
2203
+ reducedPrice: 15,
2204
+ }),
2205
+ OldGroupPrice.create({
2206
+ price: 20,
2207
+ reducedPrice: 10,
2208
+ }),
2209
+ ],
2210
+ });
2211
+
2212
+ group1.settings.oldPrices = [oldPrices1];
2213
+
2214
+ await group1.save();
2215
+
2216
+ group2 = await new GroupFactory({ organization, period }).create();
2217
+ group2.settings.prices = [];
2218
+ group2.settings.name = new TranslatedString('group2');
2219
+
2220
+ // initial price with discount if family members in same category (different discount as oldPrices1)
2221
+ const oldPrices2 = OldGroupPrices.create({
2222
+ startDate: null,
2223
+ sameMemberOnlyDiscount: false,
2224
+ onlySameGroup: false,
2225
+ prices: [
2226
+ OldGroupPrice.create({
2227
+ price: 30,
2228
+ reducedPrice: 20,
2229
+ }),
2230
+ OldGroupPrice.create({
2231
+ price: 25,
2232
+ reducedPrice: 15,
2233
+ }),
2234
+ // one less discount
2235
+ ],
2236
+ });
2237
+ group2.settings.oldPrices = [oldPrices2];
2238
+
2239
+ await group2.save();
2240
+
2241
+ // add group 1 and to 2 to same category
2242
+ organizationPeriod.settings.categories = [
2243
+ GroupCategory.create({
2244
+ settings: GroupCategorySettings.create({ name: 'category1' }),
2245
+ groupIds: [group1.id, group2.id],
2246
+ }),
2247
+ ];
2248
+
2249
+ await organizationPeriod.save();
2250
+
2251
+ // act
2252
+ await migratePrices();
2253
+ const g1 = await Group.getByID(group1.id);
2254
+
2255
+ // test group 1
2256
+
2257
+ // check prices (should be equal to old prices)
2258
+ expect(g1!.settings.prices).toHaveLength(1);
2259
+ expect(g1!.settings.prices[0].price.price).toBe(30);
2260
+ expect(g1!.settings.prices[0].price.reducedPrice).toBe(20);
2261
+
2262
+ // check bundle discounts (each price should have 1 bundle discount for family members in same category)
2263
+ expect(g1!.settings.prices[0].bundleDiscounts.size).toBe(1);
2264
+
2265
+ // custom discount for bundle discount of the first price should be null because the discounts are not different than the discounts for the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices1)
2266
+ expect([...g1!.settings.prices[0].bundleDiscounts.values()]).toEqual(
2267
+ expect.arrayContaining([
2268
+ expect.objectContaining({ customDiscounts: null }),
2269
+ ]),
2270
+ );
2271
+
2272
+ // test group 2
2273
+ const g2 = await Group.getByID(group2.id);
2274
+
2275
+ // check prices (should be equal to old prices)
2276
+ expect(g2!.settings.prices).toHaveLength(1);
2277
+ expect(g2!.settings.prices[0].price.price).toBe(30);
2278
+ expect(g2!.settings.prices[0].price.reducedPrice).toBe(20);
2279
+
2280
+ // check bundle discounts (each price should have 1 bundle discount for family members in same category)
2281
+ expect(g2!.settings.prices[0].bundleDiscounts.size).toBe(1);
2282
+
2283
+ // custom discount for bundle discount of the second price should not be null because the discounts are different than the discounts for the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices1)
2284
+ expect([...g2!.settings.prices[0].bundleDiscounts.values()]).toEqual(
2285
+ expect.arrayContaining([
2286
+ expect.objectContaining({ customDiscounts: expect.arrayContaining([
2287
+ expect.objectContaining({
2288
+ type: GroupPriceDiscountType.Fixed,
2289
+ value: expect.objectContaining({
2290
+ price: 5,
2291
+ reducedPrice: 5,
2292
+ }),
2293
+ }),
2294
+ ]) }),
2295
+ ]),
2296
+ );
2297
+ });
2298
+ });
2299
+
2300
+ test('Groups that are in no category should not have bundle discounts from other groups that also do not have a category', async () => {
2301
+ // arrange
2302
+ const startDate = new Date(2025, 0, 1);
2303
+ const endDate = new Date(2025, 11, 31);
2304
+ const period = await new RegistrationPeriodFactory({ startDate, endDate }).create();
2305
+ const organization = await new OrganizationFactory({ period }).create();
2306
+ period.organizationId = organization.id;
2307
+ await period.save();
2308
+ const organizationPeriod = await new OrganizationRegistrationPeriodFactory({ organization, period }).create();
2309
+
2310
+ const group1 = await new GroupFactory({ organization, period }).create();
2311
+ group1.settings.prices = [];
2312
+ group1.settings.name = new TranslatedString('group1');
2313
+
2314
+ // initial price with discount if family members in same category
2315
+ const oldPrices1 = OldGroupPrices.create({
2316
+ startDate: null,
2317
+ sameMemberOnlyDiscount: false,
2318
+ onlySameGroup: false,
2319
+ prices: [
2320
+ OldGroupPrice.create({
2321
+ price: 30,
2322
+ reducedPrice: 20,
2323
+ }),
2324
+ OldGroupPrice.create({
2325
+ price: 25,
2326
+ reducedPrice: 15,
2327
+ }),
2328
+ OldGroupPrice.create({
2329
+ price: 20,
2330
+ reducedPrice: 10,
2331
+ }),
2332
+ ],
2333
+ });
2334
+
2335
+ group1.settings.oldPrices = [oldPrices1];
2336
+
2337
+ await group1.save();
2338
+
2339
+ const group2 = await new GroupFactory({ organization, period }).create();
2340
+ group2.settings.prices = [];
2341
+ group2.settings.name = new TranslatedString('group2');
2342
+
2343
+ // initial price with discount if same members in same category
2344
+ const oldPrices2 = OldGroupPrices.create({
2345
+ startDate: null,
2346
+ sameMemberOnlyDiscount: true,
2347
+ onlySameGroup: false,
2348
+ prices: [
2349
+ OldGroupPrice.create({
2350
+ price: 30,
2351
+ reducedPrice: 20,
2352
+ }),
2353
+ OldGroupPrice.create({
2354
+ price: 25,
2355
+ reducedPrice: 15,
2356
+ }),
2357
+ OldGroupPrice.create({
2358
+ price: 20,
2359
+ reducedPrice: 10,
2360
+ }),
2361
+ ],
2362
+ });
2363
+ group2.settings.oldPrices = [oldPrices2];
2364
+
2365
+ await group2.save();
2366
+
2367
+ // add group 1 and to 2 to same category
2368
+ organizationPeriod.settings.categories = [
2369
+ GroupCategory.create({
2370
+ settings: GroupCategorySettings.create({ name: 'category1' }),
2371
+ // the groups are not in a category
2372
+ groupIds: [],
2373
+ }),
2374
+ ];
2375
+
2376
+ await organizationPeriod.save();
2377
+
2378
+ // act
2379
+ await migratePrices();
2380
+ const g1 = await Group.getByID(group1.id);
2381
+
2382
+ // test group 1
2383
+
2384
+ // check prices (should be equal to old prices)
2385
+ expect(g1!.settings.prices).toHaveLength(1);
2386
+ expect(g1!.settings.prices[0].price.price).toBe(30);
2387
+ expect(g1!.settings.prices[0].price.reducedPrice).toBe(20);
2388
+
2389
+ // check bundle discounts (each price should have 1 bundle discount for family members in same category)
2390
+ expect(g1!.settings.prices[0].bundleDiscounts.size).toBe(1);
2391
+
2392
+ // custom discount for bundle discount of the first price should be null because the discounts are not different than the discounts for the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices1)
2393
+ expect([...g1!.settings.prices[0].bundleDiscounts.values()]).toEqual(
2394
+ expect.arrayContaining([
2395
+ expect.objectContaining({ customDiscounts: null }),
2396
+ ]),
2397
+ );
2398
+
2399
+ // test group 2
2400
+ const g2 = await Group.getByID(group2.id);
2401
+
2402
+ // check prices (should be equal to old prices)
2403
+ expect(g2!.settings.prices).toHaveLength(1);
2404
+ expect(g2!.settings.prices[0].price.price).toBe(30);
2405
+ expect(g2!.settings.prices[0].price.reducedPrice).toBe(20);
2406
+
2407
+ // check bundle discounts (each price should have 1 bundle discount for family members in same category)
2408
+ expect(g2!.settings.prices[0].bundleDiscounts.size).toBe(1);
2409
+
2410
+ // custom discount for bundle discount of the second price should be null because the group is not in a category and thus should not be linked to the bundle discount of the other group
2411
+ expect([...g2!.settings.prices[0].bundleDiscounts.values()]).toEqual(
2412
+ expect.arrayContaining([
2413
+ expect.objectContaining({ customDiscounts: null }),
2414
+ ]),
2415
+ );
2416
+
2417
+ // cleanup
2418
+ await group1.delete();
2419
+ await group2.delete();
2420
+ await organizationPeriod.delete();
2421
+ period.organizationId = null;
2422
+ await period.save();
2423
+ await organization.delete();
2424
+ await period.delete();
2425
+ });
2426
+
2427
+ test('Groups that are archived should not have bundle discounts from other groups that also do not have a category', async () => {
2428
+ // arrange
2429
+ const startDate = new Date(2025, 0, 1);
2430
+ const endDate = new Date(2025, 11, 31);
2431
+ const period = await new RegistrationPeriodFactory({ startDate, endDate }).create();
2432
+ const organization = await new OrganizationFactory({ period }).create();
2433
+ period.organizationId = organization.id;
2434
+ await period.save();
2435
+ const organizationPeriod = await new OrganizationRegistrationPeriodFactory({ organization, period }).create();
2436
+
2437
+ const group1 = await new GroupFactory({ organization, period }).create();
2438
+ // archived group
2439
+ group1.status = GroupStatus.Archived;
2440
+ group1.settings.prices = [];
2441
+ group1.settings.name = new TranslatedString('group1');
2442
+
2443
+ // initial price with discount if family members in same category
2444
+ const oldPrices1 = OldGroupPrices.create({
2445
+ startDate: null,
2446
+ sameMemberOnlyDiscount: false,
2447
+ onlySameGroup: false,
2448
+ prices: [
2449
+ OldGroupPrice.create({
2450
+ price: 30,
2451
+ reducedPrice: 20,
2452
+ }),
2453
+ OldGroupPrice.create({
2454
+ price: 25,
2455
+ reducedPrice: 15,
2456
+ }),
2457
+ OldGroupPrice.create({
2458
+ price: 20,
2459
+ reducedPrice: 10,
2460
+ }),
2461
+ ],
2462
+ });
2463
+
2464
+ group1.settings.oldPrices = [oldPrices1];
2465
+
2466
+ await group1.save();
2467
+
2468
+ const group2 = await new GroupFactory({ organization, period }).create();
2469
+ // archived group
2470
+ group2.status = GroupStatus.Archived;
2471
+ group2.settings.prices = [];
2472
+ group2.settings.name = new TranslatedString('group2');
2473
+
2474
+ // initial price with discount if same members in same category
2475
+ const oldPrices2 = OldGroupPrices.create({
2476
+ startDate: null,
2477
+ sameMemberOnlyDiscount: true,
2478
+ onlySameGroup: false,
2479
+ prices: [
2480
+ OldGroupPrice.create({
2481
+ price: 30,
2482
+ reducedPrice: 20,
2483
+ }),
2484
+ OldGroupPrice.create({
2485
+ price: 25,
2486
+ reducedPrice: 15,
2487
+ }),
2488
+ OldGroupPrice.create({
2489
+ price: 20,
2490
+ reducedPrice: 10,
2491
+ }),
2492
+ ],
2493
+ });
2494
+ group2.settings.oldPrices = [oldPrices2];
2495
+
2496
+ await group2.save();
2497
+
2498
+ // add group 1 and to 2 to same category
2499
+ organizationPeriod.settings.categories = [
2500
+ GroupCategory.create({
2501
+ settings: GroupCategorySettings.create({ name: 'category1' }),
2502
+ groupIds: [group1.id, group2.id],
2503
+ }),
2504
+ ];
2505
+
2506
+ await organizationPeriod.save();
2507
+
2508
+ // act
2509
+ await migratePrices();
2510
+ const g1 = await Group.getByID(group1.id);
2511
+
2512
+ // test group 1
2513
+
2514
+ // check prices (should be equal to old prices)
2515
+ expect(g1!.settings.prices).toHaveLength(1);
2516
+ expect(g1!.settings.prices[0].price.price).toBe(30);
2517
+ expect(g1!.settings.prices[0].price.reducedPrice).toBe(20);
2518
+
2519
+ // check bundle discounts (each price should have 1 bundle discount for family members in same category)
2520
+ expect(g1!.settings.prices[0].bundleDiscounts.size).toBe(1);
2521
+
2522
+ // custom discount for bundle discount of the first price should be null because the discounts are not different than the discounts for the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices1)
2523
+ expect([...g1!.settings.prices[0].bundleDiscounts.values()]).toEqual(
2524
+ expect.arrayContaining([
2525
+ expect.objectContaining({ customDiscounts: null }),
2526
+ ]),
2527
+ );
2528
+
2529
+ // test group 2
2530
+ const g2 = await Group.getByID(group2.id);
2531
+
2532
+ // check prices (should be equal to old prices)
2533
+ expect(g2!.settings.prices).toHaveLength(1);
2534
+ expect(g2!.settings.prices[0].price.price).toBe(30);
2535
+ expect(g2!.settings.prices[0].price.reducedPrice).toBe(20);
2536
+
2537
+ // check bundle discounts (each price should have 1 bundle discount for family members in same category)
2538
+ expect(g2!.settings.prices[0].bundleDiscounts.size).toBe(1);
2539
+
2540
+ // custom discount for bundle discount of the second price should be null because the group is archived and thus should not be linked to the bundle discount of the other group, even if they are in the same category still
2541
+ expect([...g2!.settings.prices[0].bundleDiscounts.values()]).toEqual(
2542
+ expect.arrayContaining([
2543
+ expect.objectContaining({ customDiscounts: null }),
2544
+ ]),
2545
+ );
2546
+
2547
+ // cleanup
2548
+ await group1.delete();
2549
+ await group2.delete();
2550
+ await organizationPeriod.delete();
2551
+ period.organizationId = null;
2552
+ await period.save();
2553
+ await organization.delete();
2554
+ await period.delete();
2555
+ });
2556
+
2557
+ describe('Bundle discount should be reused if multiple groups have the same discount and onlySameGroup is true', () => {
2558
+ let period: RegistrationPeriod;
2559
+ let organization: Organization;
2560
+ let organizationPeriod: OrganizationRegistrationPeriod;
2561
+ let group1: Group;
2562
+ let group2: Group;
2563
+
2564
+ afterEach(async () => {
2565
+ if (group1) {
2566
+ await group1.delete();
2567
+ }
2568
+
2569
+ if (group2) {
2570
+ await group2.delete();
2571
+ }
2572
+
2573
+ if (organizationPeriod) {
2574
+ await organizationPeriod.delete();
2575
+ }
2576
+
2577
+ if (period) {
2578
+ period.organizationId = null;
2579
+ await period.save();
2580
+ }
2581
+
2582
+ if (organization) {
2583
+ await organization.delete();
2584
+ }
2585
+
2586
+ if (period) {
2587
+ await period.delete();
2588
+ }
2589
+ });
2590
+
2591
+ test('customDiscounts should be null if same discounts', async () => {
2592
+ // arrange
2593
+ const startDate = new Date(2025, 0, 1);
2594
+ const endDate = new Date(2025, 11, 31);
2595
+ period = await new RegistrationPeriodFactory({ startDate, endDate }).create();
2596
+ organization = await new OrganizationFactory({ period }).create();
2597
+ period.organizationId = organization.id;
2598
+ await period.save();
2599
+ organizationPeriod = await new OrganizationRegistrationPeriodFactory({ organization, period }).create();
2600
+
2601
+ group1 = await new GroupFactory({ organization, period }).create();
2602
+ group1.settings.prices = [];
2603
+ group1.settings.name = new TranslatedString('group1');
2604
+
2605
+ // initial price with discount if family members in same category
2606
+ const oldPrices1 = OldGroupPrices.create({
2607
+ startDate: null,
2608
+ sameMemberOnlyDiscount: false,
2609
+ onlySameGroup: true,
2610
+ prices: [
2611
+ OldGroupPrice.create({
2612
+ price: 30,
2613
+ reducedPrice: 20,
2614
+ }),
2615
+ OldGroupPrice.create({
2616
+ price: 25,
2617
+ reducedPrice: 15,
2618
+ }),
2619
+ OldGroupPrice.create({
2620
+ price: 20,
2621
+ reducedPrice: 10,
2622
+ }),
2623
+ ],
2624
+ });
2625
+
2626
+ group1.settings.oldPrices = [oldPrices1];
2627
+
2628
+ await group1.save();
2629
+
2630
+ group2 = await new GroupFactory({ organization, period }).create();
2631
+ group2.settings.prices = [];
2632
+ group2.settings.name = new TranslatedString('group2');
2633
+
2634
+ // initial price with discount if family members in same category (same discount as oldPrices1)
2635
+ const oldPrices2 = OldGroupPrices.create({
2636
+ startDate: null,
2637
+ sameMemberOnlyDiscount: false,
2638
+ onlySameGroup: true,
2639
+ prices: [
2640
+ OldGroupPrice.create({
2641
+ price: 30,
2642
+ reducedPrice: 20,
2643
+ }),
2644
+ OldGroupPrice.create({
2645
+ price: 25,
2646
+ reducedPrice: 15,
2647
+ }),
2648
+ OldGroupPrice.create({
2649
+ price: 20,
2650
+ reducedPrice: 10,
2651
+ }),
2652
+ ],
2653
+ });
2654
+ group2.settings.oldPrices = [oldPrices2];
2655
+
2656
+ await group2.save();
2657
+
2658
+ // add group 1 and to 2 to different category (should not make a difference)
2659
+ organizationPeriod.settings.categories = [
2660
+ GroupCategory.create({
2661
+ settings: GroupCategorySettings.create({ name: 'category1' }),
2662
+ groupIds: [group1.id],
2663
+ }),
2664
+ GroupCategory.create({
2665
+ settings: GroupCategorySettings.create({ name: 'category2' }),
2666
+ groupIds: [group2.id],
2667
+ }),
2668
+ ];
2669
+
2670
+ await organizationPeriod.save();
2671
+
2672
+ // act
2673
+ await migratePrices();
2674
+ const g1 = await Group.getByID(group1.id);
2675
+
2676
+ // test group 1
2677
+
2678
+ // check prices (should be equal to old prices)
2679
+ expect(g1!.settings.prices).toHaveLength(1);
2680
+ expect(g1!.settings.prices[0].price.price).toBe(30);
2681
+ expect(g1!.settings.prices[0].price.reducedPrice).toBe(20);
2682
+
2683
+ // check bundle discounts (each price should have 1 bundle discount for family members in same category)
2684
+ expect(g1!.settings.prices[0].bundleDiscounts.size).toBe(1);
2685
+
2686
+ // custom discount for bundle discount of the first price should be null because the discounts are not different than the discounts for the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices1)
2687
+ expect([...g1!.settings.prices[0].bundleDiscounts.values()]).toEqual(
2688
+ expect.arrayContaining([
2689
+ expect.objectContaining({ customDiscounts: null }),
2690
+ ]),
2691
+ );
2692
+
2693
+ // test group 2
2694
+ const g2 = await Group.getByID(group2.id);
2695
+
2696
+ // check prices (should be equal to old prices)
2697
+ expect(g2!.settings.prices).toHaveLength(1);
2698
+ expect(g2!.settings.prices[0].price.price).toBe(30);
2699
+ expect(g2!.settings.prices[0].price.reducedPrice).toBe(20);
2700
+
2701
+ // check bundle discounts (each price should have 1 bundle discount for family members in same category)
2702
+ expect(g2!.settings.prices[0].bundleDiscounts.size).toBe(1);
2703
+
2704
+ // custom discount for bundle discount of the second price should be null because the discounts are equal to the discounts for the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices1)
2705
+ expect([...g2!.settings.prices[0].bundleDiscounts.values()]).toEqual(
2706
+ expect.arrayContaining([
2707
+ expect.objectContaining({ customDiscounts: null }),
2708
+ ]),
2709
+ );
2710
+ });
2711
+
2712
+ test('should set customDiscounts if different discounts', async () => {
2713
+ // arrange
2714
+ const startDate = new Date(2025, 0, 1);
2715
+ const endDate = new Date(2025, 11, 31);
2716
+ period = await new RegistrationPeriodFactory({ startDate, endDate }).create();
2717
+ organization = await new OrganizationFactory({ period }).create();
2718
+ period.organizationId = organization.id;
2719
+ await period.save();
2720
+ organizationPeriod = await new OrganizationRegistrationPeriodFactory({ organization, period }).create();
2721
+
2722
+ group1 = await new GroupFactory({ organization, period }).create();
2723
+ group1.settings.prices = [];
2724
+ group1.settings.name = new TranslatedString('group1');
2725
+
2726
+ // initial price with discount if family members in same category
2727
+ const oldPrices1 = OldGroupPrices.create({
2728
+ startDate: null,
2729
+ sameMemberOnlyDiscount: false,
2730
+ onlySameGroup: true,
2731
+ prices: [
2732
+ OldGroupPrice.create({
2733
+ price: 30,
2734
+ reducedPrice: 20,
2735
+ }),
2736
+ OldGroupPrice.create({
2737
+ price: 25,
2738
+ reducedPrice: 15,
2739
+ }),
2740
+ OldGroupPrice.create({
2741
+ price: 20,
2742
+ reducedPrice: 10,
2743
+ }),
2744
+ ],
2745
+ });
2746
+
2747
+ group1.settings.oldPrices = [oldPrices1];
2748
+
2749
+ await group1.save();
2750
+
2751
+ group2 = await new GroupFactory({ organization, period }).create();
2752
+ group2.settings.prices = [];
2753
+ group2.settings.name = new TranslatedString('group2');
2754
+
2755
+ // initial price with discount if family members in same category (different discount as oldPrices1)
2756
+ const oldPrices2 = OldGroupPrices.create({
2757
+ startDate: null,
2758
+ sameMemberOnlyDiscount: false,
2759
+ onlySameGroup: true,
2760
+ prices: [
2761
+ OldGroupPrice.create({
2762
+ price: 30,
2763
+ reducedPrice: 20,
2764
+ }),
2765
+ OldGroupPrice.create({
2766
+ price: 25,
2767
+ reducedPrice: 15,
2768
+ }),
2769
+ OldGroupPrice.create({
2770
+ // only difference
2771
+ price: 21,
2772
+ reducedPrice: 10,
2773
+ }),
2774
+ ],
2775
+ });
2776
+ group2.settings.oldPrices = [oldPrices2];
2777
+
2778
+ await group2.save();
2779
+
2780
+ // add group 1 and to 2 to different category (should not make a difference)
2781
+ organizationPeriod.settings.categories = [
2782
+ GroupCategory.create({
2783
+ settings: GroupCategorySettings.create({ name: 'category1' }),
2784
+ groupIds: [group1.id],
2785
+ }),
2786
+ GroupCategory.create({
2787
+ settings: GroupCategorySettings.create({ name: 'category2' }),
2788
+ groupIds: [group2.id],
2789
+ }),
2790
+ ];
2791
+
2792
+ await organizationPeriod.save();
2793
+
2794
+ // act
2795
+ await migratePrices();
2796
+ const g1 = await Group.getByID(group1.id);
2797
+
2798
+ // test group 1
2799
+
2800
+ // check prices (should be equal to old prices)
2801
+ expect(g1!.settings.prices).toHaveLength(1);
2802
+ expect(g1!.settings.prices[0].price.price).toBe(30);
2803
+ expect(g1!.settings.prices[0].price.reducedPrice).toBe(20);
2804
+
2805
+ // check bundle discounts (each price should have 1 bundle discount for family members in same category)
2806
+ expect(g1!.settings.prices[0].bundleDiscounts.size).toBe(1);
2807
+
2808
+ // custom discount for bundle discount of the first price should be null because the discounts are not different than the discounts for the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices1)
2809
+ expect([...g1!.settings.prices[0].bundleDiscounts.values()]).toEqual(
2810
+ expect.arrayContaining([
2811
+ expect.objectContaining({ customDiscounts: null }),
2812
+ ]),
2813
+ );
2814
+
2815
+ // test group 2
2816
+ const g2 = await Group.getByID(group2.id);
2817
+
2818
+ // check prices (should be equal to old prices)
2819
+ expect(g2!.settings.prices).toHaveLength(1);
2820
+ expect(g2!.settings.prices[0].price.price).toBe(30);
2821
+ expect(g2!.settings.prices[0].price.reducedPrice).toBe(20);
2822
+
2823
+ // check bundle discounts (each price should have 1 bundle discount for family members in same category)
2824
+ expect(g2!.settings.prices[0].bundleDiscounts.size).toBe(1);
2825
+
2826
+ // custom discount for bundle discount of the second price should not be null because the discounts are different than the discounts for the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices1)
2827
+ expect([...g2!.settings.prices[0].bundleDiscounts.values()]).toEqual(
2828
+ expect.arrayContaining([
2829
+ expect.objectContaining({ customDiscounts: expect.arrayContaining([
2830
+ expect.objectContaining({
2831
+ type: GroupPriceDiscountType.Fixed,
2832
+ value: expect.objectContaining({
2833
+ price: 5,
2834
+ reducedPrice: 5,
2835
+ }),
2836
+ }),
2837
+ expect.objectContaining({
2838
+ type: GroupPriceDiscountType.Fixed,
2839
+ value: expect.objectContaining({
2840
+ price: 9,
2841
+ reducedPrice: 10,
2842
+ }),
2843
+ }),
2844
+ ]) }),
2845
+ ]),
2846
+ );
2847
+ });
2848
+
2849
+ test('customDiscounts should not be reused if sameMemberOnlyDiscount is different', async () => {
2850
+ // arrange
2851
+ const startDate = new Date(2025, 0, 1);
2852
+ const endDate = new Date(2025, 11, 31);
2853
+ period = await new RegistrationPeriodFactory({ startDate, endDate }).create();
2854
+ organization = await new OrganizationFactory({ period }).create();
2855
+ period.organizationId = organization.id;
2856
+ await period.save();
2857
+ organizationPeriod = await new OrganizationRegistrationPeriodFactory({ organization, period }).create();
2858
+
2859
+ group1 = await new GroupFactory({ organization, period }).create();
2860
+ group1.settings.prices = [];
2861
+ group1.settings.name = new TranslatedString('group1');
2862
+
2863
+ // initial price with discount if family members in same category
2864
+ const oldPrices1 = OldGroupPrices.create({
2865
+ startDate: null,
2866
+ sameMemberOnlyDiscount: false,
2867
+ onlySameGroup: true,
2868
+ prices: [
2869
+ OldGroupPrice.create({
2870
+ price: 30,
2871
+ reducedPrice: 20,
2872
+ }),
2873
+ OldGroupPrice.create({
2874
+ price: 25,
2875
+ reducedPrice: 15,
2876
+ }),
2877
+ OldGroupPrice.create({
2878
+ price: 20,
2879
+ reducedPrice: 10,
2880
+ }),
2881
+ ],
2882
+ });
2883
+
2884
+ group1.settings.oldPrices = [oldPrices1];
2885
+
2886
+ await group1.save();
2887
+
2888
+ group2 = await new GroupFactory({ organization, period }).create();
2889
+ group2.settings.prices = [];
2890
+ group2.settings.name = new TranslatedString('group2');
2891
+
2892
+ // initial price with discount if family members in same category (same discount as oldPrices1)
2893
+ const oldPrices2 = OldGroupPrices.create({
2894
+ startDate: null,
2895
+ sameMemberOnlyDiscount: true,
2896
+ onlySameGroup: true,
2897
+ prices: [
2898
+ OldGroupPrice.create({
2899
+ price: 30,
2900
+ reducedPrice: 20,
2901
+ }),
2902
+ OldGroupPrice.create({
2903
+ price: 25,
2904
+ reducedPrice: 15,
2905
+ }),
2906
+ OldGroupPrice.create({
2907
+ price: 20,
2908
+ reducedPrice: 10,
2909
+ }),
2910
+ ],
2911
+ });
2912
+ group2.settings.oldPrices = [oldPrices2];
2913
+
2914
+ await group2.save();
2915
+
2916
+ // add group 1 and to 2 to different category (should not make a difference)
2917
+ organizationPeriod.settings.categories = [
2918
+ GroupCategory.create({
2919
+ settings: GroupCategorySettings.create({ name: 'category1' }),
2920
+ groupIds: [group1.id],
2921
+ }),
2922
+ GroupCategory.create({
2923
+ settings: GroupCategorySettings.create({ name: 'category2' }),
2924
+ groupIds: [group2.id],
2925
+ }),
2926
+ ];
2927
+
2928
+ await organizationPeriod.save();
2929
+
2930
+ // act
2931
+ await migratePrices();
2932
+ const g1 = await Group.getByID(group1.id);
2933
+
2934
+ // test group 1
2935
+
2936
+ // check prices (should be equal to old prices)
2937
+ expect(g1!.settings.prices).toHaveLength(1);
2938
+ expect(g1!.settings.prices[0].price.price).toBe(30);
2939
+ expect(g1!.settings.prices[0].price.reducedPrice).toBe(20);
2940
+
2941
+ // check bundle discounts (each price should have 1 bundle discount for family members in same category)
2942
+ expect(g1!.settings.prices[0].bundleDiscounts.size).toBe(1);
2943
+
2944
+ // custom discount for bundle discount of the first price should be null because the discounts are not different than the discounts for the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices1)
2945
+ expect([...g1!.settings.prices[0].bundleDiscounts.values()]).toEqual(
2946
+ expect.arrayContaining([
2947
+ expect.objectContaining({ customDiscounts: null }),
2948
+ ]),
2949
+ );
2950
+
2951
+ // test group 2
2952
+ const g2 = await Group.getByID(group2.id);
2953
+
2954
+ // check prices (should be equal to old prices)
2955
+ expect(g2!.settings.prices).toHaveLength(1);
2956
+ expect(g2!.settings.prices[0].price.price).toBe(30);
2957
+ expect(g2!.settings.prices[0].price.reducedPrice).toBe(20);
2958
+
2959
+ // check bundle discounts (each price should have 1 bundle discount for family members in same category)
2960
+ expect(g2!.settings.prices[0].bundleDiscounts.size).toBe(1);
2961
+
2962
+ // custom discount for bundle discount of the second price should be null because the discounts are equal to the discounts for the bundle discount that is configured in the settings of the organization (which are the discounts for oldPrices1)
2963
+ expect([...g2!.settings.prices[0].bundleDiscounts.values()]).toEqual(
2964
+ expect.arrayContaining([
2965
+ expect.objectContaining({ customDiscounts: null }),
2966
+ ]),
2967
+ );
2968
+
2969
+ // check organization registration period
2970
+ const orgPeriod = await OrganizationRegistrationPeriod.getByID(organizationPeriod.id);
2971
+
2972
+ // the organization period should have 2 bundle discounts: 1 for family members in category 1 and 1 for same members in category 1
2973
+ expect(orgPeriod!.settings.bundleDiscounts).toHaveLength(2);
2974
+
2975
+ expect(orgPeriod!.settings.bundleDiscounts).toEqual(
2976
+ expect.arrayContaining([
2977
+ // bundle discount of oldPrices1 of group1
2978
+ expect.objectContaining({
2979
+ countWholeFamily: true,
2980
+ countPerGroup: true,
2981
+ // should contain the differences for oldPrices1
2982
+ discounts: expect.arrayContaining([
2983
+ expect.objectContaining({
2984
+ type: GroupPriceDiscountType.Fixed,
2985
+ value: expect.objectContaining({
2986
+ price: 5,
2987
+ reducedPrice: 5,
2988
+ }),
2989
+ }),
2990
+ expect.objectContaining({
2991
+ type: GroupPriceDiscountType.Fixed,
2992
+ value: expect.objectContaining({
2993
+ price: 10,
2994
+ reducedPrice: 10,
2995
+ }),
2996
+ }),
2997
+ ]) }),
2998
+ // bundle discount of oldPrices2 of group 2
2999
+ expect.objectContaining({
3000
+ countWholeFamily: false,
3001
+ countPerGroup: true,
3002
+ // should contain the differences for oldPrices1
3003
+ discounts: expect.arrayContaining([
3004
+ expect.objectContaining({
3005
+ type: GroupPriceDiscountType.Fixed,
3006
+ value: expect.objectContaining({
3007
+ price: 5,
3008
+ reducedPrice: 5,
3009
+ }),
3010
+ }),
3011
+ expect.objectContaining({
3012
+ type: GroupPriceDiscountType.Fixed,
3013
+ value: expect.objectContaining({
3014
+ price: 10,
3015
+ reducedPrice: 10,
3016
+ }),
3017
+ }),
3018
+ ]) }),
3019
+ ]),
3020
+ );
3021
+ });
3022
+ });
3023
+ });