feat(bookings): support casuals when computing remaining bookings, allowing for casual bookings#966
feat(bookings): support casuals when computing remaining bookings, allowing for casual bookings#966choden-dev wants to merge 9 commits intomasterfrom
Conversation
Preview Deployment Status✅ Storybook Preview: https://0989b559.uabc-storybook.pages.dev ✅ Frontend Preview: https://ffff6f41.uabc.pages.dev |
…ainingSessions prop
b13ce69 to
673f8fc
Compare
Coverage Report
File Coverage |
|
@choden-dev Thank you for the PR! A couple things I'd like to clarify up that I should have left in the original user story!
This also meant any db writes would result in a correction of membership status (leaves less work to designate a db request updating it). We want to keep this As for the warning block,
The admins initially wanted and quite firm about a -1 remaining sessions system (as this closely aligned with how their flow worked best for a pay as you go system although this was not followed in the frontend). We would want to keep casuals at 0 sessions although visually display a slightly different system, so that they know they are not under the booking credit system (displaying 1 would perhaps be confusing). TL;DR; The only difference being members use their credits to pay, while casuals are allocated MAX 1 session per week dependent on an existing booking within the week. Clarifications on the out of scope, I am aware of the first bug and is a P1, I've created a ticket (#967), thank you for reminding me! |
jji05
left a comment
There was a problem hiding this comment.
Great work, a couple comments left! Thank you 👍
| const remainingSessionsBasedOnRole = await getRemainingSessions( | ||
| userData, | ||
| semesterDataService, | ||
| bookingDataService, | ||
| userDataService, | ||
| ) | ||
|
|
||
| if (remainingSessionsBasedOnRole <= 0) | ||
| return NextResponse.json( | ||
| { error: "No remaining sessions" }, |
There was a problem hiding this comment.
nit: I like the logic here although it seems to be overlapping with standard member logic and could be refined down to calling getAllCurrentWeekBookingsByUserId once for both casuals and regulars. Note that this logic is used again in lines:
uabc-web/apps/backend/src/app/api/bookings/route.ts
Lines 89 to 99 in 7761177
There was a problem hiding this comment.
For casuals this would be the correct message for their role?
There was a problem hiding this comment.
Both of them are correct, in this case, it would be ideal to simplify the logic as the same logic is being checked twice
There was a problem hiding this comment.
I agree from an efficiency point of view, but realistically you should not know that the implementation of getRemainingSessions does the check, so they are actually doing different things from a black box perspective (weekly bookings vs getting remaining sessions)
There was a problem hiding this comment.
In that case, I'm happy with your implementation
| @@ -284,12 +310,16 @@ describe("/api/bookings", async () => { | |||
| const res = await POST(req) | |||
|
|
|||
| expect(res.status).toBe(StatusCodes.FORBIDDEN) | |||
| expect(await res.json()).toStrictEqual({ error: "Session is full for the selected user role" }) | |||
| expect(await res.json()).toStrictEqual({ error: "No remaining sessions" }) | |||
| }) | |||
There was a problem hiding this comment.
based on the test description, it should be returning "Session is full for the selected user role". since the logic was changed to:
check remaining sessions (for casual user mock) -> check game session capacities
we need to change the description and add a test to test for the initial test topic
There was a problem hiding this comment.
For casuals this would be the correct message for their role?
There was a problem hiding this comment.
No, the error is only because booking create mock is using casual user and that causes it to show no remaining sessions. The point of the test was to test for casual capacities
| @@ -162,7 +167,7 @@ describe("/api/bookings", async () => { | |||
|
|
|||
| const res = await POST(req) | |||
| expect(res.status).toBe(StatusCodes.FORBIDDEN) | |||
| expect(await res.json()).toStrictEqual({ error: "Weekly booking limit reached" }) | |||
| expect(await res.json()).toStrictEqual({ error: "No remaining sessions" }) | |||
| }) | |||
There was a problem hiding this comment.
on second thought, I'd prefer the error message be "Weekly booking limit reached" as it makes more sense for casuals to see a weekly limit reached rather than no remaining sessions (they never have remaining sessions as 1 remaining session indicates "member", but if casuals are displayed "1" too which makes the role system confusing), this is open for discussion.
the proposed logic change would involve only checking remainingSessions for members (essentially casuals bypass the initial remainingSessionsBasedOnRole check and only just check existing weekly bookings over here:
uabc-web/apps/backend/src/app/api/bookings/route.ts
Lines 88 to 99 in 7761177
There was a problem hiding this comment.
I disagree, having a role based on another value is confusing and users do not need to know that they become a casual if they have 0 sessions, rather it should be enough to specify that their sessions only apply to the casual slots.
There was a problem hiding this comment.
Users need to know that they become casual when they have 0 sessions, this was specified by client as they have a very different booking criteria as far as I am aware.
There was a problem hiding this comment.
Isn't it quite easy to show the user they are casual without linking it to the sessions? It's important from a business logic perspective but from a UX perspective they only need to know if they are casual/member and the implications of such.
There was a problem hiding this comment.
It is important business logic as now we display 1 session and if a user tops up, thinking they'd get 3 sessions would be wrong! Hence adding a tooltip would clarify it in a business perspective
There was a problem hiding this comment.
I thought this comment you made addresses that? i.e it shouldn't be a backend thing
| const json = await response.json() | ||
| expect(typeof json.data.remainingSessions).toBe("number") | ||
| }) |
There was a problem hiding this comment.
this doesn't necessarily make the test correct, please check if the remainingSessions is equal to memberUserMock.remainingSessions
| describe("getRemainingSessions", () => { | ||
| let mockSemesterDataService: { | ||
| getCurrentSemester: Mock | ||
| } | ||
| let mockBookingDataService: { | ||
| getAllCurrentWeekBookingsByUserId: Mock | ||
| } | ||
| let mockUserDataService: { | ||
| getUserById: Mock | ||
| } | ||
|
|
||
| beforeEach(() => { | ||
| mockSemesterDataService = { | ||
| getCurrentSemester: vi.fn(), | ||
| } | ||
| mockBookingDataService = { | ||
| getAllCurrentWeekBookingsByUserId: vi.fn(), | ||
| } | ||
| mockUserDataService = { | ||
| getUserById: vi.fn(), | ||
| } | ||
| }) | ||
|
|
||
| it("should return 1 for casual user with no existing bookings", async () => { | ||
| const user = { id: casualUserMock.id, role: MembershipType.casual, remainingSessions: null } | ||
| mockSemesterDataService.getCurrentSemester.mockResolvedValue({ id: "semester-1" }) | ||
| mockBookingDataService.getAllCurrentWeekBookingsByUserId.mockResolvedValue([]) |
There was a problem hiding this comment.
tests we write are mocked as little as possible to avoid mocks being inaccurate from actual expected functionality. I would recommend using actual data services to run tests
| numberBookedSessions?: number | ||
|
|
||
| /** | ||
| * The number of remaining sessions for the user. | ||
| */ | ||
| remainingSessions: number |
There was a problem hiding this comment.
nit: extra space and more descriptive description
| numberBookedSessions?: number | |
| /** | |
| * The number of remaining sessions for the user. | |
| */ | |
| remainingSessions: number | |
| numberBookedSessions?: number | |
| /** | |
| * The number of remaining sessions for the user. | |
| * @remarks This is used instead of `auth` as this provides a more accurate session count | |
| * for casuals by checking their exisiting bookings within the week. | |
| */ | |
| remainingSessions: number |
| @@ -63,7 +65,16 @@ export const ProfileSection = memo(({ auth }: { auth: AuthContextValue }) => { | |||
| return ( | |||
| <Container centerContent gap="xl" layerStyle="container"> | |||
| <Grid gap="xl" templateColumns={{ base: "1fr", lg: "1fr 1.5fr" }} w="full"> | |||
| <GridItem>{!user ? <UserPanelSkeleton /> : <UserPanel user={user} />}</GridItem> | |||
| <GridItem> | |||
| {isRemainingSessionsLoading || !user ? ( | |||
| <UserPanelSkeleton /> | |||
| ) : ( | |||
| <UserPanel | |||
| remainingSessions={remainingSessionsResponse?.data.remainingSessions ?? 0} | |||
| user={user} | |||
| /> | |||
| )} | |||
| </GridItem> | |||
There was a problem hiding this comment.
would be ideal to not display 1 for casuals as a member could also have 1 and it isn't clear enough of a distinction!
you can leave it as is if you add the Tooltip component over an InfoIcon for casuals so they are aware it is a weekly thing
Description
Fixes #862
Main changes
remainingSessionswithGET /me/bookings/remainingapps/backend/src/data-layer/utils/GameSessionUtils.ts)Background
The computation for$0$ for casuals. There are no references to appropriately update the
totalSessionsLeftshows thatremainingSessionsis alwaysremainingSessionsfield, except for in theresetAllMembershipsmethod. This implies that the main issue beyond a lack of UX for users with 0 bookings is that casuals must have their remaining sessions manually set before.Warning
The callback at
validateUserRemaningSessionsmakes the source of truth for the user roleremainingSessions, meaning we have two options here:remainingSession)a. This might be the only option seeing as sessions need to be manually added to users, and the casuals require a different process (pay if accepted)
a. This would require a rework of the user roles given the tight coupling between
remainingSessionand roleThe best option here would be to stop using
user.remainingSessionsas the source of truth for computing the remaining sessions in bothBookFlowanduseBookingLimitsand rather use a strategy-based approach for the different user roles.Type of change
How Has This Been Tested?
Booking as casual user
Booking as member/admin
Note
This is the same behaviour as before
Booking Process
After booking
Bookings go from 5 --> 4
Not in scope
countAttendeesthe capacity is determined by counting user roles, whilevalidateUserRemaningSessionsensures that those with 0 bookings are casual, therefore some members will be wrongly counted as casual.bookingsstating what role the user made the booking as, such asmemberorcasualChecklist before requesting a review