@stamhoofd/backend 2.75.0 → 2.75.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stamhoofd/backend",
3
- "version": "2.75.0",
3
+ "version": "2.75.1",
4
4
  "main": "./dist/index.js",
5
5
  "exports": {
6
6
  ".": {
@@ -37,14 +37,14 @@
37
37
  "@simonbackx/simple-encoding": "2.20.0",
38
38
  "@simonbackx/simple-endpoints": "1.19.0",
39
39
  "@simonbackx/simple-logging": "^1.0.1",
40
- "@stamhoofd/backend-i18n": "2.75.0",
41
- "@stamhoofd/backend-middleware": "2.75.0",
42
- "@stamhoofd/email": "2.75.0",
43
- "@stamhoofd/models": "2.75.0",
44
- "@stamhoofd/queues": "2.75.0",
45
- "@stamhoofd/sql": "2.75.0",
46
- "@stamhoofd/structures": "2.75.0",
47
- "@stamhoofd/utility": "2.75.0",
40
+ "@stamhoofd/backend-i18n": "2.75.1",
41
+ "@stamhoofd/backend-middleware": "2.75.1",
42
+ "@stamhoofd/email": "2.75.1",
43
+ "@stamhoofd/models": "2.75.1",
44
+ "@stamhoofd/queues": "2.75.1",
45
+ "@stamhoofd/sql": "2.75.1",
46
+ "@stamhoofd/structures": "2.75.1",
47
+ "@stamhoofd/utility": "2.75.1",
48
48
  "archiver": "^7.0.1",
49
49
  "aws-sdk": "^2.885.0",
50
50
  "axios": "1.6.8",
@@ -64,5 +64,5 @@
64
64
  "publishConfig": {
65
65
  "access": "public"
66
66
  },
67
- "gitHead": "4710a9386f9f489356e04518622cc83c5faa2bf1"
67
+ "gitHead": "4c6a7f14c62e4832885325d02c5aaeeccf27568b"
68
68
  }
@@ -30,11 +30,6 @@ export class OpenIDConnectStartEndpoint extends Endpoint<Params, Query, Body, Re
30
30
  // Check webshop and/or organization
31
31
  await Context.setUserOrganizationScope();
32
32
  await Context.optionalAuthenticate({ allowWithoutAccount: false });
33
- console.log('Full start connect body;', await request.request.body);
34
-
35
- if (Context.user) {
36
- console.log('User:', Context.user);
37
- }
38
33
  const service = await SSOService.fromContext(request.body.provider);
39
34
  return await service.validateAndStartAuthCodeFlow(request.body);
40
35
  }
@@ -1,7 +1,7 @@
1
1
  import { Request } from '@simonbackx/simple-endpoints';
2
2
  import { Email } from '@stamhoofd/email';
3
3
  import { BalanceItemFactory, Group, GroupFactory, MemberFactory, MemberWithRegistrations, Organization, OrganizationFactory, Registration, RegistrationFactory, RegistrationPeriod, RegistrationPeriodFactory, Token, UserFactory } from '@stamhoofd/models';
4
- import { BalanceItemCartItem, BalanceItemType, Company, IDRegisterCart, IDRegisterCheckout, IDRegisterItem, OrganizationPackages, PayconiqAccount, PaymentCustomer, PaymentMethod, PermissionLevel, Permissions, STPackageStatus, STPackageType, UserPermissions, Version } from '@stamhoofd/structures';
4
+ import { BalanceItemCartItem, BalanceItemType, Company, GroupOption, GroupOptionMenu, IDRegisterCart, IDRegisterCheckout, IDRegisterItem, OrganizationPackages, PayconiqAccount, PaymentCustomer, PaymentMethod, PermissionLevel, Permissions, ReduceablePrice, RegisterItemOption, STPackageStatus, STPackageType, UserPermissions, Version } from '@stamhoofd/structures';
5
5
  import nock from 'nock';
6
6
  import { v4 as uuidv4 } from 'uuid';
7
7
  import { testServer } from '../../../../tests/helpers/TestServer';
@@ -1011,6 +1011,334 @@ describe('Endpoint.RegisterMembers', () => {
1011
1011
  jest.useFakeTimers().resetAllMocks();
1012
1012
  }
1013
1013
  });
