Skip to content

Commit aff87c5

Browse files
authored
Merge pull request #14549 from woocommerce/woomob-1232-woo-poslocal-catalog-propagate-header-with-total-number-of
[Woo POS][Local Catalog] Sync variations
2 parents 6757314 + d0c9bb9 commit aff87c5

File tree

9 files changed

+686
-38
lines changed

9 files changed

+686
-38
lines changed

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/localcatalog/WooPosLocalCatalogSyncRepository.kt

Lines changed: 82 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.woocommerce.android.ui.woopos.localcatalog
22

33
import com.woocommerce.android.ui.woopos.common.util.WooPosLogWrapper
44
import com.woocommerce.android.ui.woopos.localcatalog.WooPosSyncProductsAction.WooPosSyncProductsResult
5+
import com.woocommerce.android.ui.woopos.localcatalog.WooPosSyncVariationsAction.WooPosSyncVariationsResult
56
import com.woocommerce.android.ui.woopos.util.datastore.WooPosSyncTimestampManager
67
import com.woocommerce.android.util.CoroutineDispatchers
78
import kotlinx.coroutines.withContext
@@ -25,6 +26,7 @@ sealed class PosLocalCatalogSyncResult {
2526
@Singleton
2627
class PosLocalCatalogSyncRepository @Inject constructor(
2728
private val posSyncProductsAction: WooPosSyncProductsAction,
29+
private val posSyncVariationsAction: WooPosSyncVariationsAction,
2830
private val syncTimestampManager: WooPosSyncTimestampManager,
2931
private val dispatchers: CoroutineDispatchers,
3032
private val logger: WooPosLogWrapper,
@@ -51,6 +53,7 @@ class PosLocalCatalogSyncRepository @Inject constructor(
5153
)
5254
}
5355

56+
@Suppress("ReturnCount")
5457
private suspend fun performSync(
5558
site: SiteModel,
5659
pageSize: Int,
@@ -61,37 +64,92 @@ class PosLocalCatalogSyncRepository @Inject constructor(
6164

6265
logger.d("Starting sync for items modified after $modifiedAfterGmt, max pages: $maxPages")
6366

64-
val productSyncResult = posSyncProductsAction.execute(site, modifiedAfterGmt, pageSize, maxPages)
65-
// TBD Local Catalog We'll want to trigger variations action here too
67+
val productSyncResult = syncProducts(site, modifiedAfterGmt, pageSize, maxPages)
68+
if (productSyncResult is WooPosSyncProductsResult.Failed) {
69+
return productSyncResult.toPosLocalCatalogSyncFailure()
70+
}
71+
72+
val variationSyncResult = syncVariations(site, modifiedAfterGmt, pageSize, maxPages)
73+
if (variationSyncResult is WooPosSyncVariationsResult.Failed) {
74+
return variationSyncResult.toPosLocalCatalogSyncFailure()
75+
}
6676

6777
val syncDuration = System.currentTimeMillis() - startTime
6878

69-
return when (productSyncResult) {
70-
is WooPosSyncProductsResult.Success -> {
71-
// TBD Local Catalog we need to use store server timestamp
72-
val currentTime = System.currentTimeMillis()
73-
// TBD Local Catalog We need to store incremental and full sync timestamps separately
74-
syncTimestampManager.storeProductsLastSyncTimestamp(currentTime)
75-
76-
PosLocalCatalogSyncResult.Success(
77-
productsSynced = productSyncResult.productsSynced,
78-
variationsSynced = 0,
79-
syncDurationMs = syncDuration
80-
)
81-
}
79+
return PosLocalCatalogSyncResult.Success(
80+
productsSynced = (productSyncResult as WooPosSyncProductsResult.Success).productsSynced,
81+
variationsSynced = (variationSyncResult as WooPosSyncVariationsResult.Success).variationsSynced,
82+
syncDurationMs = syncDuration
83+
)
84+
}
8285

83-
is WooPosSyncProductsResult.Failed.CatalogTooLarge -> {
84-
PosLocalCatalogSyncResult.Failure.CatalogTooLarge(
85-
error = "Catalog too large: ${productSyncResult.totalPages} pages exceed maximum " +
86-
"of ${productSyncResult.maxPages} pages",
87-
totalPages = productSyncResult.totalPages,
88-
maxPages = productSyncResult.maxPages
89-
)
86+
private suspend fun syncProducts(
87+
site: SiteModel,
88+
modifiedAfterGmt: String?,
89+
pageSize: Int,
90+
maxPages: Int
91+
): WooPosSyncProductsResult {
92+
val result = posSyncProductsAction.execute(site, modifiedAfterGmt, pageSize, maxPages)
93+
94+
if (result is WooPosSyncProductsResult.Success) {
95+
result.serverDate?.let { serverDate ->
96+
syncTimestampManager.parseTimestampFromApi(serverDate)?.let { timestamp ->
97+
syncTimestampManager.storeProductsLastSyncTimestamp(timestamp)
98+
logger.d("Stored products sync timestamp: $serverDate")
99+
}
90100
}
101+
}
102+
103+
return result
104+
}
105+
106+
private suspend fun syncVariations(
107+
site: SiteModel,
108+
modifiedAfterGmt: String?,
109+
pageSize: Int,
110+
maxPages: Int
111+
): WooPosSyncVariationsResult {
112+
val result = posSyncVariationsAction.execute(site, modifiedAfterGmt, pageSize, maxPages)
91113

92-
is WooPosSyncProductsResult.Failed -> {
93-
PosLocalCatalogSyncResult.Failure.UnexpectedError(productSyncResult.error)
114+
if (result is WooPosSyncVariationsResult.Success) {
115+
result.serverDate?.let { serverDate ->
116+
syncTimestampManager.parseTimestampFromApi(serverDate)?.let { timestamp ->
117+
syncTimestampManager.storeVariationsLastSyncTimestamp(timestamp)
118+
logger.d("Stored variations sync timestamp: $serverDate")
119+
}
94120
}
95121
}
122+
123+
return result
124+
}
125+
}
126+
127+
private fun WooPosSyncProductsResult.Failed.toPosLocalCatalogSyncFailure(): PosLocalCatalogSyncResult.Failure {
128+
return when (this) {
129+
is WooPosSyncProductsResult.Failed.CatalogTooLarge -> {
130+
PosLocalCatalogSyncResult.Failure.CatalogTooLarge(
131+
error = "Product catalog too large: $totalPages pages exceed maximum of $maxPages pages",
132+
totalPages = totalPages,
133+
maxPages = maxPages
134+
)
135+
}
136+
else -> {
137+
PosLocalCatalogSyncResult.Failure.UnexpectedError(error)
138+
}
139+
}
140+
}
141+
142+
private fun WooPosSyncVariationsResult.Failed.toPosLocalCatalogSyncFailure(): PosLocalCatalogSyncResult.Failure {
143+
return when (this) {
144+
is WooPosSyncVariationsResult.Failed.CatalogTooLarge -> {
145+
PosLocalCatalogSyncResult.Failure.CatalogTooLarge(
146+
error = "Variations catalog too large: $totalPages pages exceed maximum of $maxPages pages",
147+
totalPages = totalPages,
148+
maxPages = maxPages
149+
)
150+
}
151+
else -> {
152+
PosLocalCatalogSyncResult.Failure.UnexpectedError(error)
153+
}
96154
}
97155
}

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/localcatalog/WooPosSyncProductsAction.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class WooPosSyncProductsAction @Inject constructor(
1010
private val logger: WooPosLogWrapper,
1111
) {
1212
sealed class WooPosSyncProductsResult {
13-
data class Success(val productsSynced: Int) : WooPosSyncProductsResult()
13+
data class Success(val productsSynced: Int, val serverDate: String?) : WooPosSyncProductsResult()
1414

1515
sealed class Failed(val error: String) : WooPosSyncProductsResult() {
1616
class CatalogTooLarge(val totalPages: Int, val maxPages: Int) :
@@ -52,6 +52,7 @@ class WooPosSyncProductsAction @Inject constructor(
5252
var pagesSynced = 0
5353
var totalSyncedProducts = 0
5454
var shouldContinue = true
55+
var lastServerDate: String? = null
5556

5657
while (shouldContinue) {
5758
val result = posLocalCatalogStore.syncRecentlyModifiedProducts(
@@ -67,6 +68,7 @@ class WooPosSyncProductsAction @Inject constructor(
6768
processPageResult(syncResult, pagesSynced, maxPages)
6869
totalSyncedProducts += syncResult.syncedCount
6970
pagesSynced++
71+
lastServerDate = syncResult.serverDate
7072

7173
if (!syncResult.hasMore || syncResult.syncedCount == 0) {
7274
logger.d("Local Catalog: No more products to sync")
@@ -83,7 +85,7 @@ class WooPosSyncProductsAction @Inject constructor(
8385
}
8486

8587
logger.d("Local catalog sync completed, $totalSyncedProducts products synced across $pagesSynced pages")
86-
return WooPosSyncProductsResult.Success(totalSyncedProducts)
88+
return WooPosSyncProductsResult.Success(totalSyncedProducts, lastServerDate)
8789
}
8890

8991
private fun processPageResult(
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package com.woocommerce.android.ui.woopos.localcatalog
2+
3+
import com.woocommerce.android.ui.woopos.common.util.WooPosLogWrapper
4+
import org.wordpress.android.fluxc.model.SiteModel
5+
import org.wordpress.android.fluxc.store.pos.localcatalog.WooPosLocalCatalogStore
6+
import javax.inject.Inject
7+
8+
class WooPosSyncVariationsAction @Inject constructor(
9+
private val posLocalCatalogStore: WooPosLocalCatalogStore,
10+
private val logger: WooPosLogWrapper,
11+
) {
12+
sealed class WooPosSyncVariationsResult {
13+
data class Success(val variationsSynced: Int, val serverDate: String?) : WooPosSyncVariationsResult()
14+
15+
sealed class Failed(val error: String) : WooPosSyncVariationsResult() {
16+
class CatalogTooLarge(val totalPages: Int, val maxPages: Int) :
17+
Failed("Catalog too large: $totalPages pages exceed maximum of $maxPages pages")
18+
19+
class UnexpectedError(error: String) : Failed(error)
20+
}
21+
}
22+
23+
@Suppress("ReturnCount")
24+
suspend fun execute(
25+
site: SiteModel,
26+
modifiedAfterGmt: String? = null,
27+
pageSize: Int,
28+
maxPages: Int
29+
): WooPosSyncVariationsResult {
30+
var currentPage = 1
31+
var pagesSynced = 0
32+
var totalSyncedVariations = 0
33+
var shouldContinue = true
34+
var lastServerDate: String? = null
35+
36+
while (shouldContinue) {
37+
val result = posLocalCatalogStore.syncRecentlyModifiedVariations(
38+
site = site,
39+
modifiedAfterGmt = modifiedAfterGmt ?: "",
40+
page = currentPage,
41+
pageSize = pageSize
42+
)
43+
44+
result.fold(
45+
onSuccess = { syncResult ->
46+
if (pagesSynced == 0) {
47+
if (syncResult.totalPages > maxPages) {
48+
logger.e(
49+
"Variations catalog too large: ${syncResult.totalPages} pages " +
50+
"exceed maximum of $maxPages pages"
51+
)
52+
return WooPosSyncVariationsResult.Failed.CatalogTooLarge(syncResult.totalPages, maxPages)
53+
}
54+
}
55+
56+
logger.d("Variations page ${pagesSynced + 1} synced, ${syncResult.syncedCount} variations")
57+
totalSyncedVariations += syncResult.syncedCount
58+
pagesSynced++
59+
lastServerDate = syncResult.serverDate
60+
61+
if (!syncResult.hasMore || syncResult.syncedCount == 0) {
62+
logger.d("No more variations to sync")
63+
shouldContinue = false
64+
} else {
65+
currentPage = syncResult.nextPage
66+
}
67+
},
68+
onFailure = { error ->
69+
logger.e("Variations sync failed on page ${pagesSynced + 1}: ${error.message}")
70+
return WooPosSyncVariationsResult.Failed.UnexpectedError(error.message ?: "Unknown error")
71+
}
72+
)
73+
}
74+
75+
logger.d("Variations sync completed, $totalSyncedVariations variations synced across $pagesSynced pages")
76+
return WooPosSyncVariationsResult.Success(totalSyncedVariations, lastServerDate)
77+
}
78+
}

WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/localcatalog/WooPosLocalCatalogSyncRepositoryTest.kt

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import org.wordpress.android.fluxc.model.SiteModel
2020
class WooPosLocalCatalogSyncRepositoryTest : BaseUnitTest() {
2121
private lateinit var sut: PosLocalCatalogSyncRepository
2222
private var posSyncProductsAction: WooPosSyncProductsAction = mock()
23+
private var posSyncVariationsAction: WooPosSyncVariationsAction = mock()
2324
private var syncTimestampManager: WooPosSyncTimestampManager = mock()
2425
private lateinit var dispatchers: CoroutineDispatchers
2526
private lateinit var site: SiteModel
@@ -35,6 +36,7 @@ class WooPosLocalCatalogSyncRepositoryTest : BaseUnitTest() {
3536

3637
sut = PosLocalCatalogSyncRepository(
3738
posSyncProductsAction = posSyncProductsAction,
39+
posSyncVariationsAction = posSyncVariationsAction,
3840
syncTimestampManager = syncTimestampManager,
3941
dispatchers = dispatchers,
4042
logger = logger,
@@ -51,7 +53,11 @@ class WooPosLocalCatalogSyncRepositoryTest : BaseUnitTest() {
5153
// GIVEN
5254
val productsSynced = 150
5355
whenever(posSyncProductsAction.execute(any(), anyOrNull(), any(), any()))
54-
.thenReturn(WooPosSyncProductsAction.WooPosSyncProductsResult.Success(productsSynced))
56+
.thenReturn(
57+
WooPosSyncProductsAction.WooPosSyncProductsResult.Success(productsSynced, "2024-01-01T12:00:00Z")
58+
)
59+
whenever(posSyncVariationsAction.execute(any(), anyOrNull(), any(), any()))
60+
.thenReturn(WooPosSyncVariationsAction.WooPosSyncVariationsResult.Success(50, "2024-01-01T12:00:00Z"))
5561

5662
// WHEN
5763
val result = sut.syncLocalCatalogFull(site)
@@ -65,7 +71,11 @@ class WooPosLocalCatalogSyncRepositoryTest : BaseUnitTest() {
6571
// GIVEN
6672
val productsSynced = 150
6773
whenever(posSyncProductsAction.execute(any(), anyOrNull(), any(), any()))
68-
.thenReturn(WooPosSyncProductsAction.WooPosSyncProductsResult.Success(productsSynced))
74+
.thenReturn(
75+
WooPosSyncProductsAction.WooPosSyncProductsResult.Success(productsSynced, "2024-01-01T12:00:00Z")
76+
)
77+
whenever(posSyncVariationsAction.execute(any(), anyOrNull(), any(), any()))
78+
.thenReturn(WooPosSyncVariationsAction.WooPosSyncVariationsResult.Success(50, "2024-01-01T12:00:00Z"))
6979

7080
// WHEN
7181
sut.syncLocalCatalogFull(site)
@@ -108,7 +118,11 @@ class WooPosLocalCatalogSyncRepositoryTest : BaseUnitTest() {
108118
// GIVEN
109119
val productsSynced = 150
110120
whenever(posSyncProductsAction.execute(any(), anyOrNull(), any(), any()))
111-
.thenReturn(WooPosSyncProductsAction.WooPosSyncProductsResult.Success(productsSynced))
121+
.thenReturn(
122+
WooPosSyncProductsAction.WooPosSyncProductsResult.Success(productsSynced, "2024-01-01T12:00:00Z")
123+
)
124+
whenever(posSyncVariationsAction.execute(any(), anyOrNull(), any(), any()))
125+
.thenReturn(WooPosSyncVariationsAction.WooPosSyncVariationsResult.Success(50, "2024-01-01T12:00:00Z"))
112126

113127
// WHEN
114128
val result = sut.syncLocalCatalogIncremental(site)
@@ -122,7 +136,11 @@ class WooPosLocalCatalogSyncRepositoryTest : BaseUnitTest() {
122136
// GIVEN
123137
val productsSynced = 150
124138
whenever(posSyncProductsAction.execute(any(), anyOrNull(), any(), any()))
125-
.thenReturn(WooPosSyncProductsAction.WooPosSyncProductsResult.Success(productsSynced))
139+
.thenReturn(
140+
WooPosSyncProductsAction.WooPosSyncProductsResult.Success(productsSynced, "2024-01-01T12:00:00Z")
141+
)
142+
whenever(posSyncVariationsAction.execute(any(), anyOrNull(), any(), any()))
143+
.thenReturn(WooPosSyncVariationsAction.WooPosSyncVariationsResult.Success(50, "2024-01-01T12:00:00Z"))
126144

127145
// WHEN
128146
sut.syncLocalCatalogIncremental(site)

WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/localcatalog/WooPosSyncProductsActionTest.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ class WooPosSyncProductsActionTest {
220220

221221
private suspend fun givenSinglePageCatalog(productsCount: Int = PAGE_SIZE / 2) {
222222
whenever(posLocalCatalogStore.executeInTransaction<WooPosSyncProductsResult>(any()))
223-
.thenReturn(KotlinResult.success(WooPosSyncProductsResult.Success(productsCount)))
223+
.thenReturn(KotlinResult.success(WooPosSyncProductsResult.Success(productsCount, "")))
224224

225225
whenever(posLocalCatalogStore.syncRecentlyModifiedProducts(any(), anyOrNull(), eq(0), any(), any()))
226226
.thenReturn(
@@ -238,7 +238,9 @@ class WooPosSyncProductsActionTest {
238238

239239
private suspend fun givenMultiPageCatalog(page1Count: Int, page2Count: Int, page3Count: Int, totalPages: Int = 3) {
240240
whenever(posLocalCatalogStore.executeInTransaction<WooPosSyncProductsResult>(any()))
241-
.thenReturn(KotlinResult.success(WooPosSyncProductsResult.Success(page1Count + page2Count + page3Count)))
241+
.thenReturn(
242+
KotlinResult.success(WooPosSyncProductsResult.Success(page1Count + page2Count + page3Count, ""))
243+
)
242244

243245
whenever(posLocalCatalogStore.syncRecentlyModifiedProducts(any(), anyOrNull(), eq(0), any(), any()))
244246
.thenReturn(
@@ -290,7 +292,7 @@ class WooPosSyncProductsActionTest {
290292

291293
private suspend fun givenEmptyCatalog() {
292294
whenever(posLocalCatalogStore.executeInTransaction<WooPosSyncProductsResult>(any()))
293-
.thenReturn(KotlinResult.success(WooPosSyncProductsResult.Success(0)))
295+
.thenReturn(KotlinResult.success(WooPosSyncProductsResult.Success(0, "")))
294296

295297
whenever(posLocalCatalogStore.syncRecentlyModifiedProducts(any(), anyOrNull(), any(), any(), any()))
296298
.thenReturn(
@@ -346,7 +348,7 @@ class WooPosSyncProductsActionTest {
346348
private suspend fun givenPageWithZeroProductsButHasMore() {
347349
// Mock transaction to return success - this logic is complex so return 0 for simplicity
348350
whenever(posLocalCatalogStore.executeInTransaction<WooPosSyncProductsResult>(any()))
349-
.thenReturn(KotlinResult.success(WooPosSyncProductsResult.Success(0)))
351+
.thenReturn(KotlinResult.success(WooPosSyncProductsResult.Success(0, "")))
350352

351353
// Note: Due to current action logic, it won't continue if syncedCount == 0
352354
// So we use 1 product on first page instead of 0 to demonstrate continuing

0 commit comments

Comments
 (0)