From 4a06e9da8b1cd42406a795901c3fbfac6c57ba0c Mon Sep 17 00:00:00 2001 From: admin Date: Wed, 8 Jul 2026 02:01:37 +0200 Subject: [PATCH] Retry route actions after photo upload --- .../kierowca/data/upload/PhotoUploadWorker.kt | 12 ++++++- .../kierowca/data/upload/RouteActionDao.kt | 3 ++ .../kierowca/data/upload/RouteActionOutbox.kt | 2 +- .../upload/RouteActionPhotoDependencies.kt | 17 ++++++++++ .../pl/firmatpp/kierowca/ui/DriverUiRules.kt | 4 ++- .../RouteActionPhotoDependenciesTest.kt | 31 +++++++++++++++++++ 6 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/pl/firmatpp/kierowca/data/upload/RouteActionPhotoDependencies.kt create mode 100644 app/src/test/java/pl/firmatpp/kierowca/data/upload/RouteActionPhotoDependenciesTest.kt diff --git a/app/src/main/java/pl/firmatpp/kierowca/data/upload/PhotoUploadWorker.kt b/app/src/main/java/pl/firmatpp/kierowca/data/upload/PhotoUploadWorker.kt index 13e1e0b..92178db 100644 --- a/app/src/main/java/pl/firmatpp/kierowca/data/upload/PhotoUploadWorker.kt +++ b/app/src/main/java/pl/firmatpp/kierowca/data/upload/PhotoUploadWorker.kt @@ -14,7 +14,10 @@ class PhotoUploadWorker( appContext: Context, params: WorkerParameters, ) : CoroutineWorker(appContext, params) { - private val dao = DriverDatabase.get(appContext).photoUploadDao() + private val database = DriverDatabase.get(appContext) + private val dao = database.photoUploadDao() + private val routeActionDao = database.routeActionDao() + private val routeActionOutbox = RouteActionOutbox(appContext) private val repository = DriverRepository(appContext) override suspend fun doWork(): Result { @@ -62,6 +65,7 @@ class PhotoUploadWorker( } dao.markConfirmed(clientRequestId, receipt.serverPhotoId) + enqueueRouteActionsWaitingForPhoto(clientRequestId) Result.success() }.getOrElse { throwable -> val error = ApiErrorMapper.map(throwable) @@ -90,6 +94,12 @@ class PhotoUploadWorker( } } + private suspend fun enqueueRouteActionsWaitingForPhoto(clientRequestId: String) { + routeActionDao.waitingForPhotosActions() + .filter { routeActionReferencesPhotoClientRequest(it, clientRequestId) } + .forEach { routeActionOutbox.enqueueWorker(it.clientActionId) } + } + companion object { const val KEY_CLIENT_REQUEST_ID = "clientRequestId" const val KEY_PROGRESS = "progress" diff --git a/app/src/main/java/pl/firmatpp/kierowca/data/upload/RouteActionDao.kt b/app/src/main/java/pl/firmatpp/kierowca/data/upload/RouteActionDao.kt index 46c3d05..b0cbf41 100644 --- a/app/src/main/java/pl/firmatpp/kierowca/data/upload/RouteActionDao.kt +++ b/app/src/main/java/pl/firmatpp/kierowca/data/upload/RouteActionDao.kt @@ -14,6 +14,9 @@ interface RouteActionDao { @Query("SELECT * FROM route_actions WHERE clientActionId = :clientActionId LIMIT 1") suspend fun find(clientActionId: String): RouteActionEntity? + @Query("SELECT * FROM route_actions WHERE status = 'WAITING_FOR_PHOTOS'") + suspend fun waitingForPhotosActions(): List + @Query( """ SELECT * FROM route_actions diff --git a/app/src/main/java/pl/firmatpp/kierowca/data/upload/RouteActionOutbox.kt b/app/src/main/java/pl/firmatpp/kierowca/data/upload/RouteActionOutbox.kt index 8ed81a0..305ea0d 100644 --- a/app/src/main/java/pl/firmatpp/kierowca/data/upload/RouteActionOutbox.kt +++ b/app/src/main/java/pl/firmatpp/kierowca/data/upload/RouteActionOutbox.kt @@ -53,7 +53,7 @@ class RouteActionOutbox( return entity } - private fun enqueueWorker(clientActionId: String) { + fun enqueueWorker(clientActionId: String) { val request = OneTimeWorkRequestBuilder() .setInputData(workDataOf(RouteActionWorker.KEY_CLIENT_ACTION_ID to clientActionId)) .setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()) diff --git a/app/src/main/java/pl/firmatpp/kierowca/data/upload/RouteActionPhotoDependencies.kt b/app/src/main/java/pl/firmatpp/kierowca/data/upload/RouteActionPhotoDependencies.kt new file mode 100644 index 0000000..3416845 --- /dev/null +++ b/app/src/main/java/pl/firmatpp/kierowca/data/upload/RouteActionPhotoDependencies.kt @@ -0,0 +1,17 @@ +package pl.firmatpp.kierowca.data.upload + +import com.google.gson.Gson + +private val routeActionPhotoDependencyGson = Gson() + +fun routeActionReferencesPhotoClientRequest(action: RouteActionEntity, clientRequestId: String): Boolean { + val needle = clientRequestId.trim() + if (needle.isBlank()) return false + + return runCatching { + routeActionPhotoDependencyGson + .fromJson(action.photoClientRequestIdsJson, Array::class.java) + ?.any { it.trim() == needle } + ?: false + }.getOrDefault(false) +} diff --git a/app/src/main/java/pl/firmatpp/kierowca/ui/DriverUiRules.kt b/app/src/main/java/pl/firmatpp/kierowca/ui/DriverUiRules.kt index b27309c..eb84a53 100644 --- a/app/src/main/java/pl/firmatpp/kierowca/ui/DriverUiRules.kt +++ b/app/src/main/java/pl/firmatpp/kierowca/ui/DriverUiRules.kt @@ -114,8 +114,10 @@ fun routeSyncCallout(actions: List): RouteSyncCalloutUi? { } else { when (unresolved.status) { RouteActionStatus.WaitingForPhotos -> "Czekam na wysłanie zdjęć etapu, potem wyślę zmianę statusu." + RouteActionStatus.Pending, + RouteActionStatus.Syncing -> "Trwa wysyłanie zmiany statusu kursu." RouteActionStatus.FailedRetryable -> "Nie udało się wysłać. Aplikacja spróbuje ponownie." - else -> "Zmiana zapisana w telefonie. Wyślemy ją po odzyskaniu internetu." + else -> "Zmiana zapisana w telefonie. Aplikacja wyśle ją automatycznie." } }, isConflict = isConflict, diff --git a/app/src/test/java/pl/firmatpp/kierowca/data/upload/RouteActionPhotoDependenciesTest.kt b/app/src/test/java/pl/firmatpp/kierowca/data/upload/RouteActionPhotoDependenciesTest.kt new file mode 100644 index 0000000..79b22ad --- /dev/null +++ b/app/src/test/java/pl/firmatpp/kierowca/data/upload/RouteActionPhotoDependenciesTest.kt @@ -0,0 +1,31 @@ +package pl.firmatpp.kierowca.data.upload + +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class RouteActionPhotoDependenciesTest { + @Test + fun detectsRouteActionWaitingForConfirmedPhotoClientRequest() { + val action = action("""["photo-a","photo-b"]""") + + assertTrue(routeActionReferencesPhotoClientRequest(action, "photo-b")) + assertFalse(routeActionReferencesPhotoClientRequest(action, "photo-c")) + } + + @Test + fun malformedPhotoRequestJsonDoesNotMatch() { + assertFalse(routeActionReferencesPhotoClientRequest(action("not-json"), "photo-a")) + } + + private fun action(photoClientRequestIdsJson: String): RouteActionEntity = + RouteActionEntity( + clientActionId = "action-1", + routeId = "route-1", + action = RouteActionType.Start, + weight = 12.5, + occurredAt = "2026-06-30T07:40:00Z", + photoClientRequestIdsJson = photoClientRequestIdsJson, + status = RouteActionStatus.WaitingForPhotos, + ) +}