1014
+
1015
+ test('Should update group stock reservations', async () => {
1016
+ // #region arrange
1017
+ const { organization, group, groupPrice, token, member } = await initData();
1018
+ groupPrice.stock = 5;
1019
+ await group.save();
1020
+
1021
+ const body = IDRegisterCheckout.create({
1022
+ cart: IDRegisterCart.create({
1023
+ items: [
1024
+ IDRegisterItem.create({
1025
+ id: uuidv4(),
1026
+ replaceRegistrationIds: [],
1027
+ options: [],
1028
+ groupPrice,
1029
+ organizationId: organization.id,
1030
+ groupId: group.id,
1031
+ memberId: member.id,
1032
+ }),
1033
+ ],
1034
+ balanceItems: [],
1035
+ deleteRegistrationIds: [],
1036
+ }),
1037
+ administrationFee: 0,
1038
+ freeContribution: 0,
1039
+ paymentMethod: PaymentMethod.PointOfSale,
1040
+ totalPrice: 25,
1041
+ asOrganizationId: organization.id,
1042
+ customer: null,
1043
+ });
1044
+ // #endregion
1045
+
1046
+ // #region act and assert
1047
+ expect(group?.stockReservations.length).toBe(0);
1048
+
1049
+ await post(body, organization, token);
1050
+
1051
+ const updatedGroup = await Group.getByID(group.id);
1052
+ expect(updatedGroup?.stockReservations.length).toBe(1);
1053
+ // #endregion
1054
+ });
1055
+
1056
+ test('Should fail if group price stock sold out', async () => {
1057
+ // #region arrange
1058
+ const { organization, group, groupPrice, token, member, otherMembers } = await initData({
1059
+ permissionLevel: PermissionLevel.Read, otherMemberAmount: 3 });
1060
+ groupPrice.stock = 2;
1061
+ await group.save();
1062
+
1063
+ const body = IDRegisterCheckout.create({
1064
+ cart: IDRegisterCart.create({
1065
+ items: [
1066
+ IDRegisterItem.create({
1067
+ id: uuidv4(),
1068
+ replaceRegistrationIds: [],
1069
+ options: [],
1070
+ groupPrice,
1071
+ organizationId: organization.id,
1072
+ groupId: group.id,
1073
+ memberId: member.id,
1074
+ }),
1075
+ ...otherMembers.map(m => IDRegisterItem.create({
1076
+ id: uuidv4(),
1077
+ replaceRegistrationIds: [],
1078
+ options: [],
1079
+ groupPrice,
1080
+ organizationId: organization.id,
1081
+ groupId: group.id,
1082
+ memberId: m.id,
1083
+ })),
1084
+ ],
1085
+ balanceItems: [],
1086
+ deleteRegistrationIds: [],
1087
+ }),
1088
+ administrationFee: 0,
1089
+ freeContribution: 0,
1090
+ paymentMethod: PaymentMethod.PointOfSale,
1091
+ totalPrice: 75,
1092
+ customer: null,
1093
+ });
1094
+ // #endregion
1095
+
1096
+ // #region act and assert
1097
+ expect(group?.stockReservations.length).toBe(0);
1098
+
1099
+ await expect(async () => await post(body, organization, token))
1100
+ .rejects
1101
+ .toThrow(new RegExp('Maximum reached'));
1102
+ // #endregion
1103
+ });
1104
+
1105
+ test('Should fail if option stock sold out', async () => {
1106
+ // #region arrange
1107
+ const { organization, group, groupPrice, token, member } = await initData();
1108
+
1109
+ const option1 = GroupOption.create({
1110
+ name: 'option 1',
1111
+ stock: 4,
1112
+ price: ReduceablePrice.create({
1113
+ price: 5,
1114
+ reducedPrice: 3,
1115
+ }),
1116
+ });
1117
+
1118
+ const option2 = GroupOption.create({
1119
+ name: 'option 2',
1120
+ stock: 4,
1121
+ price: ReduceablePrice.create({
1122
+ price: 3,
1123
+ reducedPrice: 1,
1124
+ }),
1125
+ });
1126
+
1127
+ const optionMenu = GroupOptionMenu.create({
1128
+ name: 'option menu 1',
1129
+ multipleChoice: true,
1130
+ options: [option1, option2],
1131
+ });
1132
+
1133
+ group.settings.optionMenus = [
1134
+ optionMenu,
1135
+ ];
1136
+
1137
+ await group.save();
1138
+
1139
+ const body = IDRegisterCheckout.create({
1140
+ cart: IDRegisterCart.create({
1141
+ items: [
1142
+ IDRegisterItem.create({
1143
+ id: uuidv4(),
1144
+ replaceRegistrationIds: [],
1145
+ options: [
1146
+ RegisterItemOption.create({
1147
+ option: option1,
1148
+ amount: 2,
1149
+ optionMenu,
1150
+ }),
1151
+ RegisterItemOption.create({
1152
+ option: option2,
1153
+ amount: 5,
1154
+ optionMenu,
1155
+ }),
1156
+ ],
1157
+ groupPrice,
1158
+ organizationId: organization.id,
1159
+ groupId: group.id,
1160
+ memberId: member.id,
1161
+ }),
1162
+ ],
1163
+ balanceItems: [
1164
+ ],
1165
+ deleteRegistrationIds: [],
1166
+ }),
1167
+ administrationFee: 0,
1168
+ freeContribution: 0,
1169
+ paymentMethod: PaymentMethod.PointOfSale,
1170
+ totalPrice: 50,
1171
+ customer: null,
1172
+ });
1173
+ // #endregion
1174
+
1175
+ // #region act and assert
1176
+ await expect(async () => await post(body, organization, token))
1177
+ .rejects
1178
+ .toThrow(new RegExp('Stock empty'));
1179
+ // #endregion
1180
+ });
1181
+
1182
+ test('Should fail if max option exceeded', async () => {
1183
+ // #region arrange
1184
+ const { organization, group, groupPrice, token, member } = await initData();
1185
+
1186
+ const option1 = GroupOption.create({
1187
+ name: 'option 1',
1188
+ stock: 4,
1189
+ maximum: 5,
1190
+ allowAmount: true,
1191
+ price: ReduceablePrice.create({
1192
+ price: 5,
1193
+ reducedPrice: 3,
1194
+ }),
1195
+ });
1196
+
1197
+ const option2 = GroupOption.create({
1198
+ name: 'option 2',
1199
+ stock: 5,
1200
+ maximum: 2,
1201
+ allowAmount: true,
1202
+ price: ReduceablePrice.create({
1203
+ price: 3,
1204
+ reducedPrice: 1,
1205
+ }),
1206
+ });
1207
+
1208
+ const optionMenu = GroupOptionMenu.create({
1209
+ name: 'option menu 1',
1210
+ multipleChoice: true,
1211
+ options: [option1, option2],
1212
+ });
1213
+
1214
+ group.settings.optionMenus = [
1215
+ optionMenu,
1216
+ ];
1217
+
1218
+ await group.save();
1219
+
1220
+ const body = IDRegisterCheckout.create({
1221
+ cart: IDRegisterCart.create({
1222
+ items: [
1223
+ IDRegisterItem.create({
1224
+ id: uuidv4(),
1225
+ replaceRegistrationIds: [],
1226
+ options: [
1227
+ RegisterItemOption.create({
1228
+ option: option1,
1229
+ amount: 2,
1230
+ optionMenu,
1231
+ }),
1232
+ RegisterItemOption.create({
1233
+ option: option2,
1234
+ amount: 5,
1235
+ optionMenu,
1236
+ }),
1237
+ ],
1238
+ groupPrice,
1239
+ organizationId: organization.id,
1240
+ groupId: group.id,
1241
+ memberId: member.id,
1242
+ }),
1243
+ ],
1244
+ balanceItems: [
1245
+ ],
1246
+ deleteRegistrationIds: [],
1247
+ }),
1248
+ administrationFee: 0,
1249
+ freeContribution: 0,
1250
+ paymentMethod: PaymentMethod.PointOfSale,
1251
+ totalPrice: 50,
1252
+ customer: null,
1253
+ });
1254
+ // #endregion
1255
+
1256
+ // #region act and assert
1257
+ await expect(async () => await post(body, organization, token))
1258
+ .rejects
1259
+ .toThrow(new RegExp('Option maximum exceeded'));
1260
+ // #endregion
1261
+ });
1262
+
1263
+ test('Should not fail if max option not exceeded', async () => {
1264
+ // #region arrange
1265
+ const { organization, group, groupPrice, token, member } = await initData();
1266
+
1267
+ const option1 = GroupOption.create({
1268
+ name: 'option 1',
1269
+ stock: 4,
1270
+ maximum: 5,
1271
+ allowAmount: true,
1272
+ price: ReduceablePrice.create({
1273
+ price: 5,
1274
+ reducedPrice: 3,
1275
+ }),
1276
+ });
1277
+
1278
+ const option2 = GroupOption.create({
1279
+ name: 'option 2',
1280
+ stock: 5,
1281
+ maximum: 5,
1282
+ allowAmount: true,
1283
+ price: ReduceablePrice.create({
1284
+ price: 3,
1285
+ reducedPrice: 1,
1286
+ }),
1287
+ });
1288
+
1289
+ const optionMenu = GroupOptionMenu.create({
1290
+ name: 'option menu 1',
1291
+ multipleChoice: true,
1292
+ options: [option1, option2],
1293
+ });
1294
+
1295
+ group.settings.optionMenus = [
1296
+ optionMenu,
1297
+ ];
1298
+
1299
+ await group.save();
1300
+
1301
+ const body = IDRegisterCheckout.create({
1302
+ cart: IDRegisterCart.create({
1303
+ items: [
1304
+ IDRegisterItem.create({
1305
+ id: uuidv4(),
1306
+ replaceRegistrationIds: [],
1307
+ options: [
1308
+ RegisterItemOption.create({
1309
+ option: option1,
1310
+ amount: 2,
1311
+ optionMenu,
1312
+ }),
1313
+ RegisterItemOption.create({
1314
+ option: option2,
1315
+ amount: 5,
1316
+ optionMenu,
1317
+ }),
1318
+ ],
1319
+ groupPrice,
1320
+ organizationId: organization.id,
1321
+ groupId: group.id,
1322
+ memberId: member.id,
1323
+ }),
1324
+ ],
1325
+ balanceItems: [
1326
+ ],
1327
+ deleteRegistrationIds: [],
1328
+ }),
1329
+ administrationFee: 0,
1330
+ freeContribution: 0,
1331
+ paymentMethod: PaymentMethod.PointOfSale,
1332
+ totalPrice: 50,
1333
+ customer: null,
1334
+ });
1335
+ // #endregion
1336
+
1337
+ // #region act and assert
1338
+ const result = await post(body, organization, token);
1339
+ expect(result).toBeDefined();
1340
+ // #endregion
1341
+ });
1014
1342
  });
