Skip to content

Commit f978a74

Browse files
committed
Fetch orders when searching bookings as well
1 parent 724247a commit f978a74

File tree

3 files changed

+129
-169
lines changed

3 files changed

+129
-169
lines changed

Modules/Sources/Networking/Model/Bookings/BookingOrderInfo.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,23 @@ public struct BookingOrderInfo: Hashable {
1616
self.customerInfo = customerInfo
1717
self.productInfo = productInfo
1818
}
19+
20+
public init(booking: Booking, order: Order) {
21+
self.customerInfo = {
22+
guard let billingAddress = order.billingAddress else {
23+
return nil
24+
}
25+
return BookingCustomerInfo(billingAddress: billingAddress)
26+
}()
27+
self.productInfo = BookingProductInfo(name: order.items.first(where: { $0.productID == booking.productID })?.name ?? "")
28+
self.paymentInfo = BookingPaymentInfo(
29+
paymentMethodID: order.paymentMethodID,
30+
paymentMethodTitle: order.paymentMethodTitle,
31+
subtotal: order.items.map({ Double($0.subtotal) ?? 0 }).reduce(0, +).description,
32+
subtotalTax: order.items.map({ Double($0.subtotalTax) ?? 0 }).reduce(0, +).description,
33+
total: order.total,
34+
totalTax: order.totalTax
35+
)
36+
self.statusKey = order.status.rawValue
37+
}
1938
}

