Skip to content

Commit dcd8bb9

Browse files
committed
feat: add WEB_STORE_REFUND_DEADLINE_DAYS
refs: LINK-2401
1 parent 4021f94 commit dcd8bb9

7 files changed

Lines changed: 218 additions & 0 deletions

File tree

src/domain/app/i18n/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2210,6 +2210,7 @@
22102210
"waitingListTableCount": "{{count}} in Waiting list",
22112211
"warningHasPaymentCancellation": "The participant's payment will be cancelled and the participant cannot be removed.",
22122212
"warningHasPaymentRefund": "The participant's payment will be refunded and the participant cannot be removed.",
2213+
"warningRefundDeadlinePassed": "The refund deadline has passed and the participant cannot be removed.",
22132214
"warningNoRightsToCreate": "You have insufficient rights to create participants to this registration.",
22142215
"warningNoRightsToEdit": "You have insufficient rights to edit this participant.",
22152216
"warningNoRightsToView": "You have insufficient rights to view participants of this registration."

src/domain/app/i18n/fi.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2209,6 +2209,7 @@
22092209
"waitingListTableCount": "{{count}} jonopaikalla",
22102210
"warningHasPaymentCancellation": "Osallistujan maksua perutaan eikä osallistujaa voi poistaa.",
22112211
"warningHasPaymentRefund": "Osallistujan maksua hyvitetään eikä osallistujaa voi poistaa.",
2212+
"warningRefundDeadlinePassed": "Hyvityksen määräaika on umpeutunut eikä osallistujaa voi poistaa.",
22122213
"warningNoRightsToCreate": "Sinulla ei ole oikeuksia lisätä osallistujia tähän ilmoittautumiseen.",
22132214
"warningNoRightsToEdit": "Sinulla ei ole oikeuksia muokata tätä osallistujaa.",
22142215
"warningNoRightsToView": "Sinulla ei ole oikeuksia nähdä tämän ilmoittautumisen osallistujia."

src/domain/signup/__tests__/permissions.test.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
/* eslint-disable import/no-named-as-default-member */
2+
import addDays from 'date-fns/addDays';
23
import i18n from 'i18next';
34

