Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions Modules/Sources/Networking/Remote/BookingsRemote.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ public protocol BookingsRemoteProtocol {
from siteID: Int64,
bookingID: Int64,
attendanceStatus: BookingAttendanceStatus?,
bookingStatus: BookingStatus?
bookingStatus: BookingStatus?,
note: String?
) async throws -> Booking?

func fetchResource(resourceID: Int64,
Expand Down Expand Up @@ -152,7 +153,8 @@ public final class BookingsRemote: Remote, BookingsRemoteProtocol {
from siteID: Int64,
bookingID: Int64,
attendanceStatus: BookingAttendanceStatus?,
bookingStatus: BookingStatus?
bookingStatus: BookingStatus?,
note: String?
) async throws -> Booking? {
let path = "\(Path.bookings)/\(bookingID)"
var parameters: [String: String] = [:]
Expand All @@ -165,6 +167,10 @@ public final class BookingsRemote: Remote, BookingsRemoteProtocol {
parameters[ParameterKey.status] = bookingStatus.rawValue
}

if let note {
parameters[ParameterKey.note] = note
}

let request = JetpackRequest(
wooApiVersion: .wcBookings,
method: .put,
Expand Down Expand Up @@ -259,5 +265,6 @@ public extension BookingsRemote {
static let attendanceStatus = "attendance_status"
static let paymentStatus = "booking_status" // to be updated later when payment filtering is supported
static let status: String = "status"
static let note: String = "note"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,6 @@ public enum WooAnalyticsStat: String {
case interacRefundCanceled = "interac_refund_cancelled"

// MARK: Push Notifications Events
//
case pushNotificationReceived = "push_notification_received"
case pushNotificationAlertPressed = "push_notification_alert_pressed"
case pushNotificationOSAlertAllowed = "push_notification_os_alert_allowed"
Expand Down
12 changes: 12 additions & 0 deletions Modules/Sources/Yosemite/Actions/BookingAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,16 @@ public enum BookingAction: Action {
case markBookingAsPaid(siteID: Int64,
bookingID: Int64,
onCompletion: (Error?) -> Void)

/// Updates a booking note.
///
/// - Parameter siteID: The site ID of the booking.
/// - Parameter bookingID: The ID of the booking to be updated.
/// - Parameter note: The new note.
/// - Parameter onCompletion: called when update completes, returns an error in case of a failure.
///
case updateBookingNote(siteID: Int64,
bookingID: Int64,
note: String,
onCompletion: (Error?) -> Void)
}
45 changes: 43 additions & 2 deletions Modules/Sources/Yosemite/Stores/BookingStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ public class BookingStore: Store {
bookingID: bookingID,
onCompletion: onCompletion
)
case .updateBookingNote(let siteID, let bookingID, let note, let onCompletion):
updateBookingNote(
siteID: siteID,
bookingID: bookingID,
note: note,
onCompletion: onCompletion
)
}
}
}
Expand Down Expand Up @@ -308,6 +315,7 @@ private extension BookingStore {
bookingID: bookingID,
attendanceStatus: status,
bookingStatus: nil,
note: nil,
) {
await self.upsertStoredBookingsInBackground(
readOnlyBookings: [remoteBooking],
Expand All @@ -333,6 +341,37 @@ private extension BookingStore {
}
}

func updateBookingNote(
siteID: Int64,
bookingID: Int64,
note: String,
onCompletion: @escaping (Error?) -> Void
) {
Task { @MainActor in
do {
if let remoteBooking = try await self.remote.updateBooking(
from: siteID,
bookingID: bookingID,
attendanceStatus: nil,
bookingStatus: nil,
note: note,
) {
await self.upsertStoredBookingsInBackground(
readOnlyBookings: [remoteBooking],
readOnlyOrders: [],
siteID: siteID
)

onCompletion(nil)
} else {
return onCompletion(UpdateBookingStatusError.missingRemoteBooking)
}
} catch {
return onCompletion(error)
}
}
}

/// Updates local (Storage) Booking attendance status
func updateBookingAttendanceStatusLocally(
siteID: Int64,
Expand Down Expand Up @@ -373,7 +412,8 @@ private extension BookingStore {
from: siteID,
bookingID: bookingID,
attendanceStatus: nil,
bookingStatus: .cancelled
bookingStatus: .cancelled,
note: nil,
) {
await upsertStoredBookingsInBackground(
readOnlyBookings: [remoteBooking],
Expand Down Expand Up @@ -403,7 +443,8 @@ private extension BookingStore {
from: siteID,
bookingID: bookingID,
attendanceStatus: nil,
bookingStatus: .paid
bookingStatus: .paid,
note: nil,
) {
await upsertStoredBookingsInBackground(
readOnlyBookings: [remoteBooking],
Expand Down
36 changes: 32 additions & 4 deletions Modules/Tests/NetworkingTests/Remote/BookingsRemoteTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ struct BookingsRemoteTests {
from: sampleSiteID,
bookingID: bookingID,
attendanceStatus: .noShow,
bookingStatus: nil
bookingStatus: nil,
note: nil
)

// Then
Expand All @@ -157,7 +158,8 @@ struct BookingsRemoteTests {
from: sampleSiteID,
bookingID: bookingID,
attendanceStatus: .noShow,
bookingStatus: nil
bookingStatus: nil,
note: nil
)

// Then
Expand All @@ -179,7 +181,8 @@ struct BookingsRemoteTests {
from: sampleSiteID,
bookingID: bookingID,
attendanceStatus: nil,
bookingStatus: .confirmed
bookingStatus: .confirmed,
note: nil
)

// Then
Expand All @@ -201,7 +204,8 @@ struct BookingsRemoteTests {
from: sampleSiteID,
bookingID: bookingID,
attendanceStatus: .booked,
bookingStatus: .paid
bookingStatus: .paid,
note: nil
)

// Then
Expand Down Expand Up @@ -254,4 +258,28 @@ struct BookingsRemoteTests {
#expect((parameters["page"] as? String) == "3")
#expect((parameters["per_page"] as? String) == "100")
}

@Test func test_updateBookingNote_sends_correct_parameters_for_booking_note() async throws {
// Given
let remote = BookingsRemote(network: network)
let bookingID: Int64 = 206
network.simulateResponse(requestUrlSuffix: "bookings/\(bookingID)", filename: "booking-no-create-update-dates")

// When
_ = try await remote.updateBooking(
from: sampleSiteID,
bookingID: bookingID,
attendanceStatus: nil,
bookingStatus: nil,
note: "hello"
)

// Then
let request = try #require(network.requestsForResponseData.first as? JetpackRequest)
let parameters = request.parameters

#expect(parameters["attendance_status"] == nil)
#expect(parameters["status"] == nil)
#expect((parameters["note"] as? String) == "hello")
}
}
4 changes: 3 additions & 1 deletion Modules/Tests/YosemiteTests/Mocks/MockBookingsRemote.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ final class MockBookingsRemote: BookingsRemoteProtocol {
private var fetchResourceResult: Result<BookingResource?, Error>?
private var updateBookingResult: Result<Booking?, Error>?
private var fetchResourcesResult: Result<[BookingResource], Error>?
private var updateBookingNote: Result<Booking?, Error>?

func whenLoadingAllBookings(thenReturn result: Result<[Booking], Error>) {
loadAllBookingsResult = result
Expand Down Expand Up @@ -59,7 +60,8 @@ final class MockBookingsRemote: BookingsRemoteProtocol {
func updateBooking(from siteID: Int64,
bookingID: Int64,
attendanceStatus: BookingAttendanceStatus?,
bookingStatus: BookingStatus?) async throws -> Booking? {
bookingStatus: BookingStatus?,
note: String?) async throws -> Booking? {
guard let result = updateBookingResult else {
throw NetworkError.timeout()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,27 +242,57 @@ extension BookingDetailsViewModel {
) { [weak self] error in
if let error, let self {
DDLogError("⛔️ Error updating booking attendance status: \(error)")
displayAttendanceStatusUpdatedErrorNotice(status: newStatus)
displayErrorNotice(
messageFormat: Localization.bookingAttendanceStatusUpdateFailedMessage
) { [weak self] in
self?.updateAttendanceStatus(to: newStatus)
}
}
}
stores.dispatch(action)
}

private func displayAttendanceStatusUpdatedErrorNotice(status: BookingAttendanceStatus) {
@MainActor
func updateNote(to newNote: String) async -> MultilineCommitResult {
await withCheckedContinuation { continuation in
let action = BookingAction.updateBookingNote(
siteID: booking.siteID,
bookingID: booking.bookingID,
note: newNote
) { [booking] error in
if let error {
DDLogError("⛔️ Error updating booking note: \(error)")
let message = String.localizedStringWithFormat(
Localization.bookingNoteUpdateFailedMessage,
booking.bookingID
)

continuation.resume(returning: .failure(message: message))
return
}

continuation.resume(returning: .success)
}

stores.dispatch(action)
}
}

private func displayErrorNotice(
messageFormat: String,
retry: @escaping () -> Void
) {
let text = String.localizedStringWithFormat(
Localization.bookingAttendanceStatusUpdateFailedMessage,
messageFormat,
booking.bookingID
)
self.notice = Notice(

notice = Notice(
message: text,
feedbackType: .error,
actionTitle: Localization.retryActionTitle
) { [weak self] in
guard let self else {
return
}

updateAttendanceStatus(to: status)
) {
retry()
}
}
}
Expand Down Expand Up @@ -475,6 +505,14 @@ private extension BookingDetailsViewModel {
+ "Parameters: %1$d - Booking number"
)

static let bookingNoteUpdateFailedMessage = NSLocalizedString(
"BookingDetailsView.bookingNote.failureMessage.",
value: "Unable to update note of Booking #%1$d.",
comment: "Content of error presented when updating the not of a Booking fails. "
+ "It reads: Unable to update note of Booking #{Booking number}. "
+ "Parameters: %1$d - Booking number"
)

static let bookingCancellationFailedMessage = NSLocalizedString(
"BookingDetailsView.cancellation.failureMessage",
value: "Unable to cancel Booking #%1$d.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,9 @@ private extension BookingDetailsView {
func bookingNotesView() -> some View {
MultilineEditableTextRow(value: viewModel.note,
placeholder: Localization.bookingNotesRowText,
detailTitle: Localization.bookingNoteNavbarText)
detailTitle: Localization.bookingNoteNavbarText) { newNote in
return await viewModel.updateNote(to: newNote)
}
}
}

Expand Down
Loading