diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductErrorExt.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductErrorExt.kt index c466424b24b..ea4fe6fe895 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductErrorExt.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductErrorExt.kt @@ -5,5 +5,8 @@ import org.wordpress.android.fluxc.store.WCProductStore * Returns whether the error message is meaningful and can be displayed to the user. */ val WCProductStore.ProductError.canDisplayMessage: Boolean - get() = this.type == WCProductStore.ProductErrorType.INVALID_MIN_MAX_QUANTITY && + get() = ( + this.type == WCProductStore.ProductErrorType.INVALID_MIN_MAX_QUANTITY || + this.type == WCProductStore.ProductErrorType.GENERIC_ERROR + ) && this.message.isNotEmpty() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductInventoryFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductInventoryFragment.kt index 72bd9a93efc..cecbdd2e073 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductInventoryFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductInventoryFragment.kt @@ -66,6 +66,9 @@ class ProductInventoryFragment : new.skuErrorMessage?.takeIfNotEqualTo(old?.skuErrorMessage) { displaySkuError(it) } + new.globalUniqueIdErrorMessage?.takeIfNotEqualTo(old?.globalUniqueIdErrorMessage) { + displayGlobalUniqueIdErrorMessage(it) + } new.isStockManagementVisible?.takeIfNotEqualTo(old?.isStockManagementVisible) { isVisible -> binding.stockManagementPanel.isVisible = isVisible binding.soldIndividuallySwitch.isVisible = isVisible && new.isIndividualSaleSwitchVisible == true @@ -148,6 +151,14 @@ class ProductInventoryFragment : } } + private fun displayGlobalUniqueIdErrorMessage(messageId: Int) { + if (messageId != 0) { + binding.productGlobalUniqueId.error = getString(messageId) + } else { + binding.productGlobalUniqueId.helperText = getString(R.string.product_global_unique_id_summary) + } + } + private fun setupViews() { if (!isAdded) return @@ -220,6 +231,10 @@ class ProductInventoryFragment : with(binding.productGlobalUniqueId) { visibility = if (featureIsEnabled) View.VISIBLE else View.GONE + + setOnTextChangedListener { + viewModel.onProductUniqueGlobalIdChanged(it.toString()) + } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductInventoryViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductInventoryViewModel.kt index 52f4f9c0bdb..6eca1176028 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductInventoryViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductInventoryViewModel.kt @@ -104,8 +104,19 @@ class ProductInventoryViewModel @Inject constructor( } } + fun onProductUniqueGlobalIdChanged(globalUniqueId: String) { + onDataChanged(globalUniqueId = globalUniqueId) + + if (isOnlyNumbersAndHyphens(globalUniqueId)) { + clearGlobalUniqueIdError() + } else { + showGlobalUniqueIdError() + } + } + fun onDataChanged( sku: String? = inventoryData.sku, + globalUniqueId: String? = inventoryData.globalUniqueId, backorderStatus: ProductBackorderStatus? = inventoryData.backorderStatus, isSoldIndividually: Boolean? = inventoryData.isSoldIndividually, isStockManaged: Boolean? = inventoryData.isStockManaged, @@ -115,6 +126,7 @@ class ProductInventoryViewModel @Inject constructor( viewState = viewState.copy( inventoryData = InventoryData( sku = sku, + globalUniqueId = globalUniqueId, backorderStatus = backorderStatus, isSoldIndividually = isSoldIndividually, isStockManaged = isStockManaged, @@ -129,7 +141,7 @@ class ProductInventoryViewModel @Inject constructor( AnalyticsEvent.PRODUCT_INVENTORY_SETTINGS_DONE_BUTTON_TAPPED, mapOf(AnalyticsTracker.KEY_HAS_CHANGED_DATA to hasChanges) ) - if (hasChanges && !hasSkuError()) { + if (hasChanges && !hasSkuError() && !hasGlobalUniqueIdError()) { triggerEvent(ExitWithResult(inventoryData)) } else { triggerEvent(Exit) @@ -144,8 +156,26 @@ class ProductInventoryViewModel @Inject constructor( viewState = viewState.copy(skuErrorMessage = string.product_inventory_update_sku_error) } + private fun clearGlobalUniqueIdError() { + viewState = viewState.copy(globalUniqueIdErrorMessage = 0) + } + + private fun showGlobalUniqueIdError() { + viewState = viewState.copy(globalUniqueIdErrorMessage = string.product_inventory_update_global_unique_id_error) + } + private fun hasSkuError() = viewState.skuErrorMessage != 0 && viewState.skuErrorMessage != null + private fun hasGlobalUniqueIdError() = viewState.globalUniqueIdErrorMessage != 0 && + viewState.globalUniqueIdErrorMessage != null + + private fun isOnlyNumbersAndHyphens(input: String): Boolean { + // Define the regex pattern to match only numbers and hyphens + val pattern = "^[0-9-]+$" + // Check if the input string matches the pattern + return input.matches(pattern.toRegex()) + } + override fun onCleared() { super.onCleared() productRepository.onCleanup() @@ -155,6 +185,7 @@ class ProductInventoryViewModel @Inject constructor( data class ViewState( val inventoryData: InventoryData = InventoryData(), val skuErrorMessage: Int? = null, + val globalUniqueIdErrorMessage: Int? = null, val isIndividualSaleSwitchVisible: Boolean? = null, val isStockStatusVisible: Boolean? = null, val isStockManagementVisible: Boolean? = null, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailFragment.kt index fc96d844bd5..4050df62e72 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailFragment.kt @@ -260,6 +260,7 @@ class ProductDetailFragment : handleResult(BaseProductEditorFragment.KEY_INVENTORY_DIALOG_RESULT) { viewModel.updateProductDraft( sku = it.sku, + globalUniqueId = it.globalUniqueId, soldIndividually = it.isSoldIndividually, stockStatus = it.stockStatus, stockQuantity = it.stockQuantity, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt index 2bf012494e4..daaaef496d4 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt @@ -1248,6 +1248,7 @@ class ProductDetailViewModel @Inject constructor( shortDescription: String? = null, title: String? = null, sku: String? = null, + globalUniqueId: String? = null, slug: String? = null, manageStock: Boolean? = null, stockStatus: ProductStockStatus? = null, @@ -1299,6 +1300,7 @@ class ProductDetailViewModel @Inject constructor( shortDescription = shortDescription ?: product.shortDescription, name = title ?: product.name, sku = sku ?: product.sku, + globalUniqueId = globalUniqueId ?: product.globalUniqueId, slug = slug ?: product.slug, isStockManaged = manageStock ?: product.isStockManaged, stockStatus = stockStatus ?: product.stockStatus, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailFragment.kt index cedf0a9fc40..73db8a9d40d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailFragment.kt @@ -211,6 +211,7 @@ class VariationDetailFragment : handleResult(BaseProductEditorFragment.KEY_INVENTORY_DIALOG_RESULT) { viewModel.onVariationChanged( sku = it.sku, + globalUniqueId = it.globalUniqueId, stockStatus = it.stockStatus, stockQuantity = it.stockQuantity, backorderStatus = it.backorderStatus, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailViewModel.kt index c5c20db2ebb..f840ba289b4 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailViewModel.kt @@ -211,6 +211,7 @@ class VariationDetailViewModel @Inject constructor( remoteProductId: Long? = null, remoteVariationId: Long? = null, sku: String? = null, + globalUniqueId: String? = null, image: Optional? = null, regularPrice: BigDecimal? = viewState.variation?.regularPrice, salePrice: BigDecimal? = viewState.variation?.salePrice, @@ -243,6 +244,7 @@ class VariationDetailViewModel @Inject constructor( remoteProductId = remoteProductId ?: variation.remoteProductId, remoteVariationId = remoteVariationId ?: variation.remoteVariationId, sku = sku ?: variation.sku, + globalUniqueId = globalUniqueId ?: variation.globalUniqueId, image = if (image != null) image.value else variation.image, regularPrice = regularPrice, salePrice = salePrice, diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 57d986c5533..857843ab16a 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -2114,6 +2114,7 @@ Quantity How many items are in stock SKU already in use by another product + Please enter only numbers and hyphens Please enter a number Sale price must be less than regular price You must set the sale price if a sale is scheduled diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductInventoryViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductInventoryViewModelTest.kt index 8010ea79e14..88aa34006ba 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductInventoryViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductInventoryViewModelTest.kt @@ -35,6 +35,7 @@ class ProductInventoryViewModelTest : BaseUnitTest() { private val initialData = InventoryData( "SKU123", + globalUniqueId = "123-456", isStockManaged = false, isSoldIndividually = false, stockStatus = InStock, @@ -44,6 +45,7 @@ class ProductInventoryViewModelTest : BaseUnitTest() { private val initialDataWithNonWholeDecimalQuantity = InventoryData( "SKU123", + globalUniqueId = "123-456", isStockManaged = true, isSoldIndividually = false, stockStatus = InStock, @@ -53,6 +55,7 @@ class ProductInventoryViewModelTest : BaseUnitTest() { private val expectedData = InventoryData( "SKU321", + globalUniqueId = "123-456", isStockManaged = true, isSoldIndividually = true, stockStatus = OutOfStock, @@ -98,6 +101,7 @@ class ProductInventoryViewModelTest : BaseUnitTest() { viewModel.onDataChanged( expectedData.sku, + expectedData.globalUniqueId, expectedData.backorderStatus, expectedData.isSoldIndividually, expectedData.isStockManaged, @@ -131,6 +135,27 @@ class ProductInventoryViewModelTest : BaseUnitTest() { } @Test + fun `Test that an error is shown if the global unique id contains characters other than numbers and hyphens`() = + testBlocking { + val invalidGlobalUniqueId = "invalid" + val validGlobalUniqueId = "123-456" + var actual: ViewState? = null + viewModel.viewStateData.observeForever { _, new -> + actual = new + } + + viewModel.onProductUniqueGlobalIdChanged(invalidGlobalUniqueId) + + assertThat(actual?.inventoryData?.globalUniqueId).isEqualTo(invalidGlobalUniqueId) + assertThat(actual?.globalUniqueIdErrorMessage) + .isEqualTo(string.product_inventory_update_global_unique_id_error) + + viewModel.onProductUniqueGlobalIdChanged(validGlobalUniqueId) + + assertThat(actual?.inventoryData?.globalUniqueId).isEqualTo(validGlobalUniqueId) + assertThat(actual?.globalUniqueIdErrorMessage).isEqualTo(0) + } + fun `Test that a discard dialog isn't shown if no data changed`() = testBlocking { val events = mutableListOf() @@ -156,6 +181,7 @@ class ProductInventoryViewModelTest : BaseUnitTest() { viewModel.onDataChanged( expectedData.sku, + expectedData.globalUniqueId, expectedData.backorderStatus, expectedData.isSoldIndividually, expectedData.isStockManaged,