Skip to content

Commit e97000d

Browse files
Merge pull request #5763 from simpledotorg/master
2 parents 3c6352a + e6ab840 commit e97000d

16 files changed

Lines changed: 421 additions & 16 deletions

File tree

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,17 @@
55
### Internal
66

77
- Add `ReturnScore` table
8+
- Add `ReturnScore` sync resource
9+
- Bump Sentry Android to v6.2.0
10+
- Bump AndroidX Benchmark to v1.5.0-alpha04
11+
- Bump AndroidX Paging to v3.4.2
12+
- Bump Play Services Auth to v21.5.1
13+
- Bump Kotlin to v2.3.20
14+
- Bump KSP to v2.3.6
15+
- Bump Sentry to v8.36.0
16+
- Bump dagger to v2.59.2
17+
- Bump Jackson Core to v2.21.1
18+
- Bump Compose BOM to v2026.03.00
819

920
## 2026.03.02
1021

app/src/androidTest/java/org/simple/clinic/di/TestAppComponent.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import org.simple.clinic.patient.onlinelookup.api.LookupPatientOnlineApiIntegrat
4040
import org.simple.clinic.patientattribute.PatientAttributeRepositoryAndroidTest
4141
import org.simple.clinic.protocolv2.ProtocolRepositoryAndroidTest
4242
import org.simple.clinic.protocolv2.sync.ProtocolSyncAndroidTest
43+
import org.simple.clinic.returnscore.ReturnScoreRepositoryAndroidTest
4344
import org.simple.clinic.rules.LocalAuthenticationRule
4445
import org.simple.clinic.rules.RegisterPatientRule
4546
import org.simple.clinic.rules.SaveDatabaseRule
@@ -73,6 +74,7 @@ import org.simple.clinic.sync.ProtocolSyncIntegrationTest
7374
import org.simple.clinic.sync.QuestionnaireResponseSyncIntegrationTest
7475
import org.simple.clinic.sync.QuestionnaireSyncIntegrationTest
7576
import org.simple.clinic.sync.ReportsSyncIntegrationTest
77+
import org.simple.clinic.sync.ReturnScoreSyncIntegrationTest
7678
import org.simple.clinic.sync.TeleconsultationSyncIntegrationTest
7779
import org.simple.clinic.teleconsultlog.teleconsultrecord.TeleconsultRecordRepositoryAndroidTest
7880
import org.simple.clinic.teleconsultlog.teleconsultrecord.TeleconsultRecordSyncIntegrationTest
@@ -170,4 +172,6 @@ interface TestAppComponent {
170172
fun inject(target: PatientAttributeSyncIntegrationTest)
171173
fun inject(target: CVDRiskRepositoryAndroidTest)
172174
fun inject(target: CVDRiskSyncIntegrationTest)
175+
fun inject(target: ReturnScoreRepositoryAndroidTest)
176+
fun inject(target: ReturnScoreSyncIntegrationTest)
173177
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package org.simple.clinic.returnscore
2+
3+
import com.google.common.truth.Truth.assertThat
4+
import org.junit.Before
5+
import org.junit.Rule
6+
import org.junit.Test
7+
import org.junit.rules.RuleChain
8+
import org.simple.clinic.AppDatabase
9+
import org.simple.clinic.TestClinicApp
10+
import org.simple.clinic.TestData
11+
import org.simple.clinic.rules.SaveDatabaseRule
12+
import org.simple.clinic.util.Rules
13+
import java.util.UUID
14+
import javax.inject.Inject
15+
16+
class ReturnScoreRepositoryAndroidTest {
17+
18+
@Inject
19+
lateinit var database: AppDatabase
20+
21+
@Inject
22+
lateinit var returnScoreRepository: ReturnScoreRepository
23+
24+
@get:Rule
25+
val rules: RuleChain = Rules
26+
.global()
27+
.around(SaveDatabaseRule())
28+
29+
@Before
30+
fun setUp() {
31+
TestClinicApp.appComponent().inject(this)
32+
}
33+
34+
@Test
35+
fun saving_return_scores_should_work_correctly() {
36+
// given
37+
val returnScores = listOf(
38+
TestData.returnScore(
39+
uuid = UUID.fromString("ef5b7656-a6df-459c-a5b0-80d100721597"),
40+
),
41+
TestData.returnScore(
42+
uuid = UUID.fromString("ef5b7656-a6df-459c-a5b0-80d123021597"),
43+
)
44+
)
45+
46+
// when
47+
returnScoreRepository.save(returnScores)
48+
49+
// then
50+
val savedReturnScores = returnScoreRepository.returnScoresImmediate()
51+
52+
assertThat(savedReturnScores).isEqualTo(returnScores)
53+
}
54+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package org.simple.clinic.sync
2+
3+
import com.f2prateek.rx.preferences2.Preference
4+
import com.google.common.truth.Truth
5+
import org.junit.Before
6+
import org.junit.Rule
7+
import org.junit.Test
8+
import org.junit.rules.RuleChain
9+
import org.simple.clinic.AppDatabase
10+
import org.simple.clinic.TestClinicApp
11+
import org.simple.clinic.main.TypedPreference
12+
import org.simple.clinic.returnscore.ReturnScoreRepository
13+
import org.simple.clinic.returnscore.sync.ReturnScoreSync
14+
import org.simple.clinic.returnscore.sync.ReturnScoreSyncApi
15+
import org.simple.clinic.rules.SaveDatabaseRule
16+
import org.simple.clinic.rules.ServerAuthenticationRule
17+
import org.simple.clinic.util.Rules
18+
import java.util.Optional
19+
import javax.inject.Inject
20+
21+
class ReturnScoreSyncIntegrationTest {
22+
23+
@Inject
24+
lateinit var appDatabase: AppDatabase
25+
26+
@Inject
27+
lateinit var repository: ReturnScoreRepository
28+
29+
@Inject
30+
@TypedPreference(TypedPreference.Type.LastReturnScorePullToken)
31+
lateinit var lastPullToken: Preference<Optional<String>>
32+
33+
@Inject
34+
lateinit var syncApi: ReturnScoreSyncApi
35+
36+
@Inject
37+
lateinit var syncInterval: SyncInterval
38+
39+
@get:Rule
40+
val ruleChain: RuleChain = Rules
41+
.global()
42+
.around(ServerAuthenticationRule())
43+
.around(SaveDatabaseRule())
44+
45+
private lateinit var sync: ReturnScoreSync
46+
47+
private val batchSize = 1000
48+
private lateinit var config: SyncConfig
49+
50+
@Before
51+
fun setUp() {
52+
TestClinicApp.appComponent().inject(this)
53+
54+
resetLocalData()
55+
56+
config = SyncConfig(
57+
syncInterval = syncInterval,
58+
pullBatchSize = batchSize,
59+
pushBatchSize = batchSize,
60+
name = ""
61+
)
62+
63+
sync = ReturnScoreSync(
64+
syncCoordinator = SyncCoordinator(),
65+
api = syncApi,
66+
repository = repository,
67+
lastPullToken = lastPullToken,
68+
config = config
69+
)
70+
}
71+
72+
private fun resetLocalData() {
73+
clearReturnScoreDao()
74+
lastPullToken.delete()
75+
}
76+
77+
private fun clearReturnScoreDao() {
78+
appDatabase.returnScoreDao().clear()
79+
}
80+
81+
@Test
82+
fun syncing_records_should_work_as_expected() {
83+
// when
84+
Truth.assertThat(repository.recordCount().blockingFirst()).isEqualTo(0)
85+
sync.pull()
86+
87+
// then
88+
val pulledRecords = repository.returnScoresImmediate()
89+
90+
Truth.assertThat(pulledRecords).isNotEmpty()
91+
}
92+
}

app/src/main/java/org/simple/clinic/main/TypedPreference.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@ annotation class TypedPreference(val value: Type) {
2525
DataProtectionConsent,
2626
LastPatientAttributePullToken,
2727
LastCVDRiskPullToken,
28+
LastReturnScorePullToken
2829
}
2930
}

app/src/main/java/org/simple/clinic/returnscore/ReturnScore.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import androidx.room.OnConflictStrategy
1010
import androidx.room.PrimaryKey
1111
import androidx.room.Query
1212
import io.reactivex.Flowable
13+
import io.reactivex.Observable
1314
import kotlinx.parcelize.Parcelize
1415
import org.simple.clinic.storage.Timestamps
1516
import java.util.UUID
@@ -41,6 +42,15 @@ data class ReturnScore(
4142
@Query("SELECT * FROM ReturnScore WHERE deletedAt IS NULL")
4243
fun getAll(): Flowable<List<ReturnScore>>
4344

45+
@Query("SELECT * FROM ReturnScore WHERE deletedAt IS NULL")
46+
fun getAllImmediate(): List<ReturnScore>
47+
48+
@Query("SELECT * FROM ReturnScore WHERE scoreType == :type AND deletedAt IS NULL LIMIT 1")
49+
fun getByScoreType(type: ScoreType): Flowable<List<ReturnScore>>
50+
51+
@Query("SELECT COUNT(uuid) FROM ReturnScore")
52+
fun count(): Observable<Int>
53+
4454
@Query("DELETE FROM returnscore")
4555
fun clear(): Int
4656

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package org.simple.clinic.returnscore
2+
3+
import io.reactivex.Observable
4+
import org.simple.clinic.di.AppScope
5+
import org.simple.clinic.patient.SyncStatus
6+
import org.simple.clinic.returnscore.sync.ReturnScorePayload
7+
import org.simple.clinic.sync.SynceableRepository
8+
import java.util.UUID
9+
import javax.inject.Inject
10+
11+
@AppScope
12+
class ReturnScoreRepository @Inject constructor(
13+
private val dao: ReturnScore.RoomDao
14+
) : SynceableRepository<ReturnScore, ReturnScorePayload> {
15+
16+
override fun save(records: List<ReturnScore>) {
17+
saveRecords(records)
18+
}
19+
20+
override fun setSyncStatus(from: SyncStatus, to: SyncStatus) {
21+
// no-op
22+
}
23+
24+
override fun setSyncStatus(ids: List<UUID>, to: SyncStatus) {
25+
// no-op
26+
}
27+
28+
override fun mergeWithLocalData(payloads: List<ReturnScorePayload>) {
29+
val records = payloads
30+
.map { it.toDatabaseModel() }
31+
32+
saveRecords(records)
33+
}
34+
35+
override fun recordCount(): Observable<Int> {
36+
return dao.count()
37+
}
38+
39+
override fun pendingSyncRecordCount(): Observable<Int> {
40+
return Observable.just(0)
41+
}
42+
43+
override fun pendingSyncRecords(limit: Int, offset: Int): List<ReturnScore> {
44+
return emptyList()
45+
}
46+
47+
private fun saveRecords(records: List<ReturnScore>) {
48+
dao.save(records)
49+
}
50+
51+
fun returnScores(): Observable<List<ReturnScore>> {
52+
return dao.getAll().toObservable()
53+
}
54+
55+
fun returnScoresImmediate(): List<ReturnScore> {
56+
return dao.getAllImmediate()
57+
}
58+
59+
fun returnScoresByType(type: ScoreType): Observable<List<ReturnScore>> {
60+
return dao.getByScoreType(type).toObservable()
61+
}
62+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.simple.clinic.returnscore.di
2+
3+
import com.f2prateek.rx.preferences2.Preference
4+
import com.f2prateek.rx.preferences2.RxSharedPreferences
5+
import dagger.Module
6+
import dagger.Provides
7+
import org.simple.clinic.AppDatabase
8+
import org.simple.clinic.main.TypedPreference
9+
import org.simple.clinic.returnscore.ReturnScore
10+
import org.simple.clinic.returnscore.sync.ReturnScoreSyncApi
11+
import org.simple.clinic.util.preference.StringPreferenceConverter
12+
import org.simple.clinic.util.preference.getOptional
13+
import retrofit2.Retrofit
14+
import java.util.Optional
15+
import javax.inject.Named
16+
17+
@Module
18+
open class ReturnScoreModule {
19+
20+
@Provides
21+
fun dao(appDatabase: AppDatabase): ReturnScore.RoomDao {
22+
return appDatabase.returnScoreDao()
23+
}
24+
25+
@Provides
26+
fun syncApi(@Named("for_deployment") retrofit: Retrofit): ReturnScoreSyncApi {
27+
return retrofit.create(ReturnScoreSyncApi::class.java)
28+
}
29+
30+
@Provides
31+
@TypedPreference(TypedPreference.Type.LastReturnScorePullToken)
32+
fun lastPullToken(rxSharedPrefs: RxSharedPreferences): Preference<Optional<String>> {
33+
return rxSharedPrefs.getOptional("last_return_score_pull_token_v1", StringPreferenceConverter())
34+
}
35+
}
36+
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package org.simple.clinic.returnscore.sync
2+
3+
import com.squareup.moshi.Json
4+
import com.squareup.moshi.JsonClass
5+
import org.simple.clinic.returnscore.ReturnScore
6+
import org.simple.clinic.returnscore.ScoreType
7+
import org.simple.clinic.storage.Timestamps
8+
import java.time.Instant
9+
import java.util.UUID
10+
11+
@JsonClass(generateAdapter = true)
12+
data class ReturnScorePayload(
13+
@Json(name = "id")
14+
val uuid: UUID,
15+
16+
@Json(name = "patient_id")
17+
val patientUuid: UUID,
18+
19+
@Json(name = "score_type")
20+
val scoreType: ScoreType,
21+
22+
@Json(name = "score_value")
23+
val scoreValue: Float,
24+
25+
@Json(name = "created_at")
26+
val createdAt: Instant,
27+
28+
@Json(name = "updated_at")
29+
val updatedAt: Instant,
30+
31+
@Json(name = "deleted_at")
32+
val deletedAt: Instant?,
33+
) {
34+
35+
fun toDatabaseModel() = ReturnScore(
36+
uuid = uuid,
37+
patientUuid = patientUuid,
38+
scoreType = scoreType,
39+
scoreValue = scoreValue,
40+
timestamps = Timestamps(
41+
createdAt = createdAt,
42+
updatedAt = updatedAt,
43+
deletedAt = deletedAt
44+
)
45+
)
46+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.simple.clinic.returnscore.sync
2+
3+
import com.squareup.moshi.Json
4+
import com.squareup.moshi.JsonClass
5+
import org.simple.clinic.sync.DataPullResponse
6+
7+
@JsonClass(generateAdapter = true)
8+
data class ReturnScorePullResponse(
9+
10+
@Json(name = "patient_scores")
11+
override val payloads: List<ReturnScorePayload>,
12+
13+
@Json(name = "process_token")
14+
override val processToken: String
15+
16+
) : DataPullResponse<ReturnScorePayload>

0 commit comments

Comments
 (0)