diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 4a743eae563..c90a951cbcf 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -7,6 +7,7 @@ - [*] Jetpack setup: Native experience for the connection step [https://github.com/woocommerce/woocommerce-ios/pull/15983] - [*] Payments: Updated the In-Person Payments `Learn More` redirection to display the correct page based on the selected payment provider [https://github.com/woocommerce/woocommerce-ios/pull/15998] - [*] Add "POS" label in Most recent orders in My store dashboard [https://github.com/woocommerce/woocommerce-ios/pull/16006] +- [*] Order details: Always show shipping labels section if order is eligible for label creation. [https://github.com/woocommerce/woocommerce-ios/pull/16010] 23.0 ----- diff --git a/WooCommerce/Classes/ViewModels/Order Details/OrderDetailsDataSource.swift b/WooCommerce/Classes/ViewModels/Order Details/OrderDetailsDataSource.swift index bb77957b682..7f602473730 100644 --- a/WooCommerce/Classes/ViewModels/Order Details/OrderDetailsDataSource.swift +++ b/WooCommerce/Classes/ViewModels/Order Details/OrderDetailsDataSource.swift @@ -61,15 +61,6 @@ final class OrderDetailsDataSource: NSObject { return true } - /// Whether the button to create shipping labels should be visible. - /// - var shouldShowShippingLabelCreation: Bool { - if featureFlags.isFeatureFlagEnabled(.revampedShippingLabelCreation) { - return isEligibleForShippingLabelCreation && !isEligibleForPayment && shipments.isEmpty - } - return isEligibleForShippingLabelCreation && shippingLabels.nonRefunded.isEmpty && !isEligibleForPayment - } - /// Whether the option to re-create shipping labels should be visible. /// var shouldAllowRecreatingShippingLabels: Bool { @@ -456,11 +447,6 @@ private extension OrderDetailsDataSource { configureShippingLabelDetail(cell: cell) case let cell as WCShipInstallTableViewCell where row == .installWCShip: configureInstallWCShip(cell: cell) - case let cell as ImageAndTitleAndTextTableViewCell where row == .shippingLabelCreationInfo(showsSeparator: true), - let cell as ImageAndTitleAndTextTableViewCell where row == .shippingLabelCreationInfo(showsSeparator: false): - if case .shippingLabelCreationInfo(let showsSeparator) = row { - configureShippingLabelCreationInfo(cell: cell, showsSeparator: showsSeparator) - } case let cell as ImageAndTitleAndTextTableViewCell where row == .shippingLabelPrintingInfo: configureShippingLabelPrintingInfo(cell: cell) case let cell as LargeHeightLeftImageTableViewCell where row == .addOrderNote: @@ -489,8 +475,6 @@ private extension OrderDetailsDataSource { configureCustomAmount(cell: cell, at: indexPath) case let cell as ButtonTableViewCell where row == .collectCardPaymentButton: configureCollectPaymentButton(cell: cell, at: indexPath) - case let cell as ButtonTableViewCell where row == .shippingLabelCreateButton: - configureCreateShippingLabelButton(cell: cell, at: indexPath) case let cell as ButtonTableViewCell where row == .markCompleteButton(style: .primary, showsBottomSpacing: true), let cell as ButtonTableViewCell where row == .markCompleteButton(style: .primary, showsBottomSpacing: false), let cell as ButtonTableViewCell where row == .markCompleteButton(style: .secondary, showsBottomSpacing: true), @@ -721,33 +705,6 @@ private extension OrderDetailsDataSource { cell.hideSeparator() } - private func configureCreateShippingLabelButton(cell: ButtonTableViewCell, at indexPath: IndexPath) { - cell.configure(style: .primary, - title: Titles.createShippingLabel, - bottomSpacing: 0) { - self.onCellAction?(.createShippingLabel(shipmentIndex: nil), nil) - } - cell.hideSeparator() - } - - private func configureShippingLabelCreationInfo(cell: ImageAndTitleAndTextTableViewCell, showsSeparator: Bool) { - cell.update(with: .imageAndTitleOnly(fontStyle: .footnote), - data: .init(title: Title.shippingLabelCreationInfoAction, - image: .infoOutlineFootnoteImage, - imageTintColor: .systemColor(.secondaryLabel), - numberOfLinesForTitle: 0, - isActionable: false, - showsSeparator: showsSeparator)) - - cell.selectionStyle = .default - - cell.accessibilityTraits = .button - cell.accessibilityLabel = Title.shippingLabelCreationInfoAction - cell.accessibilityHint = - NSLocalizedString("Tap to show information about creating a shipping label", - comment: "VoiceOver accessibility hint for the row that shows information about creating a shipping label") - } - private func configureShippingLabelDetail(cell: WooBasicTableViewCell) { cell.bodyLabel?.text = isEligibleForWooShipping ? Footer.viewShippingLabel : Footer.showShippingLabelDetails cell.applyPlainTextStyle() @@ -1272,7 +1229,34 @@ extension OrderDetailsDataSource { siteShippingMethods = resultsControllers.siteShippingMethods productVariations = resultsControllers.productVariations shippingLabels = resultsControllers.shippingLabels - shipments = resultsControllers.shipments + shipments = { + let cachedShipments = resultsControllers.shipments + if cachedShipments.isNotEmpty { + return cachedShipments + } + + if !isEligibleForShippingLabelCreation || shippingLabels.isNotEmpty { + /// Skips creating shipments if order is not eligible for creating labels + /// If there are labels but not shipments, the labels were created with legacy plugin, + /// so skip creating shipments to display them the old way instead. + return [] + } + + /// returns a placeholder shipment with all the order items + return [WooShippingShipment( + siteID: order.siteID, + orderID: order.orderID, + index: "0", + items: order.items.map { item in + var subItems: [String] = [] + for index in 0.. info link for creating a shipping label on the mobile device.") static let shippingLabelPackageFormat = NSLocalizedString("Package %d", comment: "Order shipping label package section title format. The number indicates the index of the shipping label package.") @@ -2022,8 +1993,6 @@ extension OrderDetailsDataSource { case trackingAdd case collectCardPaymentButton case installWCShip - case shippingLabelCreateButton - case shippingLabelCreationInfo(showsSeparator: Bool) case shippingLabelDetail case shippingLabelPrintingInfo case shippingLabelProducts @@ -2085,10 +2054,6 @@ extension OrderDetailsDataSource { return ButtonTableViewCell.reuseIdentifier case .installWCShip: return WCShipInstallTableViewCell.reuseIdentifier - case .shippingLabelCreateButton: - return ButtonTableViewCell.reuseIdentifier - case .shippingLabelCreationInfo: - return ImageAndTitleAndTextTableViewCell.reuseIdentifier case .shippingLabelDetail: return WooBasicTableViewCell.reuseIdentifier case .shippingLabelPrintingInfo: diff --git a/WooCommerce/Classes/ViewModels/Order Details/OrderDetailsViewModel.swift b/WooCommerce/Classes/ViewModels/Order Details/OrderDetailsViewModel.swift index 1fc72b3fb94..67aa5a2f7ee 100644 --- a/WooCommerce/Classes/ViewModels/Order Details/OrderDetailsViewModel.swift +++ b/WooCommerce/Classes/ViewModels/Order Details/OrderDetailsViewModel.swift @@ -123,7 +123,7 @@ final class OrderDetailsViewModel { /// The eligibility check for Woo Shipping can be updated late due to being async /// So the additional check for shipments determines if the new form should be displayed. var shouldNavigateToNewShippingLabelFlow: Bool { - dataSource.isEligibleForWooShipping || dataSource.shipments.isNotEmpty + dataSource.isEligibleForWooShipping } private(set) lazy var editNoteViewModel: EditCustomerNoteViewModel = { @@ -514,10 +514,6 @@ extension OrderDetailsViewModel { forceReadOnly: false) let navController = WooNavigationController(rootViewController: loaderViewController) viewController.present(navController, animated: true, completion: nil) - case .shippingLabelCreationInfo: - let infoViewController = ShippingLabelCreationInfoViewController() - let navigationController = WooNavigationController(rootViewController: infoViewController) - viewController.present(navigationController, animated: true, completion: nil) case .shippingLabelDetail: guard let shippingLabel = dataSource.shippingLabel(at: indexPath) else { return diff --git a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/Create Shipping Label Info/ShippingLabelCreationInfoViewController.swift b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/Create Shipping Label Info/ShippingLabelCreationInfoViewController.swift deleted file mode 100644 index 302569d8c6a..00000000000 --- a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/Create Shipping Label Info/ShippingLabelCreationInfoViewController.swift +++ /dev/null @@ -1,58 +0,0 @@ -import UIKit - -/// Displays information about shipping label creation, including the benefits. -final class ShippingLabelCreationInfoViewController: UIViewController { - /// Empty state screen - private lazy var emptyStateViewController = EmptyStateViewController(style: .basic) - - /// Empty state screen configuration - private let emptyStateConfig: EmptyStateViewController.Config = { - let message = NSAttributedString(string: Constants.message, - attributes: [.font: EmptyStateViewController.Config.messageFont.bold]) - return .withLink(message: message, - image: .shippingLabelCreationInfoImage, - details: Constants.details, - linkTitle: Constants.buttonTitle, - linkURL: WooConstants.URLs.shippingLabelCreationInfo.asURL()) - }() - - override func viewDidLoad() { - super.viewDidLoad() - - configureNavigationBar() - configureEmptyViewController() - } -} - -// MARK: Constants -private extension ShippingLabelCreationInfoViewController { - func configureNavigationBar() { - title = Constants.title - addCloseNavigationBarButton() - } - - func configureEmptyViewController() { - addChild(emptyStateViewController) - - emptyStateViewController.view.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(emptyStateViewController.view) - - emptyStateViewController.view.pinSubviewToAllEdges(view) - emptyStateViewController.didMove(toParent: self) - emptyStateViewController.configure(emptyStateConfig) - } -} - -// MARK: Constants -private extension ShippingLabelCreationInfoViewController { - struct Constants { - static let title = NSLocalizedString("WooCommerce Shipping", - comment: "Navigation bar title in the shipping label creation info screen") - static let message = NSLocalizedString("Save time and money by fulfilling with WooCommerce Shipping", - comment: "Message text in the shipping label creation info screen") - static let details = NSLocalizedString("Cut the post office line by printing shipping labels at home with your mobile device at discounted rates!", - comment: "Details text in the shipping label creation info screen") - static let buttonTitle = NSLocalizedString("Learn more", - comment: "Button title in the shipping label creation info screen") - } -} diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index 40d26a9ee10..afb6717df0a 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -608,7 +608,6 @@ 02DFD5042B20486C0048CD70 /* ProductStepper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DFD5032B20486C0048CD70 /* ProductStepper.swift */; }; 02DFD5062B2048C50048CD70 /* ProductStepperViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DFD5052B2048C50048CD70 /* ProductStepperViewModel.swift */; }; 02DFD5082B205AEF0048CD70 /* ProductStepperViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DFD5072B205AEF0048CD70 /* ProductStepperViewModelTests.swift */; }; - 02DFECE725EE338F0070F212 /* ShippingLabelCreationInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DFECE625EE338F0070F212 /* ShippingLabelCreationInfoViewController.swift */; }; 02E19B9C284743A40010B254 /* ProductImageUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E19B9B284743A40010B254 /* ProductImageUploader.swift */; }; 02E222C829FBA60F004579A1 /* WooAnalyticsEvent+ProductFormAI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E222C729FBA60F004579A1 /* WooAnalyticsEvent+ProductFormAI.swift */; }; 02E262C9238D0AD300B79588 /* ProductStockStatusListSelectorCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E262C8238D0AD300B79588 /* ProductStockStatusListSelectorCommand.swift */; }; @@ -3803,7 +3802,6 @@ 02DFD5032B20486C0048CD70 /* ProductStepper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductStepper.swift; sourceTree = ""; }; 02DFD5052B2048C50048CD70 /* ProductStepperViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductStepperViewModel.swift; sourceTree = ""; }; 02DFD5072B205AEF0048CD70 /* ProductStepperViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductStepperViewModelTests.swift; sourceTree = ""; }; - 02DFECE625EE338F0070F212 /* ShippingLabelCreationInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShippingLabelCreationInfoViewController.swift; sourceTree = ""; }; 02E19B9B284743A40010B254 /* ProductImageUploader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductImageUploader.swift; sourceTree = ""; }; 02E222C729FBA60F004579A1 /* WooAnalyticsEvent+ProductFormAI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WooAnalyticsEvent+ProductFormAI.swift"; sourceTree = ""; }; 02E262C8238D0AD300B79588 /* ProductStockStatusListSelectorCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductStockStatusListSelectorCommand.swift; sourceTree = ""; }; @@ -6735,7 +6733,6 @@ children = ( CEAB739A2C81E3A000A7EB39 /* WooShipping Create Shipping Labels */, DEE6437426D87C2D00888A75 /* Print Customs Form */, - 02DFECE525EE33430070F212 /* Create Shipping Label Info */, 023D69BA2589BF2500F7DA72 /* Refund Shipping Label */, 023D69C52589BF5F00F7DA72 /* Print Shipping Label */, 53284F4A66A725F479CD9584 /* EUShippingNoticeTopBannerFactory.swift */, @@ -7788,14 +7785,6 @@ path = "Beta features"; sourceTree = ""; }; - 02DFECE525EE33430070F212 /* Create Shipping Label Info */ = { - isa = PBXGroup; - children = ( - 02DFECE625EE338F0070F212 /* ShippingLabelCreationInfoViewController.swift */, - ); - path = "Create Shipping Label Info"; - sourceTree = ""; - }; 02E262C3238D04DB00B79588 /* ListSelector */ = { isa = PBXGroup; children = ( @@ -15416,7 +15405,6 @@ 2D88C1112DF883C300A6FB2C /* AttributedString+Helpers.swift in Sources */, CE2A9FC623BFFADE002BEC1C /* RefundedProductsViewModel.swift in Sources */, 205B7EC32C19FC3000D14A36 /* PointOfSaleCardPresentPaymentConnectingToReaderAlertViewModel.swift in Sources */, - 02DFECE725EE338F0070F212 /* ShippingLabelCreationInfoViewController.swift in Sources */, 093B265527DE8F020026F92D /* UnitInputViewModel+BulkUpdatePrice.swift in Sources */, 02C7EE8C2B22B21D008B7DF8 /* CollapsibleProductRowCardViewModel.swift in Sources */, 0245465F24EE9106004F531C /* ProductVariationFormEventLogger.swift in Sources */, diff --git a/WooCommerce/WooCommerceTests/ViewRelated/Orders/Order Details/OrderDetailsDataSourceTests.swift b/WooCommerce/WooCommerceTests/ViewRelated/Orders/Order Details/OrderDetailsDataSourceTests.swift index efeecab57c7..e1afc111d86 100644 --- a/WooCommerce/WooCommerceTests/ViewRelated/Orders/Order Details/OrderDetailsDataSourceTests.swift +++ b/WooCommerce/WooCommerceTests/ViewRelated/Orders/Order Details/OrderDetailsDataSourceTests.swift @@ -217,7 +217,7 @@ final class OrderDetailsDataSourceTests: XCTestCase { XCTAssertNil(issueRefundRow) } - func test_markOrderComplete_button_is_visible_and_primary_style_if_order_is_processing_and_not_eligible_for_shipping_label_creation() throws { + func test_markOrderComplete_button_is_visible_and_primary_style_if_order_is_processing() throws { // Given let order = makeOrder().copy(status: .processing) let dataSource = OrderDetailsDataSource(order: order, @@ -234,24 +234,6 @@ final class OrderDetailsDataSourceTests: XCTestCase { XCTAssertNotNil(row(row: .markCompleteButton(style: .primary, showsBottomSpacing: true), in: productsSection)) } - func test_markOrderComplete_button_is_visible_and_secondary_style_if_order_is_processing_and_eligible_for_shipping_label_creation() throws { - // Given - let order = makeOrder().copy(status: .processing) - let dataSource = OrderDetailsDataSource(order: order, - storageManager: storageManager, - cardPresentPaymentsConfiguration: Mocks.configuration, - receiptEligibilityUseCase: MockReceiptEligibilityUseCase()) - dataSource.isEligibleForShippingLabelCreation = true - - // When - dataSource.reloadSections() - - // Then - let productsSection = try section(withTitle: Title.products, from: dataSource) - XCTAssertNotNil(row(row: .markCompleteButton(style: .secondary, showsBottomSpacing: false), in: productsSection)) - XCTAssertNotNil(row(row: .shippingLabelCreationInfo(showsSeparator: false), in: productsSection)) - } - func test_markOrderComplete_button_is_hidden_if_order_is_not_processing() throws { // Given let order = makeOrder().copy(status: .onHold) @@ -301,107 +283,6 @@ final class OrderDetailsDataSourceTests: XCTestCase { XCTAssertNotNil(row(row: .collectCardPaymentButton, in: paymentSection)) } - func test_create_shipping_label_button_is_visible_for_eligible_order_with_no_labels() throws { - // Given - let order = makeOrder() - let dataSource = OrderDetailsDataSource(order: order, - storageManager: storageManager, - cardPresentPaymentsConfiguration: Mocks.configuration, - receiptEligibilityUseCase: MockReceiptEligibilityUseCase()) - dataSource.isEligibleForShippingLabelCreation = true - - // When - dataSource.reloadSections() - - // Then - let productSection = try section(withTitle: Title.products, from: dataSource) - let createShippingLabelRow = row(row: .shippingLabelCreateButton, in: productSection) - XCTAssertNotNil(createShippingLabelRow) - } - - func test_create_shipping_label_button_is_visible_for_eligible_order_with_only_refunded_labels() throws { - // Given - let order = makeOrder() - let refundedShippingLabel = ShippingLabel.fake().copy(siteID: order.siteID, orderID: order.orderID, refund: ShippingLabelRefund.fake()) - insert(shippingLabel: refundedShippingLabel, order: order) - - let dataSource = OrderDetailsDataSource(order: order, - storageManager: storageManager, - cardPresentPaymentsConfiguration: Mocks.configuration, - receiptEligibilityUseCase: MockReceiptEligibilityUseCase()) - dataSource.isEligibleForShippingLabelCreation = true - dataSource.configureResultsControllers { } - - // When - dataSource.reloadSections() - - // Then - let productSection = try section(withTitle: Title.products, from: dataSource) - let createShippingLabelRow = row(row: .shippingLabelCreateButton, in: productSection) - XCTAssertNotNil(createShippingLabelRow) - } - - func test_create_shipping_label_button_is_not_visible_for_eligible_order_with_labels() throws { - // Given - var order = makeOrder() - let shippingLabel = ShippingLabel.fake().copy(siteID: order.siteID, orderID: order.orderID) - order = order.copy(shippingLabels: [shippingLabel]) - insert(shippingLabel: shippingLabel, order: order) - - let dataSource = OrderDetailsDataSource(order: order, - storageManager: storageManager, - cardPresentPaymentsConfiguration: Mocks.configuration, - receiptEligibilityUseCase: MockReceiptEligibilityUseCase(), - featureFlags: MockFeatureFlagService(revampedShippingLabelCreation: false)) - dataSource.isEligibleForShippingLabelCreation = true - dataSource.configureResultsControllers { } - - // When - dataSource.reloadSections() - - // Then - let productSection = try section(withTitle: Title.products, from: dataSource) - let createShippingLabelRow = row(row: .shippingLabelCreateButton, in: productSection) - XCTAssertNil(createShippingLabelRow) - } - - func test_create_shipping_label_button_is_not_visible_for_ineligible_order() throws { - // Given - let order = makeOrder() - let dataSource = OrderDetailsDataSource(order: order, - storageManager: storageManager, - cardPresentPaymentsConfiguration: Mocks.configuration, - receiptEligibilityUseCase: MockReceiptEligibilityUseCase()) - dataSource.isEligibleForShippingLabelCreation = false - - // When - dataSource.reloadSections() - - // Then - let productSection = try section(withTitle: Title.products, from: dataSource) - let createShippingLabelRow = row(row: .shippingLabelCreateButton, in: productSection) - XCTAssertNil(createShippingLabelRow) - } - - func test_create_shipping_label_button_is_not_visible_when_order_is_eligible_for_payment() throws { - // Given - let order = makeOrder().copy(status: .processing, datePaid: .some(nil), total: "100") - let dataSource = OrderDetailsDataSource(order: order, - storageManager: storageManager, - cardPresentPaymentsConfiguration: Mocks.configuration, - receiptEligibilityUseCase: MockReceiptEligibilityUseCase()) - dataSource.isEligibleForShippingLabelCreation = true - - // When - dataSource.configureResultsControllers { } - dataSource.reloadSections() - - // Then - let productSection = try section(withTitle: Title.products, from: dataSource) - let createShippingLabelRow = row(row: .shippingLabelCreateButton, in: productSection) - XCTAssertNil(createShippingLabelRow) - } - func test_more_button_is_visible_in_product_section_for_eligible_order_without_refunded_labels() throws { // Given var order = makeOrder() @@ -1068,6 +949,44 @@ final class OrderDetailsDataSourceTests: XCTestCase { XCTAssertEqual(shipmentsSection?.rows.count, 2) } + func test_shipping_labels_section_is_available_when_no_shipments_are_available_and_eligible_for_label_creation() throws { + // Given + let order = makeOrder() + let dataSource = OrderDetailsDataSource(order: order, + storageManager: storageManager, + cardPresentPaymentsConfiguration: Mocks.configuration, + receiptEligibilityUseCase: MockReceiptEligibilityUseCase(), + featureFlags: MockFeatureFlagService(revampedShippingLabelCreation: false)) + dataSource.isEligibleForShippingLabelCreation = true + dataSource.configureResultsControllers { } + + // When + dataSource.reloadSections() + + // Then + let shipmentsSection = dataSource.sections.first { $0.category == .wooShipping } + XCTAssertEqual(shipmentsSection?.rows.count, 1) + } + + func test_shipping_labels_section_is_unavailable_when_no_shipments_are_available_and_ineligible_for_label_creation() throws { + // Given + let order = makeOrder() + let dataSource = OrderDetailsDataSource(order: order, + storageManager: storageManager, + cardPresentPaymentsConfiguration: Mocks.configuration, + receiptEligibilityUseCase: MockReceiptEligibilityUseCase(), + featureFlags: MockFeatureFlagService(revampedShippingLabelCreation: false)) + dataSource.isEligibleForShippingLabelCreation = false + dataSource.configureResultsControllers { } + + // When + dataSource.reloadSections() + + // Then + let shipmentsSection = dataSource.sections.first { $0.category == .wooShipping } + XCTAssertNil(shipmentsSection) + } + func test_isEligibleForBackendReceipt_when_initialized_then_defaults_to_false() { // Given let order = Order.fake()