Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@
### Internal

- Add `ReturnScore` table
- Add `ReturnScore` sync resource
- Bump Sentry Android to v6.2.0
- Bump AndroidX Benchmark to v1.5.0-alpha04
- Bump AndroidX Paging to v3.4.2
- Bump Play Services Auth to v21.5.1
- Bump Kotlin to v2.3.20
- Bump KSP to v2.3.6
- Bump Sentry to v8.36.0
- Bump dagger to v2.59.2
- Bump Jackson Core to v2.21.1
- Bump Compose BOM to v2026.03.00

## 2026.03.02

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import org.simple.clinic.patient.onlinelookup.api.LookupPatientOnlineApiIntegrat
import org.simple.clinic.patientattribute.PatientAttributeRepositoryAndroidTest
import org.simple.clinic.protocolv2.ProtocolRepositoryAndroidTest
import org.simple.clinic.protocolv2.sync.ProtocolSyncAndroidTest
import org.simple.clinic.returnscore.ReturnScoreRepositoryAndroidTest
import org.simple.clinic.rules.LocalAuthenticationRule
import org.simple.clinic.rules.RegisterPatientRule
import org.simple.clinic.rules.SaveDatabaseRule
Expand Down Expand Up @@ -73,6 +74,7 @@ import org.simple.clinic.sync.ProtocolSyncIntegrationTest
import org.simple.clinic.sync.QuestionnaireResponseSyncIntegrationTest
import org.simple.clinic.sync.QuestionnaireSyncIntegrationTest
import org.simple.clinic.sync.ReportsSyncIntegrationTest
import org.simple.clinic.sync.ReturnScoreSyncIntegrationTest
import org.simple.clinic.sync.TeleconsultationSyncIntegrationTest
import org.simple.clinic.teleconsultlog.teleconsultrecord.TeleconsultRecordRepositoryAndroidTest
import org.simple.clinic.teleconsultlog.teleconsultrecord.TeleconsultRecordSyncIntegrationTest
Expand Down Expand Up @@ -170,4 +172,6 @@ interface TestAppComponent {
fun inject(target: PatientAttributeSyncIntegrationTest)
fun inject(target: CVDRiskRepositoryAndroidTest)
fun inject(target: CVDRiskSyncIntegrationTest)
fun inject(target: ReturnScoreRepositoryAndroidTest)
fun inject(target: ReturnScoreSyncIntegrationTest)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.simple.clinic.returnscore

import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.simple.clinic.AppDatabase
import org.simple.clinic.TestClinicApp
import org.simple.clinic.TestData
import org.simple.clinic.rules.SaveDatabaseRule
import org.simple.clinic.util.Rules
import java.util.UUID
import javax.inject.Inject

class ReturnScoreRepositoryAndroidTest {

@Inject
lateinit var database: AppDatabase

@Inject
lateinit var returnScoreRepository: ReturnScoreRepository

@get:Rule
val rules: RuleChain = Rules
.global()
.around(SaveDatabaseRule())

@Before
fun setUp() {
TestClinicApp.appComponent().inject(this)
}

@Test
fun saving_return_scores_should_work_correctly() {
// given
val returnScores = listOf(
TestData.returnScore(
uuid = UUID.fromString("ef5b7656-a6df-459c-a5b0-80d100721597"),
),
TestData.returnScore(
uuid = UUID.fromString("ef5b7656-a6df-459c-a5b0-80d123021597"),
)
)

// when
returnScoreRepository.save(returnScores)

// then
val savedReturnScores = returnScoreRepository.returnScoresImmediate()

assertThat(savedReturnScores).isEqualTo(returnScores)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package org.simple.clinic.sync

import com.f2prateek.rx.preferences2.Preference
import com.google.common.truth.Truth
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.simple.clinic.AppDatabase
import org.simple.clinic.TestClinicApp
import org.simple.clinic.main.TypedPreference
import org.simple.clinic.returnscore.ReturnScoreRepository
import org.simple.clinic.returnscore.sync.ReturnScoreSync
import org.simple.clinic.returnscore.sync.ReturnScoreSyncApi
import org.simple.clinic.rules.SaveDatabaseRule
import org.simple.clinic.rules.ServerAuthenticationRule
import org.simple.clinic.util.Rules
import java.util.Optional
import javax.inject.Inject

class ReturnScoreSyncIntegrationTest {

@Inject
lateinit var appDatabase: AppDatabase

@Inject
lateinit var repository: ReturnScoreRepository

@Inject
@TypedPreference(TypedPreference.Type.LastReturnScorePullToken)
lateinit var lastPullToken: Preference<Optional<String>>

@Inject
lateinit var syncApi: ReturnScoreSyncApi

@Inject
lateinit var syncInterval: SyncInterval

@get:Rule
val ruleChain: RuleChain = Rules
.global()
.around(ServerAuthenticationRule())
.around(SaveDatabaseRule())

private lateinit var sync: ReturnScoreSync

private val batchSize = 1000
private lateinit var config: SyncConfig

@Before
fun setUp() {
TestClinicApp.appComponent().inject(this)

resetLocalData()

config = SyncConfig(
syncInterval = syncInterval,
pullBatchSize = batchSize,
pushBatchSize = batchSize,
name = ""
)

sync = ReturnScoreSync(
syncCoordinator = SyncCoordinator(),
api = syncApi,
repository = repository,
lastPullToken = lastPullToken,
config = config
)
}

private fun resetLocalData() {
clearReturnScoreDao()
lastPullToken.delete()
}

private fun clearReturnScoreDao() {
appDatabase.returnScoreDao().clear()
}

@Test
fun syncing_records_should_work_as_expected() {
// when
Truth.assertThat(repository.recordCount().blockingFirst()).isEqualTo(0)
sync.pull()

// then
val pulledRecords = repository.returnScoresImmediate()

Truth.assertThat(pulledRecords).isNotEmpty()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ annotation class TypedPreference(val value: Type) {
DataProtectionConsent,
LastPatientAttributePullToken,
LastCVDRiskPullToken,
LastReturnScorePullToken
}
}
10 changes: 10 additions & 0 deletions app/src/main/java/org/simple/clinic/returnscore/ReturnScore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import androidx.room.OnConflictStrategy
import androidx.room.PrimaryKey
import androidx.room.Query
import io.reactivex.Flowable
import io.reactivex.Observable
import kotlinx.parcelize.Parcelize
import org.simple.clinic.storage.Timestamps
import java.util.UUID
Expand Down Expand Up @@ -41,6 +42,15 @@ data class ReturnScore(
@Query("SELECT * FROM ReturnScore WHERE deletedAt IS NULL")
fun getAll(): Flowable<List<ReturnScore>>

@Query("SELECT * FROM ReturnScore WHERE deletedAt IS NULL")
fun getAllImmediate(): List<ReturnScore>

@Query("SELECT * FROM ReturnScore WHERE scoreType == :type AND deletedAt IS NULL LIMIT 1")
fun getByScoreType(type: ScoreType): Flowable<List<ReturnScore>>

@Query("SELECT COUNT(uuid) FROM ReturnScore")
fun count(): Observable<Int>

@Query("DELETE FROM returnscore")
fun clear(): Int

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.simple.clinic.returnscore

import io.reactivex.Observable
import org.simple.clinic.di.AppScope
import org.simple.clinic.patient.SyncStatus
import org.simple.clinic.returnscore.sync.ReturnScorePayload
import org.simple.clinic.sync.SynceableRepository
import java.util.UUID
import javax.inject.Inject

@AppScope
class ReturnScoreRepository @Inject constructor(
private val dao: ReturnScore.RoomDao
) : SynceableRepository<ReturnScore, ReturnScorePayload> {

override fun save(records: List<ReturnScore>) {
saveRecords(records)
}

override fun setSyncStatus(from: SyncStatus, to: SyncStatus) {
// no-op
}

override fun setSyncStatus(ids: List<UUID>, to: SyncStatus) {
// no-op
}

override fun mergeWithLocalData(payloads: List<ReturnScorePayload>) {
val records = payloads
.map { it.toDatabaseModel() }

saveRecords(records)
}

override fun recordCount(): Observable<Int> {
return dao.count()
}

override fun pendingSyncRecordCount(): Observable<Int> {
return Observable.just(0)
}

override fun pendingSyncRecords(limit: Int, offset: Int): List<ReturnScore> {
return emptyList()
}

private fun saveRecords(records: List<ReturnScore>) {
dao.save(records)
}

fun returnScores(): Observable<List<ReturnScore>> {
return dao.getAll().toObservable()
}

fun returnScoresImmediate(): List<ReturnScore> {
return dao.getAllImmediate()
}

fun returnScoresByType(type: ScoreType): Observable<List<ReturnScore>> {
return dao.getByScoreType(type).toObservable()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.simple.clinic.returnscore.di

import com.f2prateek.rx.preferences2.Preference
import com.f2prateek.rx.preferences2.RxSharedPreferences
import dagger.Module
import dagger.Provides
import org.simple.clinic.AppDatabase
import org.simple.clinic.main.TypedPreference
import org.simple.clinic.returnscore.ReturnScore
import org.simple.clinic.returnscore.sync.ReturnScoreSyncApi
import org.simple.clinic.util.preference.StringPreferenceConverter
import org.simple.clinic.util.preference.getOptional
import retrofit2.Retrofit
import java.util.Optional
import javax.inject.Named

@Module
open class ReturnScoreModule {

@Provides
fun dao(appDatabase: AppDatabase): ReturnScore.RoomDao {
return appDatabase.returnScoreDao()
}

@Provides
fun syncApi(@Named("for_deployment") retrofit: Retrofit): ReturnScoreSyncApi {
return retrofit.create(ReturnScoreSyncApi::class.java)
}

@Provides
@TypedPreference(TypedPreference.Type.LastReturnScorePullToken)
fun lastPullToken(rxSharedPrefs: RxSharedPreferences): Preference<Optional<String>> {
return rxSharedPrefs.getOptional("last_return_score_pull_token_v1", StringPreferenceConverter())
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.simple.clinic.returnscore.sync

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import org.simple.clinic.returnscore.ReturnScore
import org.simple.clinic.returnscore.ScoreType
import org.simple.clinic.storage.Timestamps
import java.time.Instant
import java.util.UUID

@JsonClass(generateAdapter = true)
data class ReturnScorePayload(
@Json(name = "id")
val uuid: UUID,

@Json(name = "patient_id")
val patientUuid: UUID,

@Json(name = "score_type")
val scoreType: ScoreType,

@Json(name = "score_value")
val scoreValue: Float,

@Json(name = "created_at")
val createdAt: Instant,

@Json(name = "updated_at")
val updatedAt: Instant,

@Json(name = "deleted_at")
val deletedAt: Instant?,
) {

fun toDatabaseModel() = ReturnScore(
uuid = uuid,
patientUuid = patientUuid,
scoreType = scoreType,
scoreValue = scoreValue,
timestamps = Timestamps(
createdAt = createdAt,
updatedAt = updatedAt,
deletedAt = deletedAt
)
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.simple.clinic.returnscore.sync

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import org.simple.clinic.sync.DataPullResponse

@JsonClass(generateAdapter = true)
data class ReturnScorePullResponse(

@Json(name = "patient_scores")
override val payloads: List<ReturnScorePayload>,

@Json(name = "process_token")
override val processToken: String

) : DataPullResponse<ReturnScorePayload>
Loading
Loading