Skip to content

Commit f8e2fd7

Browse files
authored
Merge pull request #14564 from woocommerce/woomob-1244-woo-poslocal-catalog-use-new-pos-specific-variation-model-in
[POS][Local Catalog] WooPosVariation
2 parents adfdc06 + 4ae74f9 commit f8e2fd7

19 files changed

+634
-167
lines changed
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package com.woocommerce.android.ui.woopos.common.data
22

3-
import com.woocommerce.android.model.ProductVariation
4-
import com.woocommerce.android.model.toAppModel
53
import com.woocommerce.android.tools.SelectedSite
64
import kotlinx.coroutines.Dispatchers.IO
75
import kotlinx.coroutines.withContext
@@ -11,8 +9,10 @@ import javax.inject.Inject
119
class WooPosGetVariationById @Inject constructor(
1210
private val store: WCProductStore,
1311
private val site: SelectedSite,
12+
private val mapper: WooPosVariationMapper,
1413
) {
15-
suspend operator fun invoke(productId: Long, variationId: Long): ProductVariation? = withContext(IO) {
16-
store.getVariationByRemoteId(site.getOrNull()!!, productId, variationId)?.toAppModel()
14+
suspend operator fun invoke(productId: Long, variationId: Long): WooPosVariation? = withContext(IO) {
15+
val siteModel = site.getOrNull() ?: return@withContext null
16+
store.getVariationByRemoteId(siteModel, productId, variationId)?.toWooPosVariation(mapper)
1717
}
1818
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.woocommerce.android.ui.woopos.common.data
2+
3+
import java.math.BigDecimal
4+
5+
/**
6+
* POS-specific product variation model containing only the fields needed for POS functionality.
7+
* This model provides better performance and cleaner separation compared to the general ProductVariation model.
8+
*/
9+
data class WooPosVariation(
10+
val remoteVariationId: Long,
11+
val remoteProductId: Long,
12+
val globalUniqueId: String,
13+
val price: BigDecimal?,
14+
val image: WooPosVariationImage?,
15+
val attributes: List<WooPosVariationAttribute>,
16+
val isVisible: Boolean,
17+
val isDownloadable: Boolean
18+
) {
19+
20+
data class WooPosVariationImage(
21+
val source: String
22+
)
23+
24+
data class WooPosVariationAttribute(
25+
val id: Long?,
26+
val name: String?,
27+
val option: String?
28+
)
29+
}
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
package com.woocommerce.android.ui.woopos.common.data
2+
3+
import com.google.gson.Gson
4+
import com.google.gson.JsonSyntaxException
5+
import com.google.gson.reflect.TypeToken
6+
import com.woocommerce.android.R
7+
import com.woocommerce.android.model.Product
8+
import com.woocommerce.android.model.ProductVariation
9+
import com.woocommerce.android.util.WooLog
10+
import com.woocommerce.android.viewmodel.ResourceProvider
11+
import org.wordpress.android.fluxc.model.WCProductVariationModel
12+
import org.wordpress.android.fluxc.persistence.entity.pos.WCPosVariationModel
13+
import javax.inject.Inject
14+
import javax.inject.Singleton
15+
16+
/**
17+
* Mapper class responsible for all WooPosVariation conversions and transformations.
18+
* This centralizes all variation mapping logic, JSON parsing, and name generation.
19+
*/
20+
@Singleton
21+
class WooPosVariationMapper @Inject constructor(
22+
private val gson: Gson
23+
) {
24+
fun fromProductVariation(productVariation: ProductVariation): WooPosVariation {
25+
return WooPosVariation(
26+
remoteVariationId = productVariation.remoteVariationId,
27+
remoteProductId = productVariation.remoteProductId,
28+
globalUniqueId = productVariation.globalUniqueId,
29+
price = productVariation.price,
30+
image = productVariation.image?.let { WooPosVariation.WooPosVariationImage(it.source) },
31+
attributes = productVariation.attributes.map {
32+
WooPosVariation.WooPosVariationAttribute(
33+
id = it.id,
34+
name = it.name,
35+
option = it.option
36+
)
37+
},
38+
isVisible = productVariation.isVisible,
39+
isDownloadable = productVariation.isDownloadable
40+
)
41+
}
42+
43+
@Suppress("SwallowedException")
44+
fun fromWCProductVariationModel(model: WCProductVariationModel): WooPosVariation {
45+
val attributesList = model.attributeList?.map { attribute ->
46+
WooPosVariation.WooPosVariationAttribute(
47+
id = attribute.id,
48+
name = attribute.name,
49+
option = attribute.option
50+
)
51+
} ?: emptyList()
52+
53+
val imageModel = try {
54+
if (model.image.isNotEmpty()) model.getImageModel() else null
55+
} catch (e: JsonSyntaxException) {
56+
WooLog.w(
57+
WooLog.T.POS,
58+
"Failed to parse image from JSON attributes for variation " +
59+
"${model.remoteVariationId.value}: ${e.message}"
60+
)
61+
null
62+
}
63+
64+
return WooPosVariation(
65+
remoteVariationId = model.remoteVariationId.value,
66+
remoteProductId = model.remoteProductId.value,
67+
globalUniqueId = model.globalUniqueId,
68+
price = model.price.toBigDecimalOrNull(),
69+
image = imageModel?.src?.let { WooPosVariation.WooPosVariationImage(it) },
70+
attributes = attributesList,
71+
isVisible = model.status == "publish",
72+
isDownloadable = model.downloadable
73+
)
74+
}
75+
76+
@Suppress("SwallowedException")
77+
fun fromWCPosVariationModel(model: WCPosVariationModel): WooPosVariation {
78+
val attributesList = parseAttributesJson(model.attributesJson)
79+
80+
return WooPosVariation(
81+
remoteVariationId = model.remoteVariationId.value,
82+
remoteProductId = model.remoteProductId.value,
83+
globalUniqueId = model.globalUniqueId,
84+
price = model.price.toBigDecimalOrNull(),
85+
image = if (model.imageUrl.isNotEmpty()) WooPosVariation.WooPosVariationImage(model.imageUrl) else null,
86+
attributes = attributesList,
87+
isVisible = model.status == "publish",
88+
isDownloadable = model.downloadable
89+
)
90+
}
91+
92+
fun getNameForPOS(
93+
variation: WooPosVariation,
94+
parentProduct: Product? = null,
95+
resourceProvider: ResourceProvider,
96+
): String {
97+
return parentProduct?.variationEnabledAttributes?.joinToString(", ") { attribute ->
98+
val option = variation.attributes.firstOrNull { it.name == attribute.name }
99+
if (option?.option != null) {
100+
"${attribute.name}: ${option.option}"
101+
} else {
102+
resourceProvider.getString(
103+
R.string.woopos_variations_any_variation,
104+
attribute.name
105+
)
106+
}
107+
} ?: variation.attributes.joinToString(", ") { attribute ->
108+
when {
109+
attribute.option != null && attribute.name != null -> "${attribute.name}: ${attribute.option}"
110+
attribute.option != null -> attribute.option
111+
attribute.name != null -> resourceProvider.getString(
112+
R.string.woopos_variations_any_variation,
113+
attribute.name
114+
)
115+
else -> ""
116+
}
117+
}
118+
}
119+
120+
fun getName(variation: WooPosVariation, parentProduct: Product? = null): String {
121+
return parentProduct?.variationEnabledAttributes?.joinToString(" - ") { attribute ->
122+
val option = variation.attributes.firstOrNull { it.name == attribute.name }
123+
option?.option ?: "Any ${attribute.name}"
124+
} ?: variation.attributes.mapNotNull { it.option }.joinToString(" - ")
125+
}
126+
127+
/**
128+
* Parses JSON string containing variation attributes into a list of WooPosVariationAttribute objects.
129+
*
130+
* @param attributesJson The JSON string to parse
131+
* @return List of parsed attributes, or empty list if parsing fails
132+
*/
133+
private fun parseAttributesJson(attributesJson: String): List<WooPosVariation.WooPosVariationAttribute> {
134+
if (attributesJson.isEmpty()) return emptyList()
135+
136+
return try {
137+
val type = object : TypeToken<List<AttributeJsonItem>>() {}.type
138+
val items: List<AttributeJsonItem> = gson.fromJson(attributesJson, type)
139+
items.map { item ->
140+
WooPosVariation.WooPosVariationAttribute(
141+
id = item.id,
142+
name = item.name,
143+
option = item.option
144+
)
145+
}
146+
} catch (e: JsonSyntaxException) {
147+
WooLog.w(WooLog.T.POS, "Failed to parse attributes JSON: ${e.message}")
148+
emptyList()
149+
}
150+
}
151+
152+
private data class AttributeJsonItem(
153+
val id: Long?,
154+
val name: String?,
155+
val option: String?
156+
)
157+
}
158+
159+
fun ProductVariation.toWooPosVariation(mapper: WooPosVariationMapper): WooPosVariation =
160+
mapper.fromProductVariation(this)
161+
162+
fun WCProductVariationModel.toWooPosVariation(mapper: WooPosVariationMapper): WooPosVariation =
163+
mapper.fromWCProductVariationModel(this)
164+
165+
fun WCPosVariationModel.toWooPosVariation(mapper: WooPosVariationMapper): WooPosVariation =
166+
mapper.fromWCPosVariationModel(this)
167+
168+
fun WooPosVariation.getNameForPOS(
169+
mapper: WooPosVariationMapper,
170+
parentProduct: Product? = null,
171+
resourceProvider: ResourceProvider,
172+
): String = mapper.getNameForPOS(this, parentProduct, resourceProvider)
173+
174+
fun WooPosVariation.getName(mapper: WooPosVariationMapper, parentProduct: Product? = null): String =
175+
mapper.getName(this, parentProduct)

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/searchbyidentifier/WooPosSearchByIdentifier.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package com.woocommerce.android.ui.woopos.common.data.searchbyidentifier
22

33
import com.woocommerce.android.model.Product
4-
import com.woocommerce.android.model.ProductVariation
54
import com.woocommerce.android.ui.woopos.common.data.WooPosProductsTypesFilterConfig
5+
import com.woocommerce.android.ui.woopos.common.data.WooPosVariation
66
import com.woocommerce.android.ui.woopos.common.data.WooPosVariationsTypesFilterConfig
77
import com.woocommerce.android.ui.woopos.common.util.WooPosLogWrapper
88
import org.wordpress.android.fluxc.store.WCProductStore
@@ -75,7 +75,7 @@ class WooPosSearchByIdentifier @Inject constructor(
7575
}
7676
}
7777

78-
private fun meetsVariationFilterRequirements(variation: ProductVariation): Boolean {
78+
private fun meetsVariationFilterRequirements(variation: WooPosVariation): Boolean {
7979
val requiredStatus = variationFilterConfig.filters[VariationFilterOption.STATUS]
8080
val hasValidStatus = when (requiredStatus) {
8181
WooPosVariationsTypesFilterConfig.VARIATION_STATUS_PUBLISH -> variation.isVisible

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/searchbyidentifier/WooPosSearchByIdentifierResult.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package com.woocommerce.android.ui.woopos.common.data.searchbyidentifier
22

33
import com.woocommerce.android.model.Product
4-
import com.woocommerce.android.model.ProductVariation
4+
import com.woocommerce.android.ui.woopos.common.data.WooPosVariation
55

66
sealed class WooPosSearchByIdentifierResult {
77
data class Success(val product: Product) : WooPosSearchByIdentifierResult()
8-
data class VariationSuccess(val variation: ProductVariation, val parentProduct: Product) :
8+
data class VariationSuccess(val variation: WooPosVariation, val parentProduct: Product) :
99
WooPosSearchByIdentifierResult()
1010

1111
data class Failure(val error: Error) : WooPosSearchByIdentifierResult()

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/searchbyidentifier/WooPosSearchByIdentifierVariationFetch.kt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package com.woocommerce.android.ui.woopos.common.data.searchbyidentifier
22

3-
import com.woocommerce.android.model.ProductVariation
4-
import com.woocommerce.android.model.toAppModel
53
import com.woocommerce.android.tools.SelectedSite
4+
import com.woocommerce.android.ui.woopos.common.data.WooPosVariation
5+
import com.woocommerce.android.ui.woopos.common.data.WooPosVariationMapper
6+
import com.woocommerce.android.ui.woopos.common.data.toWooPosVariation
67
import com.woocommerce.android.ui.woopos.home.items.variations.WooPosVariationsLRUCache
78
import org.wordpress.android.fluxc.store.WCProductStore
89
import javax.inject.Inject
@@ -11,10 +12,11 @@ class WooPosSearchByIdentifierVariationFetch @Inject constructor(
1112
private val selectedSite: SelectedSite,
1213
private val productStore: WCProductStore,
1314
private val variationsCache: WooPosVariationsLRUCache,
14-
private val errorMapper: WooPosSearchByIdentifierProductErrorMapper
15+
private val errorMapper: WooPosSearchByIdentifierProductErrorMapper,
16+
private val mapper: WooPosVariationMapper
1517
) {
1618
sealed class VariationFetchResult {
17-
data class Success(val variation: ProductVariation) : VariationFetchResult()
19+
data class Success(val variation: WooPosVariation) : VariationFetchResult()
1820
data class Failure(val error: WooPosSearchByIdentifierResult.Error) : VariationFetchResult()
1921
}
2022

@@ -34,7 +36,7 @@ class WooPosSearchByIdentifierVariationFetch @Inject constructor(
3436
selectedSite.get(),
3537
parentId,
3638
variationId
37-
)?.toAppModel()
39+
)?.toWooPosVariation(mapper)
3840

3941
return if (variation != null) {
4042
variationsCache.add(parentId, variation)

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModel.kt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ import androidx.lifecycle.map
88
import androidx.lifecycle.viewModelScope
99
import com.woocommerce.android.R
1010
import com.woocommerce.android.model.Product
11-
import com.woocommerce.android.model.ProductVariation
1211
import com.woocommerce.android.ui.woopos.common.composeui.modifier.BarcodeInputDetector
1312
import com.woocommerce.android.ui.woopos.common.data.WooPosGetCouponById
1413
import com.woocommerce.android.ui.woopos.common.data.WooPosGetProductById
1514
import com.woocommerce.android.ui.woopos.common.data.WooPosGetVariationById
15+
import com.woocommerce.android.ui.woopos.common.data.WooPosVariation
16+
import com.woocommerce.android.ui.woopos.common.data.WooPosVariationMapper
17+
import com.woocommerce.android.ui.woopos.common.data.getNameForPOS
1618
import com.woocommerce.android.ui.woopos.common.data.searchbyidentifier.WooPosSearchByIdentifier
1719
import com.woocommerce.android.ui.woopos.common.data.searchbyidentifier.WooPosSearchByIdentifierResult
1820
import com.woocommerce.android.ui.woopos.common.util.WooPosLogWrapper
@@ -28,7 +30,6 @@ import com.woocommerce.android.ui.woopos.home.cart.WooPosCartStatus.CHECKOUT
2830
import com.woocommerce.android.ui.woopos.home.cart.WooPosCartStatus.EDITABLE
2931
import com.woocommerce.android.ui.woopos.home.cart.WooPosCartStatus.EMPTY
3032
import com.woocommerce.android.ui.woopos.home.items.WooPosItemsViewModel
31-
import com.woocommerce.android.ui.woopos.home.items.variations.getNameForPOS
3233
import com.woocommerce.android.ui.woopos.util.WooPosGetCachedStoreCurrency
3334
import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent
3435
import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.BackToCartTapped
@@ -69,6 +70,7 @@ class WooPosCartViewModel @Inject constructor(
6970
private val wooPosLogWrapper: WooPosLogWrapper,
7071
private val soundHelper: WooPosSoundHelper,
7172
private val barcodeEventTracker: WooPosBarcodeEventTracker,
73+
private val variationMapper: WooPosVariationMapper,
7274
savedState: SavedStateHandle,
7375
) : ViewModel() {
7476
private val _state = savedState.getStateFlow(
@@ -589,7 +591,7 @@ class WooPosCartViewModel @Inject constructor(
589591
imageUrl = firstImageUrl,
590592
)
591593

592-
private suspend fun ProductVariation.toCartListItem(
594+
private suspend fun WooPosVariation.toCartListItem(
593595
itemNumber: Int,
594596
product: Product
595597
): WooPosCartItemViewState.Product.Variation =
@@ -598,7 +600,7 @@ class WooPosCartViewModel @Inject constructor(
598600
id = product.remoteId,
599601
variationId = this.remoteVariationId,
600602
name = product.name,
601-
description = getNameForPOS(product, resourceProvider),
603+
description = getNameForPOS(variationMapper, product, resourceProvider),
602604
price = formatPrice(price),
603605
imageUrl = image?.source,
604606
)
@@ -635,7 +637,7 @@ class WooPosCartViewModel @Inject constructor(
635637
id = variation.remoteProductId,
636638
variationId = variation.remoteVariationId,
637639
name = this.parentProduct.name,
638-
description = variation.getNameForPOS(this.parentProduct, resourceProvider),
640+
description = variation.getNameForPOS(variationMapper, this.parentProduct, resourceProvider),
639641
price = formatPrice(variation.price),
640642
imageUrl = variation.image?.source
641643
)

0 commit comments

Comments
 (0)