Modules/Sources/Yosemite/Stores/BookingStore.swift

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,18 @@ private extension BookingStore {
197197
startDateAfter: startDateAfter,
198198
searchQuery: searchQuery,
199199
order: order)
200-
onCompletion(.success(bookings))
200+
let orders = try await ordersRemote.loadOrders(
201+
for: siteID,
202+
orderIDs: bookings.map { $0.orderID }
203+
)
204+
let updatedBookings = bookings.map { booking in
205+
guard let order = orders.first(where: { booking.orderID == $0.orderID }) else {
206+
return booking
207+
}
208+
let orderInfo = BookingOrderInfo(booking: booking, order: order)
209+
return booking.copy(orderInfo: orderInfo)
210+
}
211+
onCompletion(.success(updatedBookings))
201212
} catch {
202213
onCompletion(.failure(error))
203214
}
@@ -269,45 +280,30 @@ private extension BookingStore {
269280
storage.insertNewObject(ofType: StorageBooking.self)
270281

271282
if let associatedOrder = readOnlyOrders.first(where: { $0.orderID == readOnlyBooking.orderID }) {
283+
let readOnlyOrderInfo = BookingOrderInfo(booking: readOnlyBooking, order: associatedOrder)
284+
272285
let orderInfo = storageBooking.orderInfo ?? storage.insertNewObject(ofType: Storage.BookingOrderInfo.self)
273286

274287
let productInfo = orderInfo.productInfo ?? storage.insertNewObject(ofType: Storage.BookingProductInfo.self)
275-
let productName = associatedOrder.items.first(where: { $0.productID == readOnlyBooking.productID })?.name
276-
productInfo.update(with: .init(name: productName ?? ""))
288+
if let readOnlyProductInfo = readOnlyOrderInfo.productInfo {
289+
productInfo.update(with: readOnlyProductInfo)
290+
}
277291
orderInfo.productInfo = productInfo
278292

279-
if let billingAddress = associatedOrder.billingAddress {
280-
let customerInfo = orderInfo.customerInfo ?? storage.insertNewObject(ofType: Storage.BookingCustomerInfo.self)
281-
customerInfo.update(with: .init(billingAddress: billingAddress))
282-
orderInfo.customerInfo = customerInfo
293+
let customerInfo = orderInfo.customerInfo ?? storage.insertNewObject(ofType: Storage.BookingCustomerInfo.self)
294+
if let readOnlyCustomerInfo = readOnlyOrderInfo.customerInfo {
295+
customerInfo.update(with: readOnlyCustomerInfo)
283296
}
297+
orderInfo.customerInfo = customerInfo
284298

285299
let paymentInfo = orderInfo.paymentInfo ?? storage.insertNewObject(ofType: Storage.BookingPaymentInfo.self)
286-
paymentInfo.update(with:
287-
BookingPaymentInfo(
288-
paymentMethodID: associatedOrder.paymentMethodID,
289-
paymentMethodTitle: associatedOrder.paymentMethodTitle,
290-
subtotal: associatedOrder.items.map({ Double($0.subtotal) ?? 0 }).reduce(0, +).description,
291-
subtotalTax: associatedOrder.items.map({ Double($0.subtotalTax) ?? 0 }).reduce(0, +).description,
292-
total: associatedOrder.total,
293-
totalTax: associatedOrder.totalTax
294-
)
295-
)
296-
300+
if let readOnlyPaymentInfo = readOnlyOrderInfo.paymentInfo {
301+
paymentInfo.update(with: readOnlyPaymentInfo)
302+
}
297303
orderInfo.paymentInfo = paymentInfo
298304

299305
orderInfo.statusKey = associatedOrder.status.rawValue
300306
storageBooking.orderInfo = orderInfo
301-
} else if let product = storage.loadProduct(siteID: readOnlyBooking.siteID, productID: readOnlyBooking.productID) {
302-
/// Fallback when order info cannot be fetched:
303-
/// get cached product to fill in product info
304-
let productInfo = storage.insertNewObject(ofType: Storage.BookingProductInfo.self)
305-
productInfo.name = product.name
306-
307-
let orderInfo = storage.insertNewObject(ofType: Storage.BookingOrderInfo.self)
308-
orderInfo.statusKey = readOnlyBooking.statusKey
309-
orderInfo.productInfo = productInfo
310-
storageBooking.orderInfo = orderInfo
311307
}
312308

313309
storageBooking.update(with: readOnlyBooking)

Modules/Tests/YosemiteTests/Stores/BookingStoreTests.swift

Lines changed: 86 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -455,9 +455,14 @@ struct BookingStoreTests {
455455

456456
@Test func searchBookings_returns_bookings_on_success() async throws {
457457
// Given
458-
let booking1 = Booking.fake().copy(siteID: sampleSiteID, bookingID: 123)
459-
let booking2 = Booking.fake().copy(siteID: sampleSiteID, bookingID: 456)
458+
let booking1 = Booking.fake().copy(siteID: sampleSiteID, bookingID: 123, orderID: 1)
459+
let booking2 = Booking.fake().copy(siteID: sampleSiteID, bookingID: 456, orderID: 2)
460460
remote.whenLoadingAllBookings(thenReturn: .success([booking1, booking2]))
461+
462+
let order1 = Order.fake().copy(orderID: 1)
463+
let order2 = Order.fake().copy(orderID: 2)
464+
ordersRemote.whenLoadingOrders(thenReturn: .success([order1, order2]))
465+
461466
let store = BookingStore(dispatcher: Dispatcher(),
462467
storageManager: storageManager,
463468
network: network,
@@ -510,8 +515,12 @@ struct BookingStoreTests {
510515

511516
@Test func searchBookings_does_not_save_results_to_storage() async throws {
512517
// Given
513-
let booking = Booking.fake().copy(siteID: sampleSiteID, bookingID: 123)
518+
let booking = Booking.fake().copy(siteID: sampleSiteID, bookingID: 123, orderID: 1)
514519
remote.whenLoadingAllBookings(thenReturn: .success([booking]))
520+
521+
let order = Order.fake().copy(orderID: 1)
522+
ordersRemote.whenLoadingOrders(thenReturn: .success([order]))
523+
515524
let store = BookingStore(dispatcher: Dispatcher(),
516525
storageManager: storageManager,
517526
network: network,
@@ -535,6 +544,80 @@ struct BookingStoreTests {
535544
#expect(storedBookingCount == 0)
536545
}
537546

547+
@Test func searchBookings_fetches_orders_for_bookings() async throws {
548+
// Given
549+
let booking1 = Booking.fake().copy(siteID: sampleSiteID, bookingID: 123, orderID: 10)
550+
let booking2 = Booking.fake().copy(siteID: sampleSiteID, bookingID: 456, orderID: 20)
551+
remote.whenLoadingAllBookings(thenReturn: .success([booking1, booking2]))
552+
553+
let order1 = Order.fake().copy(orderID: 10)
554+
let order2 = Order.fake().copy(orderID: 20)
555+
ordersRemote.whenLoadingOrders(thenReturn: .success([order1, order2]))
556+
557+
let store = BookingStore(dispatcher: Dispatcher(),
558+
storageManager: storageManager,
559+
network: network,
560+
remote: remote,
561+
ordersRemote: ordersRemote)
562+
563+
// When
564+
let result = await withCheckedContinuation { continuation in
565+
store.onAction(BookingAction.searchBookings(siteID: sampleSiteID,
566+
searchQuery: "test",
567+
pageNumber: defaultPageNumber,
568+
pageSize: defaultPageSize,
569+
onCompletion: { result in
570+
continuation.resume(returning: result)
571+
}))
572+
}
573+
574+
// Then
575+
#expect(result.isSuccess)
576+
#expect(ordersRemote.invokedLoadOrders)
577+
#expect(ordersRemote.invokedLoadOrdersParameters?.orderIDs == [10, 20])
578+
}
579+
580+
@Test func searchBookings_returns_bookings_with_orderInfo_when_orders_are_available() async throws {
581+
// Given
582+
let productID: Int64 = 100
583+
let booking = Booking.fake().copy(
584+
siteID: sampleSiteID,
585+
bookingID: 123,
586+
orderID: 1,
587+
productID: productID
588+
)
589+
remote.whenLoadingAllBookings(thenReturn: .success([booking]))
590+
591+
let orderItem = OrderItem.fake().copy(itemID: 1, name: "Test Product", productID: productID)
592+
let order = Order.fake().copy(orderID: 1, status: .processing, items: [orderItem])
593+
ordersRemote.whenLoadingOrders(thenReturn: .success([order]))
594+
595+
let store = BookingStore(dispatcher: Dispatcher(),
596+
storageManager: storageManager,
597+
network: network,
598+
remote: remote,
599+
ordersRemote: ordersRemote)
600+
601+
// When
602+
let result = await withCheckedContinuation { continuation in
603+
store.onAction(BookingAction.searchBookings(siteID: sampleSiteID,
604+
searchQuery: "test",
605+
pageNumber: defaultPageNumber,
606+
pageSize: defaultPageSize,
607+
onCompletion: { result in
608+
continuation.resume(returning: result)
609+
}))
610+
}
611+
612+
// Then
613+
let bookings = try result.get()
614+
#expect(bookings.count == 1)
615+
let returnedBooking = try #require(bookings.first)
616+
let orderInfo = try #require(returnedBooking.orderInfo)
617+
#expect(orderInfo.productInfo?.name == "Test Product")
618+
#expect(orderInfo.statusKey == "processing")
619+
}
620+
538621
// MARK: - orderInfo Storage Tests
539622

540623
@Test func synchronizeBookings_stores_complete_orderInfo_with_all_nested_properties() async throws {
@@ -663,144 +746,6 @@ struct BookingStoreTests {
663746
#expect(customerInfo.billingFirstName == "Test")
664747
#expect(customerInfo.billingLastName == "User")
665748
}
666-
667-
@Test func synchronizeBookings_falls_back_to_cached_product_when_order_cannot_be_fetched() async throws {
668-
// Given
669-
let productID: Int64 = 200
670-
let booking = Booking.fake().copy(
671-
siteID: sampleSiteID,
672-
bookingID: 999,
673-
orderID: 0, // No valid order
674-
productID: productID,
675-
statusKey: "pending"
676-
)
677-
remote.whenLoadingAllBookings(thenReturn: .success([booking]))
678-
679-
// Store a cached product in storage
680-
let product = Product.fake().copy(siteID: sampleSiteID, productID: productID, name: "Cached Product")
681-
storeProduct(product)
682-
683-
// Orders remote returns empty (order not found)
684-
ordersRemote.whenLoadingOrders(thenReturn: .success([]))
685-
686-
let store = BookingStore(dispatcher: Dispatcher(),
687-
storageManager: storageManager,
688-
network: network,
689-
remote: remote,
690-
ordersRemote: ordersRemote)
691-
692-
// When
693-
let result = await withCheckedContinuation { continuation in
694-
store.onAction(BookingAction.synchronizeBookings(siteID: sampleSiteID,
695-
pageNumber: defaultPageNumber,
696-
pageSize: defaultPageSize,
697-
onCompletion: { result in
698-
continuation.resume(returning: result)
699-
}))
700-
}
701-
702-
// Then
703-
#expect(result.isSuccess)
704-
let storedBooking = try #require(viewStorage.loadBooking(siteID: sampleSiteID, bookingID: 999))
705-
let orderInfo = try #require(storedBooking.orderInfo)
706-
707-
// Verify orderInfo has product info from cached product
708-
let productInfo = try #require(orderInfo.productInfo)
709-
#expect(productInfo.name == "Cached Product")
710-
711-
// Verify status is from booking
712-
#expect(orderInfo.statusKey == "pending")
713-
714-
// Verify customer and payment info are not present
715-
#expect(orderInfo.customerInfo == nil)
716-
#expect(orderInfo.paymentInfo == nil)
717-
}
718-
719-
@Test func synchronizeBooking_falls_back_to_cached_product_when_order_cannot_be_fetched() async throws {
720-
// Given
721-
let productID: Int64 = 300
722-
let booking = Booking.fake().copy(
723-
siteID: sampleSiteID,
724-
bookingID: 888,
725-
orderID: 0,
726-
productID: productID,
727-
statusKey: "confirmed"
728-
)
729-
remote.whenLoadingBooking(thenReturn: .success(booking))
730-
731-
// Store a cached product
732-
let product = Product.fake().copy(siteID: sampleSiteID, productID: productID, name: "Spa Treatment")
733-
storeProduct(product)
734-
735-
// Orders remote returns empty
736-
ordersRemote.whenLoadingOrders(thenReturn: .success([]))
737-
738-
let store = BookingStore(dispatcher: Dispatcher(),
739-
storageManager: storageManager,
740-
network: network,
741-
remote: remote,
742-
ordersRemote: ordersRemote)
743-
744-
// When
745-
let result = await withCheckedContinuation { continuation in
746-
store.onAction(
747-
BookingAction.synchronizeBooking(
748-
siteID: sampleSiteID,
749-
bookingID: 888
750-
) { result in
751-
continuation.resume(returning: result)
752-
}
753-
)
754-
}
755-
756-
// Then
757-
#expect(result.isSuccess)
758-
let storedBooking = try #require(viewStorage.loadBooking(siteID: sampleSiteID, bookingID: 888))
759-
let orderInfo = try #require(storedBooking.orderInfo)
760-
761-
// Verify fallback behavior
762-
let productInfo = try #require(orderInfo.productInfo)
763-
#expect(productInfo.name == "Spa Treatment")
764-
#expect(orderInfo.statusKey == "confirmed")
765-
#expect(orderInfo.customerInfo == nil)
766-
#expect(orderInfo.paymentInfo == nil)
767-
}
768-
769-
@Test func synchronizeBookings_has_no_orderInfo_when_order_and_product_not_found() async throws {
770-
// Given
771-
let booking = Booking.fake().copy(
772-
siteID: sampleSiteID,
773-
bookingID: 777,
774-
orderID: 0,
775-
productID: 999, // Product doesn't exist in cache
776-
statusKey: "pending"
777-
)
778-
remote.whenLoadingAllBookings(thenReturn: .success([booking]))
779-
ordersRemote.whenLoadingOrders(thenReturn: .success([]))
780-
781-
let store = BookingStore(dispatcher: Dispatcher(),
782-
storageManager: storageManager,
783-
network: network,
784-
remote: remote,
785-
ordersRemote: ordersRemote)
786-
787-
// When
788-
let result = await withCheckedContinuation { continuation in
789-
store.onAction(BookingAction.synchronizeBookings(siteID: sampleSiteID,
790-
pageNumber: defaultPageNumber,
791-
pageSize: defaultPageSize,
792-
onCompletion: { result in
793-
continuation.resume(returning: result)
794-
}))
795-
}
796-
797-
// Then
798-
#expect(result.isSuccess)
799-
let storedBooking = try #require(viewStorage.loadBooking(siteID: sampleSiteID, bookingID: 777))
800-
801-
// Verify no orderInfo is created when neither order nor product is available
802-
#expect(storedBooking.orderInfo == nil)
803-
}
804749
}
805750

806751
private extension BookingStoreTests {

0 commit comments

Comments
 (0)