1015
1343
 
1016
1344
  describe('Register by other organization', () => {
@@ -87,24 +87,14 @@ export class FileSignService {
87
87
 
88
88
  file.signature = jws;
89
89
  };
90
- }
91
-
92
- static async fillSignedUrl(file: File, duration = 60 * 60) {
93
- if (!file.isPrivate) {
94
- if (file.signedUrl) {
95
- console.error('Warning: public file has a signed url');
96
- // this will not be encoded because of the file encode implementation
97
- }
98
- return;
99
- }
100
90
 
101
- // Only created signed urls for files that were generated by our own server
102
- if (!await file.verify()) {
103
- console.error('Failed to verify file:', file.id);
104
- return;
105
- }
91
+ File.getWithSignedUrl = async (file: File) => {
92
+ return this.withSignedUrl(file);
93
+ };
94
+ }
106
95
 
107
- console.log('Signing file:', file.id);
96
+ static async withSignedUrl(file: File, duration = 60 * 60) {
97
+ console.log('Generating signed url:', file.id);
108
98
 
109
99
  if (file.signedUrl) {
110
100
  console.error('Warning: file already signed');
@@ -126,13 +116,13 @@ export class FileSignService {
126
116
  }
127
117
  catch (e) {
128
118
  console.error('Failed to sign file:', e);
129
- return file;
119
+ return null;
130
120
  }
131
121
  }
132
122
 
133
123
  static async fillSignedUrlsForStruct(data: any) {
134
124
  if (data instanceof File) {
135
- return await this.fillSignedUrl(data);
125
+ return (await data.withSignedUrl()) ?? undefined; // never return null if it fails because we'll want to use the original file in that case
136
126
  }
137
127
 
138
128
  if (Array.isArray(data)) {
@@ -81,7 +81,7 @@ export class SSOService {
81
81
  const platform = await Platform.getShared();
82
82
 
83
83
  const service = new SSOService({ provider, platform, organization, user: Context.user });
84
- service.validate();
84
+ service.validateConfiguration();
85
85
 
86
86
  return service;
87
87
  }
@@ -138,11 +138,13 @@ export class SSOService {
138
138
  return loginConfiguration;
139
139
  }
140
140
 
141
- validate() {
141
+ validateConfiguration() {
142
142
  // Validate configuration exists
143
143
  const _ = this.configuration;
144
144
  const __ = this.loginConfiguration;
145
+ }
145
146
 
147
+ validateUser() {
146
148
  if (this.user) {
147
149
  this.validateEmail(this.user.email);
148
150
  }
@@ -317,6 +319,9 @@ export class SSOService {
317
319
  const code_challenge = generators.codeChallenge(code_verifier);
318
320
  const expires = new Date(Date.now() + 1000 * 60 * 15);
319
321
 
322
+ // Validate user id
323
+ this.validateUser();
324
+
320
325
  const session: SSOSessionContext = {
321
326
  expires,
322
327
  code_verifier,
@@ -1,6 +1,6 @@
1
1
  import { Request } from '@simonbackx/simple-endpoints';
2
- import { BalanceItemFactory, GroupFactory, MemberFactory, MemberWithRegistrations, Organization, OrganizationFactory, OrganizationRegistrationPeriod, Platform, RegistrationFactory, RegistrationPeriod, RegistrationPeriodFactory, Token, UserFactory } from '@stamhoofd/models';
3
- import { AdministrationFeeSettings, BalanceItemCartItem, BalanceItemType, DefaultAgeGroup, FreeContributionSettings, GroupOption, GroupOptionMenu, IDRegisterCart, IDRegisterCheckout, IDRegisterItem, PaymentMethod, PermissionLevel, Permissions, PlatformMembershipType, PlatformMembershipTypeConfig, ReceivableBalanceType, ReduceablePrice, RegisterItemOption, Version } from '@stamhoofd/structures';
2
+ import { BalanceItemFactory, GroupFactory, MemberFactory, MemberWithRegistrations, Organization, OrganizationFactory, OrganizationRegistrationPeriod, Platform, RegistrationPeriod, RegistrationPeriodFactory, Token, UserFactory } from '@stamhoofd/models';
3
+ import { AdministrationFeeSettings, BalanceItemCartItem, BalanceItemStatus, BalanceItemType, BooleanStatus, DefaultAgeGroup, FreeContributionSettings, GroupOption, GroupOptionMenu, IDRegisterCart, IDRegisterCheckout, IDRegisterItem, PaymentMethod, PermissionLevel, Permissions, PlatformMembershipType, PlatformMembershipTypeConfig, ReceivableBalanceType, ReduceablePrice, RegisterItemOption, Version } from '@stamhoofd/structures';
4
4
  import { v4 as uuidv4 } from 'uuid';
5
5
  import { GetMemberFamilyEndpoint } from '../../src/endpoints/global/members/GetMemberFamilyEndpoint';
6
6
  import { RegisterMembersEndpoint } from '../../src/endpoints/global/registration/RegisterMembersEndpoint';
@@ -87,6 +87,7 @@ describe('E2E.Register', () => {
87
87
  const group = await new GroupFactory({
88
88
  organization,
89
89
  price: 25,
90
+ reducedPrice: 21,
90
91
  stock: 5,
91
92
  })
92
93
  .create();
@@ -152,10 +153,6 @@ describe('E2E.Register', () => {
152
153
  // #endregion
153
154
  });
154
155
 
155
- // todo: test max option + allowAmount
156
- // todo: test stock?
157
- // todo: test reduced price?
158
-
159
156
  test('Should create balance items for options', async () => {
160
157
  // #region arrange
161
158
  const { organization, group, groupPrice, token, member } = await initData();
@@ -492,19 +489,195 @@ describe('E2E.Register', () => {
492
489
  ]));
493
490
  // #endregion
494
491
  });
492
+
493
+ test('Should apply reduced price if member requires financial support', async () => {
494
+ // #region arrange
495
+ const { organization, group, groupPrice, token, member } = await initData();
496
+ member.details.requiresFinancialSupport = BooleanStatus.create({
497
+ value: true,
498
+ });
499
+
500
+ await member.save();
501
+
502
+ const body = IDRegisterCheckout.create({
503
+ cart: IDRegisterCart.create({
504
+ items: [
505
+ IDRegisterItem.create({
506
+ id: uuidv4(),
507
+ replaceRegistrationIds: [],
508
+ options: [],
509
+ groupPrice,
510
+ organizationId: organization.id,
511
+ groupId: group.id,
512
+ memberId: member.id,
513
+ }),
514
+ ],
515
+ balanceItems: [
516
+ ],
517
+ deleteRegistrationIds: [],
518
+ }),
519
+ administrationFee: 0,
520
+ freeContribution: 0,
521
+ paymentMethod: PaymentMethod.PointOfSale,
522
+ totalPrice: 21,
523
+ customer: null,
524
+ });
525
+ // #endregion
526
+
527
+ // #region act and assert
528
+ const balanceBefore = await getBalance(member.id, organization, token);
529
+ expect(balanceBefore).toBeDefined();
530
+ expect(balanceBefore.body.length).toBe(0);
531
+
532
+ await register(body, organization, token);
533
+
534
+ const balance = await getBalance(member.id, organization, token);
535
+ expect(balance).toBeDefined();
536
+ expect(balance.body.length).toBe(1);
537
+ expect(balance.body[0].price).toBe(21);
538
+ expect(balance.body[0].pricePaid).toBe(0);
539
+ // #endregion
540
+ });
541
+
542
+ test('Should apply reduced price for options if member requires financial support', async () => {
543
+ // #region arrange
544
+ const { organization, group, groupPrice, token, member } = await initData();
545
+ member.details.requiresFinancialSupport = BooleanStatus.create({
546
+ value: true,
547
+ });
548
+
549
+ await member.save();
550
+
551
+ const option1 = GroupOption.create({
552
+ name: 'option 1',
553
+ price: ReduceablePrice.create({
554
+ price: 5,
555
+ reducedPrice: 3,
556
+ }),
557
+ });
558
+
559
+ const option2 = GroupOption.create({
560
+ name: 'option 2',
561
+ price: ReduceablePrice.create({
562
+ price: 3,
563
+ reducedPrice: 1,
564
+ }),
565
+ });
566
+
567
+ const optionMenu = GroupOptionMenu.create({
568
+ name: 'option menu 1',
569
+ multipleChoice: true,
570
+ options: [option1, option2],
571
+ });
572
+
573
+ group.settings.optionMenus = [
574
+ optionMenu,
575
+ ];
576
+
577
+ await group.save();
578
+
579
+ const body = IDRegisterCheckout.create({
580
+ cart: IDRegisterCart.create({
581
+ items: [
582
+ IDRegisterItem.create({
583
+ id: uuidv4(),
584
+ replaceRegistrationIds: [],
585
+ options: [
586
+ RegisterItemOption.create({
587
+ option: option1,
588
+ amount: 2,
589
+ optionMenu,
590
+ }),
591
+ RegisterItemOption.create({
592
+ option: option2,
593
+ amount: 5,
594
+ optionMenu,
595
+ }),
596
+ ],
597
+ groupPrice,
598
+ organizationId: organization.id,
599
+ groupId: group.id,
600
+ memberId: member.id,
601
+ }),
602
+ ],
603
+ balanceItems: [
604
+ ],
605
+ deleteRegistrationIds: [],
606
+ }),
607
+ administrationFee: 0,
608
+ freeContribution: 0,
609
+ paymentMethod: PaymentMethod.PointOfSale,
610
+ totalPrice: 32,
611
+ customer: null,
612
+ });
613
+ // #endregion
614
+
615
+ // #region act and assert
616
+ const balanceBefore = await getBalance(member.id, organization, token);
617
+ expect(balanceBefore).toBeDefined();
618
+ expect(balanceBefore.body.length).toBe(0);
619
+
620
+ await register(body, organization, token);
621
+
622
+ const balance = await getBalance(member.id, organization, token);
623
+ expect(balance).toBeDefined();
624
+ expect(balance.body.length).toBe(3);
625
+ expect.arrayContaining([
626
+ expect.objectContaining({
627
+ price: 6,
628
+ pricePaid: 0,
629
+ status: BalanceItemStatus.Due,
630
+ }),
631
+ expect.objectContaining({
632
+ price: 5,
633
+ pricePaid: 0,
634
+ status: BalanceItemStatus.Due,
635
+ }),
636
+ expect.objectContaining({
637
+ price: 25,
638
+ pricePaid: 0,
639
+ status: BalanceItemStatus.Due,
640
+ }),
641
+ ]);
642
+ // #endregion
643
+ });
495
644
  });
496
645
 
497
646
  describe('Delete registrations', () => {
498
- // todo: should include call to other endpoints?
499
- test.skip('Should create negative balance items', async () => {
647
+ test('Should cancel balance item for deleted registration', async () => {
500
648
  // #region arrange
501
649
  const { member, group: group1, groupPrice: groupPrice1, organization, token } = await initData();
502
650
 
503
- const registration = await new RegistrationFactory({
504
- member,
505
- group: group1,
506
- groupPrice: groupPrice1,
507
- }).create();
651
+ const body1 = IDRegisterCheckout.create({
652
+ cart: IDRegisterCart.create({
653
+ items: [
654
+ IDRegisterItem.create({
655
+ id: uuidv4(),
656
+ replaceRegistrationIds: [],
657
+ options: [],
658
+ groupPrice: groupPrice1,
659
+ organizationId: organization.id,
660
+ groupId: group1.id,
661
+ memberId: member.id,
662
+ }),
663
+ ],
664
+ balanceItems: [
665
+ ],
666
+ deleteRegistrationIds: [],
667
+ }),
668
+ administrationFee: 0,
669
+ freeContribution: 0,
670
+ paymentMethod: PaymentMethod.PointOfSale,
671
+ totalPrice: 25,
672
+ customer: null,
673
+ asOrganizationId: organization.id,
674
+ });
675
+
676
+ const response1 = await register(body1, organization, token);
677
+ expect(response1.body).toBeDefined();
678
+ expect(response1.body.registrations.length).toBe(1);
679
+
680
+ const registrationToDelete = response1.body.registrations[0];
508
681
 
509
682
  const group = await new GroupFactory({
510
683
  organization,
@@ -514,7 +687,7 @@ describe('E2E.Register', () => {
514
687
 
515
688
  const groupPrice = group.settings.prices[0];
516
689
 
517
- const body = IDRegisterCheckout.create({
690
+ const body2 = IDRegisterCheckout.create({
518
691
  cart: IDRegisterCart.create({
519
692
  items: [
520
693
  IDRegisterItem.create({
@@ -527,31 +700,293 @@ describe('E2E.Register', () => {
527
700
  memberId: member.id,
528
701
  }),
529
702
  ],
530
- balanceItems: [],
531
- deleteRegistrationIds: [registration.id],
703
+ balanceItems: [
704
+ ],
705
+ deleteRegistrationIds: [registrationToDelete.id],
532
706
  }),
533
707
  administrationFee: 0,
534
708
  freeContribution: 0,
535
709
  paymentMethod: PaymentMethod.PointOfSale,
536
- totalPrice: 5,
537
- asOrganizationId: organization.id,
710
+ totalPrice: 30,
538
711
  customer: null,
712
+ asOrganizationId: organization.id,
539
713
  });
540
714
  // #endregion
541
715
 
542
716
  // #region act and assert
543
- const response = await register(body, organization, token);
717
+ const balanceBefore = await getBalance(member.id, organization, token);
718
+ expect(balanceBefore).toBeDefined();
719
+ expect(balanceBefore.body.length).toBe(1);
720
+ expect(balanceBefore.body[0].price).toBe(25);
721
+ expect(balanceBefore.body[0].pricePaid).toBe(0);
722
+
723
+ await register(body2, organization, token);
724
+
725
+ const balance = await getBalance(member.id, organization, token);
726
+ expect(balance).toBeDefined();
727
+ expect(balance.body.length).toBe(2);
544
728
 
545
- throw new Error('not implemented');
729
+ expect.arrayContaining([
730
+ expect.objectContaining({
731
+ price: 25,
732
+ pricePaid: 0,
733
+ status: BalanceItemStatus.Canceled,
734
+ }),
735
+ expect.objectContaining({
736
+ price: 30,
737
+ pricePaid: 0,
738
+ status: BalanceItemStatus.Due,
739
+ }),
740
+ ]);
546
741
  // #endregion
547
742
  });
548
743
 
549
- test.skip('Should apply cancelation fee', async () => {
550
- throw new Error('Not implemented');
744
+ test('Should cancel all related balance item for deleted registration', async () => {
745
+ // #region arrange
746
+ const { member, group: group1, groupPrice: groupPrice1, organization, token } = await initData();
747
+
748
+ const option1 = GroupOption.create({
749
+ name: 'option 1',
750
+ price: ReduceablePrice.create({
751
+ price: 5,
752
+ reducedPrice: 3,
753
+ }),
754
+ });
755
+
756
+ const option2 = GroupOption.create({
757
+ name: 'option 2',
758
+ price: ReduceablePrice.create({
759
+ price: 3,
760
+ reducedPrice: 1,
761
+ }),
762
+ });
763
+
764
+ const optionMenu = GroupOptionMenu.create({
765
+ name: 'option menu 1',
766
+ multipleChoice: true,
767
+ options: [option1, option2],
768
+ });
769
+
770
+ group1.settings.optionMenus = [
771
+ optionMenu,
772
+ ];
773
+
774
+ await group1.save();
775
+
776
+ const body1 = IDRegisterCheckout.create({
777
+ cart: IDRegisterCart.create({
778
+ items: [
779
+ IDRegisterItem.create({
780
+ id: uuidv4(),
781
+ replaceRegistrationIds: [],
782
+ options: [
783
+ RegisterItemOption.create({
784
+ option: option1,
785
+ amount: 2,
786
+ optionMenu,
787
+ }),
788
+ RegisterItemOption.create({
789
+ option: option2,
790
+ amount: 5,
791
+ optionMenu,
792
+ }),
793
+ ],
794
+ groupPrice: groupPrice1,
795
+ organizationId: organization.id,
796
+ groupId: group1.id,
797
+ memberId: member.id,
798
+ }),
799
+ ],
800
+ balanceItems: [
801
+ ],
802
+ deleteRegistrationIds: [],
803
+ }),
804
+ administrationFee: 0,
805
+ freeContribution: 0,
806
+ paymentMethod: PaymentMethod.PointOfSale,
807
+ totalPrice: 50,
808
+ customer: null,
809
+ asOrganizationId: organization.id,
810
+ });
811
+
812
+ const response1 = await register(body1, organization, token);
813
+ expect(response1.body).toBeDefined();
814
+ expect(response1.body.registrations.length).toBe(1);
815
+
816
+ const registrationToDelete = response1.body.registrations[0];
817
+
818
+ const group = await new GroupFactory({
819
+ organization,
820
+ price: 30,
821
+ stock: 5,
822
+ }).create();
823
+
824
+ const groupPrice = group.settings.prices[0];
825
+
826
+ const body2 = IDRegisterCheckout.create({
827
+ cart: IDRegisterCart.create({
828
+ items: [
829
+ IDRegisterItem.create({
830
+ id: uuidv4(),
831
+ replaceRegistrationIds: [],
832
+ options: [],
833
+ groupPrice,
834
+ organizationId: organization.id,
835
+ groupId: group.id,
836
+ memberId: member.id,
837
+ }),
838
+ ],
839
+ balanceItems: [
840
+ ],
841
+ deleteRegistrationIds: [registrationToDelete.id],
842
+ }),
843
+ administrationFee: 0,
844
+ freeContribution: 0,
845
+ paymentMethod: PaymentMethod.PointOfSale,
846
+ totalPrice: 30,
847
+ customer: null,
848
+ asOrganizationId: organization.id,
849
+ });
850
+ // #endregion
851
+
852
+ // #region act and assert
853
+ const balanceBefore = await getBalance(member.id, organization, token);
854
+ expect(balanceBefore).toBeDefined();
855
+ expect(balanceBefore.body.length).toBe(3);
856
+
857
+ await register(body2, organization, token);
858
+
859
+ const balance = await getBalance(member.id, organization, token);
860
+ expect(balance).toBeDefined();
861
+ expect(balance.body.length).toBe(4);
862
+
863
+ expect.arrayContaining([
864
+ expect.objectContaining({
865
+ price: 10,
866
+ pricePaid: 0,
867
+ status: BalanceItemStatus.Canceled,
868
+ }),
869
+ expect.objectContaining({
870
+ price: 15,
871
+ pricePaid: 0,
872
+ status: BalanceItemStatus.Canceled,
873
+ }),
874
+ expect.objectContaining({
875
+ price: 25,
876
+ pricePaid: 0,
877
+ status: BalanceItemStatus.Canceled,
878
+ }),
879
+ expect.objectContaining({
880
+ price: 30,
881
+ pricePaid: 0,
882
+ status: BalanceItemStatus.Due,
883
+ }),
884
+ ]);
885
+ // #endregion
551
886
  });
552
887
 
553
- test.skip('Should fail if invalid cancelation fee', async () => {
554
- throw new Error('Not implemented');
888
+ test('Should apply cancelation fee', async () => {
889
+ // #region arrange
890
+ const { member, group: group1, groupPrice: groupPrice1, organization, token } = await initData();
891
+
892
+ const body1 = IDRegisterCheckout.create({
893
+ cart: IDRegisterCart.create({
894
+ items: [
895
+ IDRegisterItem.create({
896
+ id: uuidv4(),
897
+ replaceRegistrationIds: [],
898
+ options: [],
899
+ groupPrice: groupPrice1,
900
+ organizationId: organization.id,
901
+ groupId: group1.id,
902
+ memberId: member.id,
903
+ }),
904
+ ],
905
+ balanceItems: [
906
+ ],
907
+ deleteRegistrationIds: [],
908
+ }),
909
+ administrationFee: 0,
910
+ freeContribution: 0,
911
+ paymentMethod: PaymentMethod.PointOfSale,
912
+ totalPrice: 25,
913
+ customer: null,
914
+ asOrganizationId: organization.id,
915
+ });
916
+
917
+ const response1 = await register(body1, organization, token);
918
+ expect(response1.body).toBeDefined();
919
+ expect(response1.body.registrations.length).toBe(1);
920
+
921
+ const registrationToDelete = response1.body.registrations[0];
922
+
923
+ const group = await new GroupFactory({
924
+ organization,
925
+ price: 30,
926
+ stock: 5,
927
+ }).create();
928
+
929
+ const groupPrice = group.settings.prices[0];
930
+
931
+ const body2 = IDRegisterCheckout.create({
932
+ cart: IDRegisterCart.create({
933
+ items: [
934
+ IDRegisterItem.create({
935
+ id: uuidv4(),
936
+ replaceRegistrationIds: [],
937
+ options: [],
938
+ groupPrice,
939
+ organizationId: organization.id,
940
+ groupId: group.id,
941
+ memberId: member.id,
942
+ }),
943
+ ],
944
+ balanceItems: [
945
+ ],
946
+ deleteRegistrationIds: [registrationToDelete.id],
947
+ }),
948
+ administrationFee: 0,
949
+ freeContribution: 0,
950
+ paymentMethod: PaymentMethod.PointOfSale,
951
+ totalPrice: 30,
952
+ customer: null,
953
+ asOrganizationId: organization.id,
954
+ cancellationFeePercentage: 2000,
955
+ });
956
+ // #endregion
957
+
958
+ // #region act and assert
959
+ const balanceBefore = await getBalance(member.id, organization, token);
960
+ expect(balanceBefore).toBeDefined();
961
+ expect(balanceBefore.body.length).toBe(1);
962
+ expect(balanceBefore.body[0].price).toBe(25);
963
+ expect(balanceBefore.body[0].pricePaid).toBe(0);
964
+
965
+ await register(body2, organization, token);
966
+
967
+ const balance = await getBalance(member.id, organization, token);
968
+ expect(balance).toBeDefined();
969
+ expect(balance.body.length).toBe(3);
970
+
971
+ expect.arrayContaining([
972
+ expect.objectContaining({
973
+ price: 25,
974
+ pricePaid: 0,
975
+ status: BalanceItemStatus.Canceled,
976
+ }),
977
+ expect.objectContaining({
978
+ price: 30,
979
+ pricePaid: 0,
980
+ status: BalanceItemStatus.Due,
981
+ }),
982
+ expect.objectContaining({
983
+ price: 5,
984
+ pricePaid: 0,
985
+ type: BalanceItemType.CancellationFee,
986
+ status: BalanceItemStatus.Due,
987
+ }),
988
+ ]);
989
+ // #endregion
555
990
  });
556
991
  });
557
992