Skip to content

Commit 146ebde

Browse files
committed
Merge branch 'trunk' into issue/WOOMOB-887-cache-carrier-packages-data
# Conflicts: # libs/fluxc-plugin/schemas/org.wordpress.android.fluxc.persistence.WCAndroidDatabase/63.json
2 parents 2cfd82d + cf3dd81 commit 146ebde

File tree

11 files changed

+4216
-78
lines changed

11 files changed

+4216
-78
lines changed

.github/workflows/dependabot_automerge.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,16 @@ jobs:
2828
if: steps.metadata.outputs.update-type == 'version-update:semver-patch'
2929
run: |
3030
set -e
31-
number=$(gh api repos/$GITHUB_REPOSITORY/milestones \
31+
title=$(gh api repos/$GITHUB_REPOSITORY/milestones \
3232
--jq '[.[]
3333
| select(.state=="open" and .due_on!=null and (.due_on | fromdateiso8601) >= now)
3434
]
3535
| sort_by(.due_on)
36-
| .[0].number')
36+
| .[0].title')
3737
38-
if [ -n "$number" ]; then
39-
echo "Assigning milestone #$number to PR #${{ github.event.pull_request.number }}"
40-
gh pr edit "${{ github.event.pull_request.number }}" --milestone "$number"
38+
if [ -n "$title" ]; then
39+
echo "Assigning milestone '$title' to PR #${{ github.event.pull_request.number }}"
40+
gh pr edit "${{ github.event.pull_request.number }}" --milestone "$title"
4141
else
4242
echo "No future open milestones found."
4343
fi
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.woocommerce.android.ui.woopos.common.data.models
2+
3+
import com.woocommerce.android.ui.woopos.common.util.WooPosLogWrapper
4+
import dagger.Reusable
5+
import org.wordpress.android.fluxc.model.WCProductModel
6+
import java.math.BigDecimal
7+
import javax.inject.Inject
8+
9+
@Reusable
10+
class WCProductToWooPosProductModelMapper @Inject constructor(
11+
private val wooPosProductModelMapper: WooPosProductModelVersion2Mapper,
12+
private val logger: WooPosLogWrapper
13+
) {
14+
fun map(wcProduct: WCProductModel): WooPosProductModelVersion2 {
15+
return WooPosProductModelVersion2(
16+
remoteId = wcProduct.remoteProductId,
17+
parentId = wcProduct.parentId.takeIf { it != 0L },
18+
name = wcProduct.name,
19+
sku = wcProduct.sku,
20+
globalUniqueId = wcProduct.globalUniqueId,
21+
type = wooPosProductModelMapper.mapProductType(wcProduct.type),
22+
status = wooPosProductModelMapper.mapProductStatus(wcProduct.status),
23+
pricing = wooPosProductModelMapper.mapPricing(
24+
price = wcProduct.price.toBigDecimalOrNull(),
25+
regularPrice = wcProduct.regularPrice.toBigDecimalOrNull(),
26+
salePrice = wcProduct.salePrice.toBigDecimalOrNull(),
27+
isOnSale = wcProduct.onSale
28+
),
29+
description = wcProduct.description,
30+
shortDescription = wcProduct.shortDescription,
31+
isDownloadable = wcProduct.downloadable,
32+
lastModified = wcProduct.dateModified,
33+
images = wooPosProductModelMapper.parseImages(wcProduct.images),
34+
attributes = wooPosProductModelMapper.parseAttributes(wcProduct.attributes),
35+
categories = wooPosProductModelMapper.parseCategories(wcProduct.categories),
36+
tags = wooPosProductModelMapper.parseTags(wcProduct.tags)
37+
)
38+
}
39+
40+
private fun String.toBigDecimalOrNull(): BigDecimal? {
41+
return try {
42+
if (this.isNotBlank()) BigDecimal(this) else null
43+
} catch (e: NumberFormatException) {
44+
logger.e("Failed to parse price: '$this'", e)
45+
null
46+
}
47+
}
48+
}

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/models/WooPosProductModelVersion2.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ data class WooPosProductModelVersion2(
2626
val attributes: List<WooPosProductAttribute> = emptyList(),
2727
val categories: List<WooPosProductCategory> = emptyList(),
2828
val tags: List<WooPosProductTag> = emptyList(),
29+
val variationIds: List<Long> = emptyList(),
2930
) : Parcelable {
3031

3132
sealed class WooPosPricing : Parcelable {

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/models/WooPosProductModelVersion2Mapper.kt

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ class WooPosProductModelVersion2Mapper @Inject constructor(val logger: WooPosLog
4040
attributes = parseAttributes(entity.attributes),
4141
categories = parseCategories(entity.categories),
4242
tags = parseTags(entity.tags),
43-
lastModified = entity.dateModified
43+
lastModified = entity.dateModified,
44+
variationIds = parseVariationIds(entity.variations)
4445
)
4546
}
4647

@@ -61,7 +62,7 @@ class WooPosProductModelVersion2Mapper @Inject constructor(val logger: WooPosLog
6162
}
6263
}
6364

64-
private fun parseImages(imagesJson: String): List<WooPosProductModelVersion2.WooPosProductImage> {
65+
fun parseImages(imagesJson: String): List<WooPosProductModelVersion2.WooPosProductImage> {
6566
return try {
6667
if (imagesJson.isBlank()) {
6768
emptyList()
@@ -96,7 +97,7 @@ class WooPosProductModelVersion2Mapper @Inject constructor(val logger: WooPosLog
9697
return WooPosProductModelVersion2.WooPosProductImage(id, url, name, alt)
9798
}
9899

99-
private fun parseAttributes(attributesJson: String): List<WooPosProductModelVersion2.WooPosProductAttribute> {
100+
fun parseAttributes(attributesJson: String): List<WooPosProductModelVersion2.WooPosProductAttribute> {
100101
return try {
101102
if (attributesJson.isBlank()) {
102103
emptyList()
@@ -132,7 +133,7 @@ class WooPosProductModelVersion2Mapper @Inject constructor(val logger: WooPosLog
132133
return WooPosProductModelVersion2.WooPosProductAttribute(id, name, options, isVisible, isVariation)
133134
}
134135

135-
private fun parseCategories(categoriesJson: String): List<WooPosProductModelVersion2.WooPosProductCategory> {
136+
fun parseCategories(categoriesJson: String): List<WooPosProductModelVersion2.WooPosProductCategory> {
136137
return try {
137138
if (categoriesJson.isBlank()) {
138139
emptyList()
@@ -166,7 +167,7 @@ class WooPosProductModelVersion2Mapper @Inject constructor(val logger: WooPosLog
166167
return WooPosProductModelVersion2.WooPosProductCategory(id, name, slug)
167168
}
168169

169-
private fun parseTags(tagsJson: String): List<WooPosProductModelVersion2.WooPosProductTag> {
170+
fun parseTags(tagsJson: String): List<WooPosProductModelVersion2.WooPosProductTag> {
170171
return try {
171172
if (tagsJson.isBlank()) {
172173
emptyList()
@@ -200,6 +201,29 @@ class WooPosProductModelVersion2Mapper @Inject constructor(val logger: WooPosLog
200201
return WooPosProductModelVersion2.WooPosProductTag(id, name, slug)
201202
}
202203

204+
private fun parseVariationIds(variationsJson: String): List<Long> {
205+
return try {
206+
if (variationsJson.isBlank()) {
207+
emptyList()
208+
} else {
209+
val type = object : TypeToken<List<*>>() {}.type
210+
val variationsList: List<*> = gson.fromJson(variationsJson, type)
211+
variationsList.mapNotNull { variation ->
212+
when (variation) {
213+
is Double -> variation.toLong()
214+
is Int -> variation.toLong()
215+
is Long -> variation
216+
is String -> variation.toLongOrNull()
217+
else -> null
218+
}
219+
}
220+
}
221+
} catch (e: JsonSyntaxException) {
222+
logger.w("Failed to parse variation IDs JSON: $variationsJson - ${e.message}")
223+
emptyList()
224+
}
225+
}
226+
203227
fun mapPricing(
204228
price: BigDecimal?,
205229
regularPrice: BigDecimal?,
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package com.woocommerce.android.ui.woopos.common.data.models
2+
3+
import com.google.gson.Gson
4+
import com.woocommerce.android.ui.woopos.common.util.WooPosLogWrapper
5+
import org.assertj.core.api.Assertions.assertThat
6+
import org.junit.Test
7+
import org.mockito.kotlin.mock
8+
import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId
9+
import org.wordpress.android.fluxc.model.LocalOrRemoteId.RemoteId
10+
import org.wordpress.android.fluxc.model.WCProductModel
11+
import java.math.BigDecimal
12+
13+
class WCProductToWooPosProductModelMapperTest {
14+
private val logger: WooPosLogWrapper = mock()
15+
private val wooPosProductModelMapper = WooPosProductModelVersion2Mapper(logger)
16+
private val sut: WCProductToWooPosProductModelMapper =
17+
WCProductToWooPosProductModelMapper(wooPosProductModelMapper, logger)
18+
19+
@Test
20+
fun `when mapping WCProductModel, then all attributes are correctly mapped`() {
21+
val imagesJson = """[{"id": 1, "src": "https://example.com/image.jpg", "name": "Image", "alt": "Alt text"}]"""
22+
.trimIndent()
23+
val categoriesJson = """[{"id": 10, "name": "Electronics", "slug": "electronics"}]""".trimIndent()
24+
val tagsJson = """[{"id": 20, "name": "Featured", "slug": "featured"}]""".trimIndent()
25+
26+
val attributes = arrayOf(
27+
WCProductModel.ProductAttribute(
28+
id = 100L,
29+
name = "Color",
30+
variation = true,
31+
visible = true,
32+
options = listOf("Red", "Blue")
33+
)
34+
)
35+
36+
val wcProduct = createFullWCProductModel(imagesJson, attributes, categoriesJson, tagsJson)
37+
38+
val result = sut.map(wcProduct)
39+
40+
assertThat(result.remoteId).isEqualTo(123L)
41+
assertThat(result.parentId).isEqualTo(456L)
42+
assertThat(result.name).isEqualTo("Test Product")
43+
assertThat(result.sku).isEqualTo("TEST-SKU")
44+
assertThat(result.globalUniqueId).isEqualTo("global-123")
45+
assertThat(result.description).isEqualTo("Full description")
46+
assertThat(result.shortDescription).isEqualTo("Short description")
47+
assertThat(result.isDownloadable).isTrue()
48+
assertThat(result.lastModified).isEqualTo("2024-01-15T10:00:00Z")
49+
50+
assertThat(result.type).isEqualTo(WooPosProductModelVersion2.WooPosProductType.SIMPLE)
51+
assertThat(result.status).isEqualTo(WooPosProductModelVersion2.WooPosProductStatus.PUBLISH)
52+
53+
assertThat(result.pricing).isInstanceOf(WooPosProductModelVersion2.WooPosPricing.SalePricing::class.java)
54+
val pricing = result.pricing as WooPosProductModelVersion2.WooPosPricing.SalePricing
55+
assertThat(pricing.regularPrice).isEqualTo(BigDecimal("24.99"))
56+
assertThat(pricing.salePrice).isEqualTo(BigDecimal("19.99"))
57+
58+
assertThat(result.images).hasSize(1)
59+
assertThat(result.images[0].id).isEqualTo(1L)
60+
assertThat(result.images[0].url).isEqualTo("https://example.com/image.jpg")
61+
assertThat(result.images[0].name).isEqualTo("Image")
62+
assertThat(result.images[0].alt).isEqualTo("Alt text")
63+
64+
assertThat(result.attributes).hasSize(1)
65+
assertThat(result.attributes[0].id).isEqualTo(100L)
66+
assertThat(result.attributes[0].name).isEqualTo("Color")
67+
assertThat(result.attributes[0].options).containsExactly("Red", "Blue")
68+
assertThat(result.attributes[0].isVisible).isTrue()
69+
assertThat(result.attributes[0].isVariation).isTrue()
70+
71+
assertThat(result.categories).hasSize(1)
72+
assertThat(result.categories[0].id).isEqualTo(10L)
73+
assertThat(result.categories[0].name).isEqualTo("Electronics")
74+
assertThat(result.categories[0].slug).isEqualTo("electronics")
75+
76+
assertThat(result.tags).hasSize(1)
77+
assertThat(result.tags[0].id).isEqualTo(20L)
78+
assertThat(result.tags[0].name).isEqualTo("Featured")
79+
assertThat(result.tags[0].slug).isEqualTo("featured")
80+
}
81+
82+
@Test
83+
fun `when parent id is zero, then mapped parent id is null`() {
84+
val wcProduct = WCProductModel(
85+
localSiteId = LocalId(1),
86+
remoteId = RemoteId(123L),
87+
parentId = 0L
88+
)
89+
90+
val result = sut.map(wcProduct)
91+
92+
assertThat(result.parentId).isNull()
93+
}
94+
95+
@Test
96+
fun `when parent id is non-zero, then mapped parent id has value`() {
97+
val wcProduct = WCProductModel(
98+
localSiteId = LocalId(1),
99+
remoteId = RemoteId(123L),
100+
parentId = 789L
101+
)
102+
103+
val result = sut.map(wcProduct)
104+
105+
assertThat(result.parentId).isEqualTo(789L)
106+
}
107+
108+
@Test
109+
fun `when product has empty collections, then mapped collections are empty`() {
110+
val wcProduct = WCProductModel(
111+
localSiteId = LocalId(1),
112+
remoteId = RemoteId(123L),
113+
images = "",
114+
attributes = "[]",
115+
categories = "",
116+
tags = ""
117+
)
118+
119+
val result = sut.map(wcProduct)
120+
121+
assertThat(result.images).isEmpty()
122+
assertThat(result.attributes).isEmpty()
123+
assertThat(result.categories).isEmpty()
124+
assertThat(result.tags).isEmpty()
125+
}
126+
127+
@Test
128+
fun `when product has no price, then pricing is NoPricing`() {
129+
val wcProduct = WCProductModel(
130+
localSiteId = LocalId(1),
131+
remoteId = RemoteId(123L),
132+
price = "",
133+
regularPrice = "",
134+
salePrice = "",
135+
onSale = false
136+
)
137+
138+
val result = sut.map(wcProduct)
139+
140+
assertThat(result.pricing).isEqualTo(WooPosProductModelVersion2.WooPosPricing.NoPricing)
141+
}
142+
143+
@Test
144+
fun `when product has regular price only, then pricing is RegularPricing`() {
145+
val wcProduct = WCProductModel(
146+
localSiteId = LocalId(1),
147+
remoteId = RemoteId(123L),
148+
price = "",
149+
regularPrice = "29.99",
150+
salePrice = "",
151+
onSale = false
152+
)
153+
154+
val result = sut.map(wcProduct)
155+
156+
assertThat(result.pricing).isInstanceOf(WooPosProductModelVersion2.WooPosPricing.RegularPricing::class.java)
157+
val pricing = result.pricing as WooPosProductModelVersion2.WooPosPricing.RegularPricing
158+
assertThat(pricing.price).isEqualTo(BigDecimal("29.99"))
159+
}
160+
161+
private fun createFullWCProductModel(
162+
imagesJson: String,
163+
attributes: Array<WCProductModel.ProductAttribute>,
164+
categoriesJson: String,
165+
tagsJson: String
166+
): WCProductModel {
167+
val wcProduct = WCProductModel(
168+
localSiteId = LocalId(1),
169+
remoteId = RemoteId(123L),
170+
parentId = 456L,
171+
name = "Test Product",
172+
sku = "TEST-SKU",
173+
globalUniqueId = "global-123",
174+
type = "simple",
175+
status = "publish",
176+
price = "19.99",
177+
regularPrice = "24.99",
178+
salePrice = "19.99",
179+
onSale = true,
180+
description = "Full description",
181+
shortDescription = "Short description",
182+
downloadable = true,
183+
dateModified = "2024-01-15T10:00:00Z",
184+
images = imagesJson,
185+
attributes = Gson().toJson(attributes),
186+
categories = categoriesJson,
187+
tags = tagsJson
188+
)
189+
return wcProduct
190+
}
191+
}

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ androidx-datastore = '1.1.7'
1717
androidx-exifinterface = "1.0.0"
1818
androidx-fragment = '1.8.5'
1919
androidx-hilt = '1.2.0'
20-
androidx-lifecycle = '2.9.2'
20+
androidx-lifecycle = '2.9.3'
2121
androidx-navigation = '2.9.3'
2222
androidx-paging-runtime = "2.1.2"
2323
androidx-preference = '1.2.1'

0 commit comments

Comments
 (0)