45
import {
6+
fakeEvent,
57
fakePaymentCancellation,
68
fakePaymentRefund,
9+
fakePriceGroup,
710
fakeRegistration,
11+
fakeRegistrationPriceGroup,
812
fakeSignup,
913
fakeSignupGroup,
1014
fakeUser,
@@ -29,6 +33,7 @@ describe('getSignupActionWarning function', () => {
2933
(action) => {
3034
const commonProps = {
3135
authenticated: false,
36+
registration: fakeRegistration(),
3237
t: i18n.t.bind(i18n),
3338
userCanDoAction: false,
3439
};
@@ -72,6 +77,7 @@ describe('getSignupActionWarning function', () => {
7277
expect(
7378
getSignupActionWarning({
7479
authenticated: true,
80+
registration: fakeRegistration(),
7581
t: i18n.t.bind(i18n),
7682
userCanDoAction: false,
7783
action,
@@ -84,6 +90,7 @@ describe('getSignupActionWarning function', () => {
8490
expect(
8591
getSignupActionWarning({
8692
authenticated: true,
93+
registration: fakeRegistration(),
8794
signup: fakeSignup({
8895
paymentCancellation: fakePaymentCancellation(),
8996
}),
@@ -98,6 +105,7 @@ describe('getSignupActionWarning function', () => {
98105
expect(
99106
getSignupActionWarning({
100107
authenticated: true,
108+
registration: fakeRegistration(),
101109
signup: fakeSignup(),
102110
signupGroup: fakeSignupGroup({
103111
paymentCancellation: fakePaymentCancellation(),
@@ -113,6 +121,7 @@ describe('getSignupActionWarning function', () => {
113121
expect(
114122
getSignupActionWarning({
115123
authenticated: true,
124+
registration: fakeRegistration(),
116125
signup: fakeSignup({
117126
paymentRefund: fakePaymentRefund(),
118127
}),
@@ -127,6 +136,7 @@ describe('getSignupActionWarning function', () => {
127136
expect(
128137
getSignupActionWarning({
129138
authenticated: true,
139+
registration: fakeRegistration(),
130140
signup: fakeSignup(),
131141
signupGroup: fakeSignupGroup({
132142
paymentRefund: fakePaymentRefund(),
@@ -137,6 +147,112 @@ describe('getSignupActionWarning function', () => {
137147
})
138148
).toBe('Osallistujan maksua hyvitetään eikä osallistujaa voi poistaa.');
139149
});
150+
151+
it('should return correct warning when trying to delete paid signup past refund deadline', () => {
152+
const eventInFiveDays = addDays(new Date(), 5);
153+
154+
expect(
155+
getSignupActionWarning({
156+
authenticated: true,
157+
registration: fakeRegistration({
158+
event: fakeEvent({ startTime: eventInFiveDays.toISOString() }),
159+
registrationPriceGroups: [
160+
fakeRegistrationPriceGroup({ price: '10.00' }),
161+
],
162+
}),
163+
signup: fakeSignup({
164+
priceGroup: fakePriceGroup(),
165+
}),
166+
t: i18n.t.bind(i18n),
167+
userCanDoAction: true,
168+
action: SIGNUP_ACTIONS.DELETE,
169+
})
170+
).toBe('Hyvityksen määräaika on umpeutunut eikä osallistujaa voi poistaa.');
171+
});
172+
173+
it('should return correct warning when trying to delete signup group with paid signups past refund deadline', () => {
174+
const eventInFiveDays = addDays(new Date(), 5);
175+
176+
expect(
177+
getSignupActionWarning({
178+
authenticated: true,
179+
registration: fakeRegistration({
180+
event: fakeEvent({ startTime: eventInFiveDays.toISOString() }),
181+
registrationPriceGroups: [
182+
fakeRegistrationPriceGroup({ price: '10.00' }),
183+
],
184+
}),
185+
signupGroup: fakeSignupGroup({
186+
signups: [fakeSignup({ priceGroup: fakePriceGroup() })],
187+
}),
188+
t: i18n.t.bind(i18n),
189+
userCanDoAction: true,
190+
action: SIGNUP_ACTIONS.DELETE,
191+
})
192+
).toBe('Hyvityksen määräaika on umpeutunut eikä osallistujaa voi poistaa.');
193+
});
194+
195+
it('should not return refund deadline warning when trying to delete paid signup within refund deadline', () => {
196+
const eventInTenDays = addDays(new Date(), 10);
197+
198+
expect(
199+
getSignupActionWarning({
200+
authenticated: true,
201+
registration: fakeRegistration({
202+
event: fakeEvent({ startTime: eventInTenDays.toISOString() }),
203+
registrationPriceGroups: [
204+
fakeRegistrationPriceGroup({ price: '10.00' }),
205+
],
206+
}),
207+
signup: fakeSignup({
208+
priceGroup: fakePriceGroup(),
209+
}),
210+
t: i18n.t.bind(i18n),
211+
userCanDoAction: true,
212+
action: SIGNUP_ACTIONS.DELETE,
213+
})
214+
).toBe('');
215+
});
216+
217+
it('should not return refund deadline warning when trying to delete free signup past deadline', () => {
218+
const eventInFiveDays = addDays(new Date(), 5);
219+
220+
expect(
221+
getSignupActionWarning({
222+
authenticated: true,
223+
registration: fakeRegistration({
224+
event: fakeEvent({ startTime: eventInFiveDays.toISOString() }),
225+
registrationPriceGroups: [
226+
fakeRegistrationPriceGroup({ price: '0.00' }),
227+
],
228+
}),
229+
signup: fakeSignup(),
230+
t: i18n.t.bind(i18n),
231+
userCanDoAction: true,
232+
action: SIGNUP_ACTIONS.DELETE,
233+
})
234+
).toBe('');
235+
});
236+
237+
it('should not return refund deadline warning when registration has no pricing', () => {
238+
const eventInFiveDays = addDays(new Date(), 5);
239+
240+
expect(
241+
getSignupActionWarning({
242+
authenticated: true,
243+
registration: fakeRegistration({
244+
event: fakeEvent({ startTime: eventInFiveDays.toISOString() }),
245+
registrationPriceGroups: [],
246+
}),
247+
signup: fakeSignup({
248+
priceGroup: fakePriceGroup(),
249+
}),
250+
t: i18n.t.bind(i18n),
251+
userCanDoAction: true,
252+
action: SIGNUP_ACTIONS.DELETE,
253+
})
254+
).toBe('');
255+
});
140256
});
141257

142258
describe('checkCanUserDoSignupAction function', () => {
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import addDays from 'date-fns/addDays';
2+
import subDays from 'date-fns/subDays';
3+
import { describe, expect, it } from 'vitest';
4+
5+
import { isWithinRefundDeadline } from '../refundDeadline';
6+
7+
describe('isWithinRefundDeadline', () => {
8+
it('should return false if eventStartTime is null', () => {
9+
expect(isWithinRefundDeadline(null)).toBe(false);
10+
});
11+
12+
it('should return false if eventStartTime is undefined', () => {
13+
expect(isWithinRefundDeadline(undefined)).toBe(false);
14+
});
15+
16+
it('should return false if eventStartTime is invalid', () => {
17+
expect(isWithinRefundDeadline('invalid-date')).toBe(false);
18+
});
19+
20+
it('should return true if current time is within the refund deadline (7 days before event)', () => {
21+
const futureEventDate = addDays(new Date(), 10);
22+
expect(isWithinRefundDeadline(futureEventDate.toISOString())).toBe(true);
23+
});
24+
25+
it('should return false if current time is past the refund deadline (7 days before event)', () => {
26+
const nearEventDate = addDays(new Date(), 3);
27+
expect(isWithinRefundDeadline(nearEventDate.toISOString())).toBe(false);
28+
});
29+
30+
it('should return false if event has already started', () => {
31+
const pastEventDate = subDays(new Date(), 1);
32+
expect(isWithinRefundDeadline(pastEventDate.toISOString())).toBe(false);
33+
});
34+
35+
it('should return true on the boundary (exactly at midnight after deadline)', () => {
36+
// Event is 8 days from now, deadline is 7 days before event
37+
// So we're at exactly 1 day after deadline starts, should still be within
38+
const futureEventDate = addDays(new Date(), 8);
39+
expect(isWithinRefundDeadline(futureEventDate.toISOString())).toBe(true);
40+
});
41+
42+
it('should handle edge case at exactly the deadline boundary', () => {
43+
// Event is exactly 7 days from now
44+
const exactDeadlineDate = addDays(new Date(), 7);
45+
// At this point, we're at the deadline, but not past it yet
46+
expect(isWithinRefundDeadline(exactDeadlineDate.toISOString())).toBe(true);
47+
});
48+
});

src/domain/signup/permissions.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
isRegistrationAdminUserInOrganization,
1616
} from '../organization/utils';
1717
import { SIGNUP_ACTIONS, SIGNUP_ICONS, SIGNUP_LABEL_KEYS } from './constants';
18+
import { isWithinRefundDeadline } from './refundDeadline';
1819

1920
export const checkCanUserDoSignupAction = ({
2021
action,
@@ -61,13 +62,15 @@ export const checkCanUserDoSignupAction = ({
6162
export const getSignupActionWarning = ({
6263
action,
6364
authenticated,
65+
registration,
6466
signup,
6567
signupGroup,
6668
t,
6769
userCanDoAction,
6870
}: {
6971
action: SIGNUP_ACTIONS;
7072
authenticated: boolean;
73+
registration: RegistrationFieldsFragment;
7174
signup?: SignupFieldsFragment;
7275
signupGroup?: SignupGroupFieldsFragment;
7376
t: TFunction;
@@ -93,6 +96,22 @@ export const getSignupActionWarning = ({
9396
if (signup?.paymentRefund || signupGroup?.paymentRefund) {
9497
return t('signupsPage.warningHasPaymentRefund');
9598
}
99+
100+
const hasPaidSignup = Boolean(
101+
signup?.priceGroup || signupGroup?.signups?.some((s) => s?.priceGroup)
102+
);
103+
const registrationHasPricing = registration.registrationPriceGroups?.some(
104+
(pg) => pg?.price && parseFloat(pg.price) > 0
105+
);
106+
const eventStartTime = registration.event?.startTime;
107+
108+
if (
109+
hasPaidSignup &&
110+
registrationHasPricing &&
111+
!isWithinRefundDeadline(eventStartTime)
112+
) {
113+
return t('signupsPage.warningRefundDeadlinePassed');
114+
}
96115
}
97116

98117
return '';
@@ -127,6 +146,7 @@ export const checkIsSignupActionAllowed = ({
127146
const warning = getSignupActionWarning({
128147
action,
129148
authenticated,
149+
registration,
130150
signup,
131151
signupGroup,
132152
t,
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import addDays from 'date-fns/addDays';
2+
import isBefore from 'date-fns/isBefore';
3+
import startOfDay from 'date-fns/startOfDay';
4+
5+
import { WEB_STORE_REFUND_DEADLINE_DAYS } from '../../envVariables';
6+
7+
export const isWithinRefundDeadline = (
8+
eventStartTime: string | null | undefined
9+
): boolean => {
10+
if (!eventStartTime) {
11+
return false;
12+
}
13+
14+
try {
15+
const eventStartDate = new Date(eventStartTime);
16+
const deadlineDate = addDays(
17+
eventStartDate,
18+
-WEB_STORE_REFUND_DEADLINE_DAYS
19+
);
20+
const nextDayAfterDeadline = startOfDay(addDays(deadlineDate, 1));
21+
22+
return isBefore(new Date(), nextDayAfterDeadline);
23+
} catch {
24+
return false;
25+
}
26+
};

src/envVariables.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,9 @@ export const ALLOWED_SUBSTITUTE_USER_DOMAINS =
1212
import.meta.env.REACT_APP_ALLOWED_SUBSTITUTE_USER_DOMAINS?.split(',') ?? [
1313
'hel.fi',
1414
];
15+
16+
/* istanbul ignore next */
17+
export const WEB_STORE_REFUND_DEADLINE_DAYS = parseInt(
18+
import.meta.env?.REACT_APP_WEB_STORE_REFUND_DEADLINE_DAYS ?? '7',
19+
10
20+
);

0 commit comments

Comments
 (0)