Skip to content

Commit c77afcd

Browse files
authored
Bookings: Update booking list item and booking detail screen with real data (#16247)
2 parents 4194f0f + 6ace903 commit c77afcd

File tree

14 files changed

+402
-247
lines changed

14 files changed

+402
-247
lines changed

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ public struct Booking: Codable, GeneratedCopiable, Hashable, GeneratedFakeable {
2222
public let statusKey: String
2323
public let localTimezone: String
2424
public let currency: String
25-
// periphery: ignore - to be used later
2625
public let orderInfo: BookingOrderInfo?
2726

2827
public var bookingStatus: BookingStatus {

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/Model/Booking/Booking+ReadOnlyConvertible.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,15 @@ extension Storage.Booking: ReadOnlyConvertible {
5252
orderInfo: orderInfo?.toReadOnly())
5353
}
5454
}
55+
56+
extension Yosemite.Booking: ReadOnlyType {
57+
/// Indicates if the receiver is a representation of a specified Storage.Entity instance.
58+
///
59+
public func isReadOnlyRepresentation(of storageEntity: Any) -> Bool {
60+
guard let storageBooking = storageEntity as? Storage.Booking else {
61+
return false
62+
}
63+
64+
return siteID == Int(storageBooking.siteID) && bookingID == Int(storageBooking.bookingID)
65+
}
66+
}

Modules/Sources/Yosemite/Stores/BookingStore.swift

Lines changed: 24 additions & 18 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,31 +280,26 @@ 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

Modules/Tests/YosemiteTests/Stores/BookingStoreTests.swift

Lines changed: 93 additions & 3 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 {
@@ -672,6 +755,13 @@ private extension BookingStoreTests {
672755
storedBooking.update(with: booking)
673756
return storedBooking
674757
}
758+
759+
@discardableResult
760+
func storeProduct(_ product: Networking.Product) -> Storage.Product {
761+
let storedProduct = storage.insertNewObject(ofType: Storage.Product.self)
762+
storedProduct.update(with: product)
763+
return storedProduct
764+
}
675765
}
676766

677767
private class MockOrdersRemote: OrdersRemoteProtocol {

WooCommerce/Classes/Bookings/BookingList/BookingListView.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,7 @@ private extension BookingListView {
128128
.frame(maxWidth: .infinity, alignment: .leading)
129129
.foregroundStyle(Color.primary)
130130

131-
// TODO: fetch bookable products & customer to get names or wait for API update
132-
Text(String(format: "%@ • %@", "Women's Hair cut", "Marianne"))
131+
Text(booking.summaryText)
133132
.font(.footnote)
134133
.fontWeight(.medium)
135134
.foregroundStyle(Color.secondary)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import Foundation
2+
import struct Yosemite.Booking
3+
4+
extension Booking {
5+
6+
var summaryText: String {
7+
let productName = orderInfo?.productInfo?.name
8+
let customerName: String = {
9+
guard let name = orderInfo?.customerInfo?.billingAddress.fullName else {
10+
return Localization.guest
11+
}
12+
return name.isEmpty ? Localization.guest : name
13+
}()
14+
return [productName, customerName]
15+
.compactMap { $0 }
16+
.joined(separator: "")
17+
}
18+
19+
private enum Localization {
20+
static let guest = NSLocalizedString(
21+
"bookings.guest",
22+
value: "Guest",
23+
comment: "Displayed name on the booking list when no customer is associated with a booking."
24+
)
25+
}
26+
}

0 commit comments

Comments
 (0)