-
Notifications
You must be signed in to change notification settings - Fork 2
Staff fix bug #41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Staff fix bug #41
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,6 +33,7 @@ import { | |
| export interface StaffBookingRecord { | ||
| readonly bookingId: string; | ||
| readonly renterId?: string; | ||
| readonly renterUserId?: string; | ||
| readonly status?: BookingStatus; | ||
| readonly verificationStatus?: BookingVerificationStatus; | ||
| readonly bookingCreatedAt?: string; | ||
|
|
@@ -183,67 +184,82 @@ export class BookingsService { | |
|
|
||
| const bookingIdentifier = this._normalizeString(booking.bookingId) ?? bookingId; | ||
| const renterId = this._normalizeString(booking.renterId); | ||
| const userIdFromBooking = this._extractUserId(booking); | ||
|
|
||
| const renter$ = renterId | ||
| ? this._bookingService.apiBookingRenterProfileGet(renterId).pipe( | ||
| map((response: RenterProfileDtoApiResponse) => response.data ?? undefined), | ||
| catchError(() => of(undefined)), | ||
| ) | ||
| : of(undefined); | ||
|
|
||
| const rental$ = renterId | ||
| ? this._rentalService.apiRentalByRenterGet(renterId).pipe( | ||
| map((response) => response.data?.items ?? []), | ||
| map((items) => | ||
| items.find((item) => this._normalizeString(item.bookingId) === bookingIdentifier), | ||
| ), | ||
| switchMap((match) => { | ||
| const rentalId = this._normalizeString(match?.rentalId); | ||
| if (!rentalId) { | ||
| return of(undefined); | ||
| } | ||
|
|
||
| return this._rentalService.apiRentalRentalIdGet(rentalId).pipe( | ||
| map((response) => response.data ?? undefined), | ||
| const userId$ = userIdFromBooking | ||
| ? of(userIdFromBooking) | ||
| : this._resolveRenterUserId$(renterId); | ||
|
|
||
| return userId$.pipe( | ||
| switchMap((resolvedUserId) => { | ||
| const renterLookupId = resolvedUserId ?? renterId; | ||
|
|
||
| const renter$ = renterLookupId | ||
| ? this._bookingService.apiBookingRenterProfileGet(renterLookupId).pipe( | ||
| map((response: RenterProfileDtoApiResponse) => response.data ?? undefined), | ||
| catchError(() => of(undefined)), | ||
| ); | ||
| }), | ||
| catchError(() => of(undefined)), | ||
| ) | ||
| : of(undefined); | ||
|
|
||
| return forkJoin({ renter: renter$, rental: rental$ }).pipe( | ||
| switchMap(({ renter, rental }) => { | ||
| const vehicleId = this._normalizeString( | ||
| rental?.vehicle?.vehicleId ?? rental?.vehicleId, | ||
| ); | ||
| const vehicle$ = vehicleId | ||
| ? this._bookingService.apiBookingVehiclesVehicleIdGet(vehicleId).pipe( | ||
| map((response: VehicleDetailsDtoApiResponse) => response.data ?? undefined), | ||
| ) | ||
| : of(undefined); | ||
|
|
||
| const rental$ = renterId | ||
| ? this._rentalService.apiRentalByRenterGet(renterId).pipe( | ||
| map((response) => response.data?.items ?? []), | ||
| map((items) => | ||
| items.find( | ||
| (item) => this._normalizeString(item.bookingId) === bookingIdentifier, | ||
| ), | ||
| ), | ||
| switchMap((match) => { | ||
| const rentalId = this._normalizeString(match?.rentalId); | ||
| if (!rentalId) { | ||
| return of(undefined); | ||
| } | ||
|
|
||
| return this._rentalService.apiRentalRentalIdGet(rentalId).pipe( | ||
| map((response) => response.data ?? undefined), | ||
| catchError(() => of(undefined)), | ||
| ); | ||
| }), | ||
| catchError(() => of(undefined)), | ||
| ) | ||
| : of(undefined); | ||
|
|
||
| return vehicle$.pipe( | ||
| map( | ||
| (vehicle) => | ||
| ({ | ||
| bookingId: bookingIdentifier, | ||
| renterId, | ||
| status: booking.status, | ||
| verificationStatus: booking.verificationStatus, | ||
| bookingCreatedAt: this._normalizeString(booking.bookingCreatedAt), | ||
| startTime: this._normalizeString(booking.startTime), | ||
| endTime: this._normalizeString(booking.endTime), | ||
| vehicleAtStationId: this._normalizeString(booking.vehicleAtStationId), | ||
| verifiedAt: this._normalizeString(booking.verifiedAt), | ||
| verifiedByStaffId: this._normalizeString(booking.verifiedByStaffId), | ||
| cancelReason: this._normalizeString(booking.cancelReason), | ||
| renterProfile: renter, | ||
| rental, | ||
| vehicleDetails: vehicle, | ||
| }) satisfies StaffBookingRecord, | ||
| ), | ||
| return forkJoin({ renter: renter$, rental: rental$ }).pipe( | ||
| switchMap(({ renter, rental }) => { | ||
| const vehicleId = this._normalizeString( | ||
| rental?.vehicle?.vehicleId ?? rental?.vehicleId, | ||
| ); | ||
| const vehicle$ = vehicleId | ||
| ? this._bookingService.apiBookingVehiclesVehicleIdGet(vehicleId).pipe( | ||
| map((response: VehicleDetailsDtoApiResponse) => response.data ?? undefined), | ||
| catchError(() => of(undefined)), | ||
| ) | ||
| : of(undefined); | ||
|
|
||
| return vehicle$.pipe( | ||
| map( | ||
| (vehicle) => | ||
| ({ | ||
| bookingId: bookingIdentifier, | ||
| renterId, | ||
| renterUserId: | ||
| resolvedUserId ?? userIdFromBooking ?? this._extractUserId(renter), | ||
| status: booking.status, | ||
| verificationStatus: booking.verificationStatus, | ||
| bookingCreatedAt: this._normalizeString(booking.bookingCreatedAt), | ||
| startTime: this._normalizeString(booking.startTime), | ||
| endTime: this._normalizeString(booking.endTime), | ||
| vehicleAtStationId: this._normalizeString(booking.vehicleAtStationId), | ||
| verifiedAt: this._normalizeString(booking.verifiedAt), | ||
| verifiedByStaffId: this._normalizeString(booking.verifiedByStaffId), | ||
| cancelReason: this._normalizeString(booking.cancelReason), | ||
| renterProfile: renter, | ||
| rental, | ||
| vehicleDetails: vehicle, | ||
| }) satisfies StaffBookingRecord, | ||
| ), | ||
| ); | ||
| }), | ||
| ); | ||
| }), | ||
| ); | ||
|
|
@@ -343,11 +359,13 @@ export class BookingsService { | |
| const bookingId = booking.bookingId; | ||
| const rental = rentalByBookingId.get(bookingId); | ||
| const renterProfile = booking.renterId ? renterById.get(booking.renterId) : undefined; | ||
| const renterUserId = this._extractUserId(booking) ?? this._extractUserId(renterProfile); | ||
| const vehicleDetails = this._resolveVehicleDetails(rental, vehicleDetailsMap); | ||
|
|
||
| records.push({ | ||
| bookingId, | ||
| renterId: this._normalizeString(booking.renterId), | ||
| renterUserId, | ||
| status: booking.status, | ||
| verificationStatus: booking.verificationStatus, | ||
| bookingCreatedAt: this._normalizeString(booking.bookingCreatedAt), | ||
|
|
@@ -553,6 +571,44 @@ export class BookingsService { | |
| return trimmed.length > 0 ? trimmed : undefined; | ||
| } | ||
|
|
||
| private _extractUserId(source: unknown): string | undefined { | ||
| if (!source || typeof source !== 'object') { | ||
| return undefined; | ||
| } | ||
|
|
||
| const record = source as Record<string, unknown>; | ||
| const candidateKeys: readonly string[] = ['userId', 'userID', 'identityId', 'accountId']; | ||
|
|
||
| for (const key of candidateKeys) { | ||
| const candidate = record[key]; | ||
| if (typeof candidate !== 'string') { | ||
| continue; | ||
| } | ||
|
|
||
| const normalized = this._normalizeString(candidate); | ||
| if (normalized) { | ||
| return normalized; | ||
| } | ||
| } | ||
|
|
||
| return undefined; | ||
| } | ||
|
|
||
| private _resolveRenterUserId$(renterId: string | undefined): Observable<string | undefined> { | ||
| if (!renterId) { | ||
| return of(undefined); | ||
| } | ||
|
|
||
| return this._staffService.apiStaffRentersGet().pipe( | ||
| map((response: RenterProfileDtoListApiResponse) => response.data ?? []), | ||
| map((renters) => | ||
| renters.find((renter) => this._normalizeString(renter?.renterId) === renterId), | ||
| ), | ||
| map((match) => this._extractUserId(match)), | ||
| catchError(() => of(undefined)), | ||
| ); | ||
|
Comment on lines
+602
to
+609
|
||
| } | ||
|
|
||
| private _compareByDateDesc(first?: string, second?: string): number { | ||
| const firstTime = first ? Date.parse(first) : Number.NaN; | ||
| const secondTime = second ? Date.parse(second) : Number.NaN; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Module-level mutable state (refreshInFlight$) creates a shared singleton that persists across multiple injector contexts and can cause race conditions or stale state. Consider moving this into a service with proper lifecycle management or using a BehaviorSubject to ensure proper cleanup and testability.