Skip to content

Commit 8df5644

Browse files
Merge pull request #5731 from simpledotorg/master
2 parents 79afbf1 + c80d877 commit 8df5644

27 files changed

Lines changed: 619 additions & 95 deletions

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@
44

55
### Internal
66

7-
-Update `ci-checks.yml` & `story-link-check.sh` to verify valid jira reference
7+
- Update `ci-checks.yml` & `story-link-check.sh` to verify valid jira reference
8+
- Add effect to fetch complete medical records
9+
- Bump sqlcipher to v4.13.0
10+
- Bump dagger to v2.59.1
11+
12+
### Changes
13+
14+
- Add `Sync Medical Records` button on setting page behind feature flag
815

916
## 2026.02.02
1017

app/src/androidTest/java/org/simple/clinic/settings/SettingsScreenTest.kt

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class SettingsScreenTest {
1818

1919
private val defaultSettingsModel = SettingsModel.default(
2020
isChangeLanguageFeatureEnabled = true,
21+
showDiagnosisButton = true
2122
)
2223

2324
@Test
@@ -30,7 +31,8 @@ class SettingsScreenTest {
3031
navigationIconClick = { /*no-op*/ },
3132
changeLanguageButtonClick = { /*no-op*/ },
3233
updateButtonClick = { /*no-op*/ },
33-
logoutButtonClick = { /*no-op*/ }
34+
logoutButtonClick = { /*no-op*/ },
35+
syncMedicalRecordClick = { /*no-op*/ }
3436
)
3537
}
3638

@@ -58,7 +60,8 @@ class SettingsScreenTest {
5860
navigationIconClick = { /*no-op*/ },
5961
changeLanguageButtonClick = { /*no-op*/ },
6062
updateButtonClick = { /*no-op*/ },
61-
logoutButtonClick = { /*no-op*/ }
63+
logoutButtonClick = { /*no-op*/ },
64+
syncMedicalRecordClick = { /*no-op*/ }
6265
)
6366
}
6467

@@ -78,7 +81,8 @@ class SettingsScreenTest {
7881
navigationIconClick = { /*no-op*/ },
7982
changeLanguageButtonClick = { /*no-op*/ },
8083
updateButtonClick = { /*no-op*/ },
81-
logoutButtonClick = { /*no-op*/ }
84+
logoutButtonClick = { /*no-op*/ },
85+
syncMedicalRecordClick = { /*no-op*/ }
8286
)
8387
}
8488

@@ -97,7 +101,8 @@ class SettingsScreenTest {
97101
navigationIconClick = { /*no-op*/ },
98102
changeLanguageButtonClick = { /*no-op*/ },
99103
updateButtonClick = { /*no-op*/ },
100-
logoutButtonClick = { /*no-op*/ }
104+
logoutButtonClick = { /*no-op*/ },
105+
syncMedicalRecordClick = { /*no-op*/ }
101106
)
102107
}
103108

@@ -115,7 +120,8 @@ class SettingsScreenTest {
115120
navigationIconClick = { /*no-op*/ },
116121
changeLanguageButtonClick = { /*no-op*/ },
117122
updateButtonClick = { /*no-op*/ },
118-
logoutButtonClick = { /*no-op*/ }
123+
logoutButtonClick = { /*no-op*/ },
124+
syncMedicalRecordClick = { /*no-op*/ }
119125
)
120126
}
121127

@@ -133,7 +139,8 @@ class SettingsScreenTest {
133139
navigationIconClick = { /*no-op*/ },
134140
changeLanguageButtonClick = { /*no-op*/ },
135141
updateButtonClick = { /*no-op*/ },
136-
logoutButtonClick = { /*no-op*/ }
142+
logoutButtonClick = { /*no-op*/ },
143+
syncMedicalRecordClick = { /*no-op*/ }
137144
)
138145
}
139146

@@ -150,7 +157,8 @@ class SettingsScreenTest {
150157
navigationIconClick = { /*no-op*/ },
151158
changeLanguageButtonClick = { /*no-op*/ },
152159
updateButtonClick = { /*no-op*/ },
153-
logoutButtonClick = { /*no-op*/ }
160+
logoutButtonClick = { /*no-op*/ },
161+
syncMedicalRecordClick = { /*no-op*/ }
154162
)
155163
}
156164

