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
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ final class WooShippingCustomsFormViewModel: ObservableObject {
@Published var returnToSenderIfNotDelivered = false

@Published var requiredInformationIsEntered = false
@Published var itemsRequiredInformationIsEntered = false
@Published private var itemsRequiredInformationIsEntered = false

@Published var contentExplanation = ""
@Published var restrictionDetails = ""
Expand All @@ -30,15 +30,22 @@ final class WooShippingCustomsFormViewModel: ObservableObject {
@Published private(set) var destinationCountryCode: String?

private var cancellables = Set<AnyCancellable>()
private let onCompletion: (ShippingLabelCustomsForm) -> ()

/// The callback that passes the `ShippingLabelCustomsForm` to outer environment
/// Called when:
/// - The customs form is closed
/// - The customs form is pre-filled with data and all required fields are completed.
private let onFormReady: (ShippingLabelCustomsForm) -> ()

@Published private(set) var itemsViewModels: [WooShippingCustomsItemViewModel] = []

init(order: Order,
shipment: Shipment,
originCountryCode: AnyPublisher<String?, Never>? = nil,
stores: StoresManager = ServiceLocator.stores,
storageManager: StorageManagerType = ServiceLocator.storageManager,
onCompletion: @escaping (ShippingLabelCustomsForm) -> ()) {
self.onCompletion = onCompletion
onFormReady: @escaping (ShippingLabelCustomsForm) -> ()) {
self.onFormReady = onFormReady

itemsViewModels = shipment.items.map {
WooShippingCustomsItemViewModel(itemName: $0.name,
Expand All @@ -55,31 +62,27 @@ final class WooShippingCustomsFormViewModel: ObservableObject {
listenToItemsRequiredInformationValues()
listenForRequiredInformation()
listenForInternationalTransactionNumberIsRequired()
listenForRequiredInformationCompletedUponPreFill()
}

@Published private(set) var itemsViewModels: [WooShippingCustomsItemViewModel] = []
/// WOOMOB-734
/// Solves the issue where a pre-filled form becomes complete without a manual submission
///
/// Listens for the `requiredInformationIsEntered` state
/// As soon as all required info is entered, calls the `emitForm` just once
func listenForRequiredInformationCompletedUponPreFill() {
$requiredInformationIsEntered
.first { $0 == true }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using first here can be risky. I still see the purchase failure with the following steps (edge case):

  • Select an order with a US address as the destination.
  • Edit the address to outside of the US.
  • Proceed to purchase -> same error about missing customs form.

.sink { [weak self] _ in
DispatchQueue.main.async {
self?.emitForm()
}
}
.store(in: &cancellables)
}

func onDismiss() {
/// Ignoring `packageID` and `packageName` as these are not needed in WooShipping plugin, only in WCS&T
let form = ShippingLabelCustomsForm(packageID: "",
packageName: "",
contentsType: contentType.toFormContentsType(),
contentExplanation: contentType == .other ? contentExplanation : "",
restrictionType: restrictionType.toFormRestrictionType(),
restrictionComments: restrictionType == .other ? restrictionDetails : "",
nonDeliveryOption: returnToSenderIfNotDelivered ? .return : .abandon,
itn: internationalTransactionNumber.isValidITN ? internationalTransactionNumber : "",
items: itemsViewModels.map {
ShippingLabelCustomsForm.Item(description: $0.description,
quantity: $0.itemQuantity,
value: Double($0.valuePerUnit) ?? 0,
weight: Double($0.weightPerUnit) ?? 0,
hsTariffNumber: $0.isValidTariffNumber ? $0.hsTariffNumber : "",
originCountry: $0.selectedCountry?.code ?? "",
productID: $0.itemProductID)
}
)
onCompletion(form)
emitForm()
}

func updateDestinationCountry(code: String) {
Expand Down Expand Up @@ -192,6 +195,33 @@ private extension WooShippingCustomsFormViewModel {
}
return ServiceLocator.currencySettings.symbol(from: currencyCode)
}

private func emitForm() {
/// Ignoring `packageID` and `packageName` as these are not needed in WooShipping plugin, only in WCS&T
let form = ShippingLabelCustomsForm(
packageID: "",
packageName: "",
contentsType: contentType.toFormContentsType(),
contentExplanation: contentType == .other ? contentExplanation : "",
restrictionType: restrictionType.toFormRestrictionType(),
restrictionComments: restrictionType == .other ? restrictionDetails : "",
nonDeliveryOption: returnToSenderIfNotDelivered ? .return : .abandon,
itn: internationalTransactionNumber.isValidITN ? internationalTransactionNumber : "",
items: itemsViewModels.map {
ShippingLabelCustomsForm.Item(
description: $0.description,
quantity: $0.itemQuantity,
value: Double($0.valuePerUnit) ?? 0,
weight: Double($0.weightPerUnit) ?? 0,
hsTariffNumber: $0.isValidTariffNumber ? $0.hsTariffNumber : "",
originCountry: $0.selectedCountry?.code ?? "",
productID: $0.itemProductID
)
}
)

onFormReady(form)
}
}

private extension WooShippingCustomsFormViewModel {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ private extension WooShippingCustomsItemViewModel {
func combineToPreselectCountry() {
$originCountryCode
.compactMap { $0 }
.filter { !$0.isEmpty }
.first() /// Make sure to only handle the initial value
.combineLatest($countries)
.map { code, countries in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ final class WooShippingCustomsFormViewModelTests: XCTestCase {

viewModel = WooShippingCustomsFormViewModel(order: Order.fake(),
shipment: sampleShipment,
onCompletion: { _ in })
onFormReady: { _ in })
}

override func tearDown() {
Expand All @@ -25,7 +25,7 @@ final class WooShippingCustomsFormViewModelTests: XCTestCase {
var passedForm: ShippingLabelCustomsForm?
viewModel = WooShippingCustomsFormViewModel(order: Order.fake(),
shipment: shipment,
onCompletion: { form in
onFormReady: { form in
passedForm = form
})

Expand Down Expand Up @@ -66,7 +66,7 @@ final class WooShippingCustomsFormViewModelTests: XCTestCase {
var passedForm: ShippingLabelCustomsForm?
viewModel = WooShippingCustomsFormViewModel(order: Order.fake(),
shipment: sampleShipment,
onCompletion: { form in
onFormReady: { form in
passedForm = form
})

Expand All @@ -84,7 +84,7 @@ final class WooShippingCustomsFormViewModelTests: XCTestCase {
var passedForm: ShippingLabelCustomsForm?
viewModel = WooShippingCustomsFormViewModel(order: Order.fake(),
shipment: sampleShipment,
onCompletion: { form in
onFormReady: { form in
passedForm = form
})

Expand All @@ -109,7 +109,7 @@ final class WooShippingCustomsFormViewModelTests: XCTestCase {
var passedForm: ShippingLabelCustomsForm?
viewModel = WooShippingCustomsFormViewModel(order: Order.fake(),
shipment: sampleShipment,
onCompletion: { form in
onFormReady: { form in
passedForm = form
})

Expand All @@ -127,7 +127,7 @@ final class WooShippingCustomsFormViewModelTests: XCTestCase {
let order = Order.fake().copy(currency: "USD")
viewModel = WooShippingCustomsFormViewModel(order: order,
shipment: sampleShipment,
onCompletion: { _ in })
onFormReady: { _ in })

// Then
XCTAssertEqual(viewModel.itemsViewModels.first?.currencySymbol, "$")
Expand All @@ -153,7 +153,7 @@ final class WooShippingCustomsFormViewModelTests: XCTestCase {
// Given
viewModel = WooShippingCustomsFormViewModel(order: Order.fake().copy(currency: "USD"),
shipment: sampleShipment,
onCompletion: { _ in })
onFormReady: { _ in })

// When
viewModel.itemsViewModels.forEach { item in
Expand All @@ -176,7 +176,7 @@ final class WooShippingCustomsFormViewModelTests: XCTestCase {
// Given
viewModel = WooShippingCustomsFormViewModel(order: Order.fake(),
shipment: sampleShipment,
onCompletion: { _ in })
onFormReady: { _ in })

// When
viewModel.itemsViewModels.first?.hsTariffNumberTotalValue = ("123456", 1000)
Expand All @@ -189,7 +189,7 @@ final class WooShippingCustomsFormViewModelTests: XCTestCase {
// Given
viewModel = WooShippingCustomsFormViewModel(order: Order.fake(),
shipment: sampleShipment,
onCompletion: { _ in })
onFormReady: { _ in })

// When
viewModel.itemsViewModels[0].requiredInformationIsEntered = true
Expand All @@ -216,7 +216,7 @@ final class WooShippingCustomsFormViewModelTests: XCTestCase {
let requiredDestinations = ["IR", "SY", "KP", "CU", "SD"]
viewModel = WooShippingCustomsFormViewModel(order: Order.fake(),
shipment: sampleShipment,
onCompletion: { _ in })
onFormReady: { _ in })
viewModel.itemsViewModels.forEach { item in
item.hsTariffNumber = ""
item.valuePerUnit = "1000"
Expand All @@ -241,7 +241,7 @@ final class WooShippingCustomsFormViewModelTests: XCTestCase {
// Given
viewModel = WooShippingCustomsFormViewModel(order: Order.fake(),
shipment: sampleShipment,
onCompletion: { _ in })
onFormReady: { _ in })

// When
viewModel.itemsViewModels.first?.requiredInformationIsEntered = true
Expand All @@ -255,7 +255,7 @@ final class WooShippingCustomsFormViewModelTests: XCTestCase {
// Given
viewModel = WooShippingCustomsFormViewModel(order: Order.fake(),
shipment: sampleShipment,
onCompletion: { _ in })
onFormReady: { _ in })

// When
viewModel.internationalTransactionNumber = "NOEEI 30.37(a)"
Expand All @@ -269,7 +269,7 @@ final class WooShippingCustomsFormViewModelTests: XCTestCase {
// Given
viewModel = WooShippingCustomsFormViewModel(order: Order.fake(),
shipment: sampleShipment,
onCompletion: { _ in })
onFormReady: { _ in })

// When
viewModel.itemsViewModels.first?.requiredInformationIsEntered = true
Expand All @@ -285,7 +285,7 @@ final class WooShippingCustomsFormViewModelTests: XCTestCase {
// Given
viewModel = WooShippingCustomsFormViewModel(order: Order.fake(),
shipment: sampleShipment,
onCompletion: { _ in })
onFormReady: { _ in })

// When
viewModel.itemsViewModels.first?.requiredInformationIsEntered = true
Expand All @@ -301,7 +301,7 @@ final class WooShippingCustomsFormViewModelTests: XCTestCase {
// Given
viewModel = WooShippingCustomsFormViewModel(order: Order.fake(),
shipment: sampleShipment,
onCompletion: { _ in })
onFormReady: { _ in })

// When
viewModel.itemsViewModels.first?.requiredInformationIsEntered = true
Expand All @@ -324,7 +324,7 @@ final class WooShippingCustomsFormViewModelTests: XCTestCase {
// When
viewModel = WooShippingCustomsFormViewModel(order: Order.fake(),
shipment: shipment,
onCompletion: { _ in })
onFormReady: { _ in })
let firstItemViewModel = viewModel.itemsViewModels.first

// Then
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,66 @@ final class WooShippingShipmentDetailsViewModelTests: XCTestCase {
// Then
XCTAssertEqual(customsItemViewModel.selectedCountry?.code, expectedCountry)
}

func test_package_contains_complete_customs_form_when_required_data_is_prefilled() throws {
// Setup
let originAddressSubject = PassthroughSubject<WooShippingAddress?, Never>()
let destinationAddressSubject = PassthroughSubject<WooShippingAddress?, Never>()
let stores = MockStoresManager(sessionManager: .testingInstance)
let storageManager = MockStorageManager()

// Given
let originCountry = Country(code: "US", name: "United States", states: [])
let destinationCountry = Country(code: "CA", name: "Canada", states: [])

let countries = [
originCountry,
destinationCountry
]

stores.whenReceivingAction(ofType: DataAction.self) { action in
switch action {
case let .synchronizeCountries(_, onCompletion):
storageManager.insertSampleCountries(readOnlyCountries: countries)
onCompletion(.success(countries))
}
}

let shipment = sampleShipment

let viewModel = WooShippingShipmentDetailsViewModel(
order: Order.fake(),
shipment: shipment,
shippingLabel: nil,
originAddress: originAddressSubject.eraseToAnyPublisher(),
destinationAddress: destinationAddressSubject.eraseToAnyPublisher(),
stores: stores,
storageManager: storageManager
)

// When
destinationAddressSubject.send(sampleDestinationAddress(country: destinationCountry.code, state: ""))
originAddressSubject.send(sampleOriginAddress(country: originCountry.code, state: ""))

viewModel.selectPackage(samplePackageData())

// Then
XCTAssertTrue(viewModel.customsInformationIsCompleted)

waitUntil {
guard let customsForm = viewModel.currentPackage?.customsForm else {
return false
}

let customsFormItem = customsForm.items[0]
let shipmentItem = shipment.items[0]

return customsFormItem.description == shipmentItem.name &&
customsFormItem.value == shipmentItem.value &&
customsFormItem.weight == shipmentItem.weight &&
customsFormItem.originCountry == originCountry.code
}
}
}

private extension WooShippingShipmentDetailsViewModelTests {
Expand Down