Compare commits

...
4 Commits
6 changed files with 76 additions and 7 deletions
+1 -1
View File
@@ -34,7 +34,7 @@ android {
applicationId = "pl.firmatpp.kierowca" applicationId = "pl.firmatpp.kierowca"
minSdk = 26 minSdk = 26
targetSdk = 35 targetSdk = 35
versionCode = 47 versionCode = 50
versionName = "1.0.44" versionName = "1.0.44"
setProperty("archivesBaseName", "pl.firmatpp.kierowca") setProperty("archivesBaseName", "pl.firmatpp.kierowca")
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
@@ -18,7 +18,7 @@ interface RouteActionDao {
""" """
SELECT * FROM route_actions SELECT * FROM route_actions
WHERE routeId = :routeId WHERE routeId = :routeId
AND status IN ('PENDING', 'WAITING_FOR_PHOTOS', 'SYNCING', 'FAILED_RETRYABLE', 'FAILED_CONFLICT', 'FAILED_PERMANENT') AND status IN ('PENDING', 'WAITING_FOR_PHOTOS', 'SYNCING', 'CONFIRMED', 'FAILED_RETRYABLE', 'FAILED_CONFLICT', 'FAILED_PERMANENT')
ORDER BY createdAtEpochMillis DESC ORDER BY createdAtEpochMillis DESC
""", """,
) )
@@ -28,6 +28,14 @@ interface RouteActionDao {
""" """
SELECT * FROM route_actions SELECT * FROM route_actions
WHERE status IN ('PENDING', 'WAITING_FOR_PHOTOS', 'SYNCING', 'FAILED_RETRYABLE', 'FAILED_CONFLICT', 'FAILED_PERMANENT') WHERE status IN ('PENDING', 'WAITING_FOR_PHOTOS', 'SYNCING', 'FAILED_RETRYABLE', 'FAILED_CONFLICT', 'FAILED_PERMANENT')
OR clientActionId IN (
SELECT confirmed.clientActionId
FROM route_actions AS confirmed
WHERE confirmed.status = 'CONFIRMED'
AND confirmed.routeId = route_actions.routeId
ORDER BY confirmed.createdAtEpochMillis DESC
LIMIT 2
)
ORDER BY createdAtEpochMillis DESC ORDER BY createdAtEpochMillis DESC
""", """,
) )
@@ -1,6 +1,7 @@
package pl.firmatpp.kierowca.ui package pl.firmatpp.kierowca.ui
import pl.firmatpp.kierowca.data.model.DriverRouteDto import pl.firmatpp.kierowca.data.model.DriverRouteDto
import pl.firmatpp.kierowca.data.model.RoutePhotoDto
import pl.firmatpp.kierowca.data.upload.RouteActionEntity import pl.firmatpp.kierowca.data.upload.RouteActionEntity
import pl.firmatpp.kierowca.data.upload.RouteActionStatus import pl.firmatpp.kierowca.data.upload.RouteActionStatus
import pl.firmatpp.kierowca.data.upload.RouteActionType import pl.firmatpp.kierowca.data.upload.RouteActionType
@@ -21,14 +22,18 @@ internal val routeActionVisibleStatuses = setOf(
RouteActionStatus.FailedPermanent, RouteActionStatus.FailedPermanent,
) )
private val routeActionProjectionStatuses = routeActionVisibleStatuses + RouteActionStatus.Confirmed
fun projectDriverRoute(route: DriverRouteDto, actions: List<RouteActionEntity>): ProjectedDriverRoute { fun projectDriverRoute(route: DriverRouteDto, actions: List<RouteActionEntity>): ProjectedDriverRoute {
val visible = actions val visible = actions
.filter { it.routeId == route.id && it.status in routeActionVisibleStatuses } .filter { it.routeId == route.id && it.status in routeActionVisibleStatuses }
val projectable = actions
.filter { it.routeId == route.id && it.status in routeActionProjectionStatuses }
var projectedRoute = route var projectedRoute = route
var loadingWeightPending = false var loadingWeightPending = false
var unloadingWeightPending = false var unloadingWeightPending = false
visible projectable
.sortedWith(compareBy<RouteActionEntity> { it.createdAtEpochMillis }.thenBy { it.clientActionId }) .sortedWith(compareBy<RouteActionEntity> { it.createdAtEpochMillis }.thenBy { it.clientActionId })
.forEach { action -> .forEach { action ->
when (action.action) { when (action.action) {
@@ -37,8 +42,9 @@ fun projectDriverRoute(route: DriverRouteDto, actions: List<RouteActionEntity>):
status = "W TRAKCIE", status = "W TRAKCIE",
loadingWeight = action.weight, loadingWeight = action.weight,
trackingStatus = "active", trackingStatus = "active",
photos = projectedRoute.safePhotos(),
) )
loadingWeightPending = true loadingWeightPending = action.status != RouteActionStatus.Confirmed
} }
RouteActionType.Finish -> { RouteActionType.Finish -> {
projectedRoute = projectedRoute.copy( projectedRoute = projectedRoute.copy(
@@ -46,8 +52,9 @@ fun projectDriverRoute(route: DriverRouteDto, actions: List<RouteActionEntity>):
unloadingWeight = action.weight, unloadingWeight = action.weight,
trackingStatus = "finished", trackingStatus = "finished",
completedAt = action.occurredAt, completedAt = action.occurredAt,
photos = projectedRoute.safePhotos(),
) )
unloadingWeightPending = true unloadingWeightPending = action.status != RouteActionStatus.Confirmed
} }
} }
} }
@@ -67,3 +74,5 @@ fun projectDriverRoutes(routes: List<DriverRouteDto>, actions: List<RouteActionE
fun routeActionNeedsAttention(status: String): Boolean = fun routeActionNeedsAttention(status: String): Boolean =
status == RouteActionStatus.FailedConflict || status == RouteActionStatus.FailedPermanent status == RouteActionStatus.FailedConflict || status == RouteActionStatus.FailedPermanent
internal fun DriverRouteDto.safePhotos(): List<RoutePhotoDto> = photos.orEmpty()
@@ -151,9 +151,9 @@ fun routeFlowSteps(route: DriverRouteDto, actions: List<RouteActionEntity>): Lis
) )
} }
fun routePhotosForStage(photos: List<RoutePhotoDto>, stage: String): List<RoutePhotoDto> { fun routePhotosForStage(photos: List<RoutePhotoDto>?, stage: String): List<RoutePhotoDto> {
val normalized = normalizedRoutePhotoStage(stage) val normalized = normalizedRoutePhotoStage(stage)
return photos.filter { normalizedRoutePhotoStage(it.stage) == normalized } return photos.orEmpty().filter { normalizedRoutePhotoStage(it.stage) == normalized }
} }
fun normalizedRoutePhotoStage(stage: String?): String = fun normalizedRoutePhotoStage(stage: String?): String =
@@ -617,6 +617,7 @@ class DriverViewModel(application: Application) : AndroidViewModel(application)
status = "W TRAKCIE", status = "W TRAKCIE",
loadingWeight = weight, loadingWeight = weight,
trackingStatus = "active", trackingStatus = "active",
photos = route.safePhotos(),
) )
} }
"unloading" -> { "unloading" -> {
@@ -626,6 +627,7 @@ class DriverViewModel(application: Application) : AndroidViewModel(application)
unloadingWeight = weight, unloadingWeight = weight,
trackingStatus = "finished", trackingStatus = "finished",
completedAt = Instant.now().toString(), completedAt = Instant.now().toString(),
photos = route.safePhotos(),
) )
} }
else -> route else -> route
@@ -1,11 +1,13 @@
package pl.firmatpp.kierowca.ui package pl.firmatpp.kierowca.ui
import com.google.gson.Gson
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Test import org.junit.Test
import pl.firmatpp.kierowca.data.model.DriverRouteDto import pl.firmatpp.kierowca.data.model.DriverRouteDto
import pl.firmatpp.kierowca.data.model.RoutePhotoDto
import pl.firmatpp.kierowca.data.upload.RouteActionEntity import pl.firmatpp.kierowca.data.upload.RouteActionEntity
import pl.firmatpp.kierowca.data.upload.RouteActionStatus import pl.firmatpp.kierowca.data.upload.RouteActionStatus
import pl.firmatpp.kierowca.data.upload.RouteActionType import pl.firmatpp.kierowca.data.upload.RouteActionType
@@ -57,6 +59,54 @@ class DriverRouteProjectionTest {
assertEquals("finished", projection.route.trackingStatus) assertEquals("finished", projection.route.trackingStatus)
} }
@Test
fun confirmedFinishStillProjectsPlannedRouteAsFinishedWithoutSyncCallout() {
val projection = projectDriverRoute(
route = route(status = "ZAPLANOWANA"),
actions = listOf(
action(RouteActionType.Finish, RouteActionStatus.Confirmed, weight = 11.8, createdAt = 2000),
),
)
assertEquals("ZAKOŃCZONA", projection.route.status)
assertEquals(11.8, projection.route.unloadingWeight)
assertEquals("finished", projection.route.trackingStatus)
assertFalse(projection.unloadingWeightPending)
assertEquals(emptyList<RouteActionEntity>(), projection.visibleActions)
}
@Test
fun routeProjectionNormalizesNullPhotosFromCachedJsonBeforeCopy() {
val route = Gson().fromJson(
"""
{
"id": "1",
"startsAt": "",
"originName": "Baza",
"destinationName": "Instalacja",
"contractorName": "TPP",
"contractName": "Kontrakt",
"contractCode": "TPP-1",
"relationLabel": "Baza -> Instalacja",
"status": "ZAPLANOWANA",
"distanceKm": 1.0,
"notes": null,
"truck": null,
"photos": null
}
""".trimIndent(),
DriverRouteDto::class.java,
)
val projection = projectDriverRoute(
route = route,
actions = listOf(action(RouteActionType.Start, RouteActionStatus.Confirmed, weight = 12.5)),
)
assertEquals("W TRAKCIE", projection.route.status)
assertEquals(emptyList<RoutePhotoDto>(), projection.route.photos)
}
@Test @Test
fun conflictKeepsLocalRouteValuesAndMarksStageAsAttention() { fun conflictKeepsLocalRouteValuesAndMarksStageAsAttention() {
val projection = projectDriverRoute( val projection = projectDriverRoute(