@@ -168,7 +176,8 @@ class SettingsScreenTest {
168176
navigationIconClick = { /*no-op*/ },
169177
changeLanguageButtonClick = { /*no-op*/ },
170178
updateButtonClick = { /*no-op*/ },
171-
logoutButtonClick = { /*no-op*/ }
179+
logoutButtonClick = { /*no-op*/ },
180+
syncMedicalRecordClick = { /*no-op*/ }
172181
)
173182
}
174183

@@ -186,7 +195,8 @@ class SettingsScreenTest {
186195
navigationIconClick = { /*no-op*/ },
187196
changeLanguageButtonClick = { /*no-op*/ },
188197
updateButtonClick = { /*no-op*/ },
189-
logoutButtonClick = { /*no-op*/ }
198+
logoutButtonClick = { /*no-op*/ },
199+
syncMedicalRecordClick = { /*no-op*/ }
190200
)
191201
}
192202

@@ -198,14 +208,16 @@ class SettingsScreenTest {
198208
fun whenChangeLanguageFeatureIsNotEnabledThenDoNotShowChangeLanguageSetting() {
199209
val model = SettingsModel.default(
200210
isChangeLanguageFeatureEnabled = false,
211+
showDiagnosisButton = true
201212
)
202213
composeRule.setContent {
203214
SettingsScreen(
204215
model = model,
205216
navigationIconClick = { /*no-op*/ },
206217
changeLanguageButtonClick = { /*no-op*/ },
207218
updateButtonClick = { /*no-op*/ },
208-
logoutButtonClick = { /*no-op*/ }
219+
logoutButtonClick = { /*no-op*/ },
220+
syncMedicalRecordClick = { /*no-op*/ }
209221
)
210222
}
211223

@@ -217,14 +229,16 @@ class SettingsScreenTest {
217229
fun whenChangeLanguageFeatureIsEnabledThenShowChangeLanguageSetting() {
218230
val model = SettingsModel.default(
219231
isChangeLanguageFeatureEnabled = true,
232+
showDiagnosisButton = true,
220233
)
221234
composeRule.setContent {
222235
SettingsScreen(
223236
model = model,
224237
navigationIconClick = { /*no-op*/ },
225238
changeLanguageButtonClick = { /*no-op*/ },
226239
updateButtonClick = { /*no-op*/ },
227-
logoutButtonClick = { /*no-op*/ }
240+
logoutButtonClick = { /*no-op*/ },
241+
syncMedicalRecordClick = { /*no-op*/ }
228242
)
229243
}
230244

@@ -236,14 +250,16 @@ class SettingsScreenTest {
236250
fun logoutButtonShouldBeVisible() {
237251
val model = SettingsModel.default(
238252
isChangeLanguageFeatureEnabled = true,
253+
showDiagnosisButton = true
239254
)
240255
composeRule.setContent {
241256
SettingsScreen(
242257
model = model,
243258
navigationIconClick = { /*no-op*/ },
244259
changeLanguageButtonClick = { /*no-op*/ },
245260
updateButtonClick = { /*no-op*/ },
246-
logoutButtonClick = { /*no-op*/ }
261+
logoutButtonClick = { /*no-op*/ },
262+
syncMedicalRecordClick = { /*no-op*/ }
247263
)
248264
}
249265

app/src/main/java/org/simple/clinic/bloodsugar/BloodSugarMeasurement.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@ data class BloodSugarMeasurement(
9999
ORDER BY recordedAt DESC
100100
""")
101101
fun allBloodSugars(patientUuid: UUID): Observable<List<BloodSugarMeasurement>>
102+
@Query("""
103+
SELECT * FROM BloodSugarMeasurements
104+
WHERE patientUuid == :patientUuid AND deletedAt IS NULL
105+
ORDER BY recordedAt DESC
106+
""")
107+
fun allBloodSugarsImmediate(patientUuid: UUID): List<BloodSugarMeasurement>
102108

103109
@Query("""
104110
SELECT * FROM BloodSugarMeasurements

app/src/main/java/org/simple/clinic/feature/Feature.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@ enum class Feature(
2626
PatientStatinNudge(false, "patient_statin_nudge_v0"),
2727
NonLabBasedStatinNudge(false, "non_lab_based_statin_nudge"),
2828
LabBasedStatinNudge(false, "lab_based_statin_nudge"),
29-
Screening(false, "screening_feature_v0")
29+
Screening(false, "screening_feature_v0"),
30+
ShowDiagnosisButton(false, "show_diagnosis_button"),
3031
}

app/src/main/java/org/simple/clinic/medicalhistory/MedicalHistory.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsOnDiabetesTreat
2424
import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsOnHypertensionTreatment
2525
import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsSmoking
2626
import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsUsingSmokelessTobacco
27+
import org.simple.clinic.medicalhistory.sync.MedicalHistoryPayload
2728
import org.simple.clinic.patient.PatientUuid
2829
import org.simple.clinic.patient.SyncStatus
2930
import java.time.Instant
@@ -114,6 +115,28 @@ data class MedicalHistory(
114115
return copy(cholesterol = cholesterol)
115116
}
116117

118+
fun toPayload(): MedicalHistoryPayload {
119+
return MedicalHistoryPayload(
120+
uuid = uuid,
121+
patientUuid = patientUuid,
122+
diagnosedWithHypertension = diagnosedWithHypertension,
123+
isOnTreatmentForHypertension = isOnHypertensionTreatment,
124+
isOnDiabetesTreatment = isOnDiabetesTreatment,
125+
hasHadHeartAttack = hasHadHeartAttack,
126+
hasHadStroke = hasHadStroke,
127+
hasHadKidneyDisease = hasHadKidneyDisease,
128+
hasDiabetes = diagnosedWithDiabetes,
129+
hasHypertension = diagnosedWithHypertension,
130+
isSmoking = isSmoking,
131+
isUsingSmokelessTobacco = isUsingSmokelessTobacco,
132+
cholesterol = cholesterol,
133+
hypertensionDiagnosedAt = hypertensionDiagnosedAt,
134+
diabetesDiagnosedAt = diabetesDiagnosedAt,
135+
createdAt = createdAt,
136+
updatedAt = updatedAt,
137+
deletedAt = deletedAt)
138+
}
139+
117140
@Dao
118141
interface RoomDao {
119142

app/src/main/java/org/simple/clinic/medicalhistory/sync/MedicalHistorySync.kt

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -35,30 +35,7 @@ class MedicalHistorySync @Inject constructor(
3535
}
3636

3737
private fun toRequest(histories: List<MedicalHistory>): MedicalHistoryPushRequest {
38-
val payloads = histories
39-
.map {
40-
it.run {
41-
MedicalHistoryPayload(
42-
uuid = uuid,
43-
patientUuid = patientUuid,
44-
diagnosedWithHypertension = diagnosedWithHypertension,
45-
isOnTreatmentForHypertension = isOnHypertensionTreatment,
46-
isOnDiabetesTreatment = isOnDiabetesTreatment,
47-
hasHadHeartAttack = hasHadHeartAttack,
48-
hasHadStroke = hasHadStroke,
49-
hasHadKidneyDisease = hasHadKidneyDisease,
50-
hasDiabetes = diagnosedWithDiabetes,
51-
hasHypertension = diagnosedWithHypertension,
52-
isSmoking = isSmoking,
53-
isUsingSmokelessTobacco = isUsingSmokelessTobacco,
54-
cholesterol = cholesterol,
55-
hypertensionDiagnosedAt = hypertensionDiagnosedAt,
56-
diabetesDiagnosedAt = diabetesDiagnosedAt,
57-
createdAt = createdAt,
58-
updatedAt = updatedAt,
59-
deletedAt = deletedAt)
60-
}
61-
}
38+
val payloads = histories.map { it.toPayload() }
6239
return MedicalHistoryPushRequest(payloads)
6340
}
6441
}

app/src/main/java/org/simple/clinic/overdue/Appointment.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,23 @@ data class Appointment(
5151
val creationFacilityUuid: UUID?
5252
) : Parcelable {
5353

54+
fun toPayload(): AppointmentPayload {
55+
return AppointmentPayload(
56+
uuid = uuid,
57+
patientUuid = patientUuid,
58+
facilityUuid = facilityUuid,
59+
creationFacilityUuid = creationFacilityUuid,
60+
date = scheduledDate,
61+
status = status,
62+
cancelReason = cancelReason,
63+
remindOn = remindOn,
64+
agreedToVisit = agreedToVisit,
65+
appointmentType = appointmentType,
66+
createdAt = createdAt,
67+
updatedAt = updatedAt,
68+
deletedAt = deletedAt)
69+
}
70+
5471
fun wasCancelledBecauseOfInvalidPhoneNumber(): Boolean = status == Status.Cancelled && cancelReason == AppointmentCancelReason.InvalidPhoneNumber
5572

5673
sealed class Status : Parcelable {

app/src/main/java/org/simple/clinic/overdue/AppointmentSync.kt

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,26 +33,7 @@ class AppointmentSync @Inject constructor(
3333
}
3434

3535
private fun toRequest(appointments: List<Appointment>): AppointmentPushRequest {
36-
val payloads = appointments
37-
.map {
38-
it.run {
39-
AppointmentPayload(
40-
uuid = uuid,
41-
patientUuid = patientUuid,
42-
facilityUuid = facilityUuid,
43-
creationFacilityUuid = creationFacilityUuid,
44-
date = scheduledDate,
45-
status = status,
46-
cancelReason = cancelReason,
47-
remindOn = remindOn,
48-
agreedToVisit = agreedToVisit,
49-
appointmentType = appointmentType,
50-
createdAt = createdAt,
51-
updatedAt = updatedAt,
52-
deletedAt = deletedAt)
53-
}
54-
}
55-
.toList()
36+
val payloads = appointments.map { it.toPayload() }
5637
return AppointmentPushRequest(payloads)
5738
}
5839
}

app/src/main/java/org/simple/clinic/patient/PatientPhoneNumber.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import androidx.room.PrimaryKey
1212
import androidx.room.Query
1313
import io.reactivex.Flowable
1414
import kotlinx.parcelize.Parcelize
15+
import org.simple.clinic.patient.sync.PatientPhoneNumberPayload
1516
import java.time.Instant
1617
import java.util.UUID
1718

@@ -47,6 +48,18 @@ data class PatientPhoneNumber(
4748
val deletedAt: Instant?
4849
) : Parcelable {
4950

51+
fun toPayload(): PatientPhoneNumberPayload {
52+
return PatientPhoneNumberPayload(
53+
uuid = uuid,
54+
number = number,
55+
type = phoneType,
56+
active = active,
57+
createdAt = createdAt,
58+
updatedAt = updatedAt,
59+
deletedAt = deletedAt
60+
)
61+
}
62+
5063
fun withNumber(number: String): PatientPhoneNumber =
5164
copy(number = number)
5265

app/src/main/java/org/simple/clinic/patient/PatientRepository.kt

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import org.simple.clinic.facility.Facility
1313
import org.simple.clinic.medicalhistory.Answer
1414
import org.simple.clinic.overdue.Appointment.AppointmentType.Manual
1515
import org.simple.clinic.overdue.Appointment.Status.Scheduled
16-
import org.simple.clinic.patient.Answer.*
16+
import org.simple.clinic.patient.Answer.Unanswered
1717
import org.simple.clinic.patient.PatientSearchCriteria.Name
1818
import org.simple.clinic.patient.PatientSearchCriteria.NumericCriteria
1919
import org.simple.clinic.patient.SyncStatus.DONE
@@ -741,6 +741,35 @@ class PatientRepository @Inject constructor(
741741
}
742742
}
743743

744+
fun fetchCompleteMedicalRecord(): List<CompleteMedicalRecord> {
745+
val patientProfiles = database.patientDao().allPatientProfiles()
746+
return patientProfiles.mapNotNull { patientProfile ->
747+
try {
748+
val patientUuid = patientProfile.patientUuid
749+
val medicalHistory = database.medicalHistoryDao().historyForPatientImmediate(patientUuid)
750+
val appointments = database.appointmentDao().getAllAppointmentsForPatient(patientUuid)
751+
val bloodPressures = database.bloodPressureDao().allBloodPressuresRecordedSinceImmediate(
752+
patientUuid,
753+
Instant.EPOCH
754+
)
755+
val bloodSugars = database.bloodSugarDao().allBloodSugarsImmediate(patientUuid)
756+
757+
val prescribedDrugs = database.prescriptionDao().forPatientImmediate(patientUuid)
758+
759+
CompleteMedicalRecord(
760+
patient = patientProfile,
761+
medicalHistory = medicalHistory,
762+
appointments = appointments,
763+
bloodPressures = bloodPressures,
764+
bloodSugars = bloodSugars,
765+
prescribedDrugs = prescribedDrugs
766+
)
767+
} catch (_: Exception) {
768+
null
769+
}
770+
}
771+
}
772+
744773
fun addIdentifiersToPatient(
745774
patientUuid: UUID,
746775
businessIds: List<BusinessId>

0 commit comments

Comments
 (0)