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
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@

### Internal

-Update `ci-checks.yml` & `story-link-check.sh` to verify valid jira reference
- Update `ci-checks.yml` & `story-link-check.sh` to verify valid jira reference
- Add effect to fetch complete medical records
- Bump sqlcipher to v4.13.0
- Bump dagger to v2.59.1

### Changes

- Add `Sync Medical Records` button on setting page behind feature flag

## 2026.02.02

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class SettingsScreenTest {

private val defaultSettingsModel = SettingsModel.default(
isChangeLanguageFeatureEnabled = true,
showDiagnosisButton = true
)

@Test
Expand All @@ -30,7 +31,8 @@ class SettingsScreenTest {
navigationIconClick = { /*no-op*/ },
changeLanguageButtonClick = { /*no-op*/ },
updateButtonClick = { /*no-op*/ },
logoutButtonClick = { /*no-op*/ }
logoutButtonClick = { /*no-op*/ },
syncMedicalRecordClick = { /*no-op*/ }
)
}

Expand Down Expand Up @@ -58,7 +60,8 @@ class SettingsScreenTest {
navigationIconClick = { /*no-op*/ },
changeLanguageButtonClick = { /*no-op*/ },
updateButtonClick = { /*no-op*/ },
logoutButtonClick = { /*no-op*/ }
logoutButtonClick = { /*no-op*/ },
syncMedicalRecordClick = { /*no-op*/ }
)
}

Expand All @@ -78,7 +81,8 @@ class SettingsScreenTest {
navigationIconClick = { /*no-op*/ },
changeLanguageButtonClick = { /*no-op*/ },
updateButtonClick = { /*no-op*/ },
logoutButtonClick = { /*no-op*/ }
logoutButtonClick = { /*no-op*/ },
syncMedicalRecordClick = { /*no-op*/ }
)
}

Expand All @@ -97,7 +101,8 @@ class SettingsScreenTest {
navigationIconClick = { /*no-op*/ },
changeLanguageButtonClick = { /*no-op*/ },
updateButtonClick = { /*no-op*/ },
logoutButtonClick = { /*no-op*/ }
logoutButtonClick = { /*no-op*/ },
syncMedicalRecordClick = { /*no-op*/ }
)
}

Expand All @@ -115,7 +120,8 @@ class SettingsScreenTest {
navigationIconClick = { /*no-op*/ },
changeLanguageButtonClick = { /*no-op*/ },
updateButtonClick = { /*no-op*/ },
logoutButtonClick = { /*no-op*/ }
logoutButtonClick = { /*no-op*/ },
syncMedicalRecordClick = { /*no-op*/ }
)
}

Expand All @@ -133,7 +139,8 @@ class SettingsScreenTest {
navigationIconClick = { /*no-op*/ },
changeLanguageButtonClick = { /*no-op*/ },
updateButtonClick = { /*no-op*/ },
logoutButtonClick = { /*no-op*/ }
logoutButtonClick = { /*no-op*/ },
syncMedicalRecordClick = { /*no-op*/ }
)
}

Expand All @@ -150,7 +157,8 @@ class SettingsScreenTest {
navigationIconClick = { /*no-op*/ },
changeLanguageButtonClick = { /*no-op*/ },
updateButtonClick = { /*no-op*/ },
logoutButtonClick = { /*no-op*/ }
logoutButtonClick = { /*no-op*/ },
syncMedicalRecordClick = { /*no-op*/ }
)
}

Expand All @@ -168,7 +176,8 @@ class SettingsScreenTest {
navigationIconClick = { /*no-op*/ },
changeLanguageButtonClick = { /*no-op*/ },
updateButtonClick = { /*no-op*/ },
logoutButtonClick = { /*no-op*/ }
logoutButtonClick = { /*no-op*/ },
syncMedicalRecordClick = { /*no-op*/ }
)
}

Expand All @@ -186,7 +195,8 @@ class SettingsScreenTest {
navigationIconClick = { /*no-op*/ },
changeLanguageButtonClick = { /*no-op*/ },
updateButtonClick = { /*no-op*/ },
logoutButtonClick = { /*no-op*/ }
logoutButtonClick = { /*no-op*/ },
syncMedicalRecordClick = { /*no-op*/ }
)
}

Expand All @@ -198,14 +208,16 @@ class SettingsScreenTest {
fun whenChangeLanguageFeatureIsNotEnabledThenDoNotShowChangeLanguageSetting() {
val model = SettingsModel.default(
isChangeLanguageFeatureEnabled = false,
showDiagnosisButton = true
)
composeRule.setContent {
SettingsScreen(
model = model,
navigationIconClick = { /*no-op*/ },
changeLanguageButtonClick = { /*no-op*/ },
updateButtonClick = { /*no-op*/ },
logoutButtonClick = { /*no-op*/ }
logoutButtonClick = { /*no-op*/ },
syncMedicalRecordClick = { /*no-op*/ }
)
}

Expand All @@ -217,14 +229,16 @@ class SettingsScreenTest {
fun whenChangeLanguageFeatureIsEnabledThenShowChangeLanguageSetting() {
val model = SettingsModel.default(
isChangeLanguageFeatureEnabled = true,
showDiagnosisButton = true,
)
composeRule.setContent {
SettingsScreen(
model = model,
navigationIconClick = { /*no-op*/ },
changeLanguageButtonClick = { /*no-op*/ },
updateButtonClick = { /*no-op*/ },
logoutButtonClick = { /*no-op*/ }
logoutButtonClick = { /*no-op*/ },
syncMedicalRecordClick = { /*no-op*/ }
)
}

Expand All @@ -236,14 +250,16 @@ class SettingsScreenTest {
fun logoutButtonShouldBeVisible() {
val model = SettingsModel.default(
isChangeLanguageFeatureEnabled = true,
showDiagnosisButton = true
)
composeRule.setContent {
SettingsScreen(
model = model,
navigationIconClick = { /*no-op*/ },
changeLanguageButtonClick = { /*no-op*/ },
updateButtonClick = { /*no-op*/ },
logoutButtonClick = { /*no-op*/ }
logoutButtonClick = { /*no-op*/ },
syncMedicalRecordClick = { /*no-op*/ }
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ data class BloodSugarMeasurement(
ORDER BY recordedAt DESC
""")
fun allBloodSugars(patientUuid: UUID): Observable<List<BloodSugarMeasurement>>
@Query("""
SELECT * FROM BloodSugarMeasurements
WHERE patientUuid == :patientUuid AND deletedAt IS NULL
ORDER BY recordedAt DESC
""")
fun allBloodSugarsImmediate(patientUuid: UUID): List<BloodSugarMeasurement>

@Query("""
SELECT * FROM BloodSugarMeasurements
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/java/org/simple/clinic/feature/Feature.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ enum class Feature(
PatientStatinNudge(false, "patient_statin_nudge_v0"),
NonLabBasedStatinNudge(false, "non_lab_based_statin_nudge"),
LabBasedStatinNudge(false, "lab_based_statin_nudge"),
Screening(false, "screening_feature_v0")
Screening(false, "screening_feature_v0"),
ShowDiagnosisButton(false, "show_diagnosis_button"),
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsOnDiabetesTreat
import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsOnHypertensionTreatment
import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsSmoking
import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsUsingSmokelessTobacco
import org.simple.clinic.medicalhistory.sync.MedicalHistoryPayload
import org.simple.clinic.patient.PatientUuid
import org.simple.clinic.patient.SyncStatus
import java.time.Instant
Expand Down Expand Up @@ -114,6 +115,28 @@ data class MedicalHistory(
return copy(cholesterol = cholesterol)
}

fun toPayload(): MedicalHistoryPayload {
return MedicalHistoryPayload(
uuid = uuid,
patientUuid = patientUuid,
diagnosedWithHypertension = diagnosedWithHypertension,
isOnTreatmentForHypertension = isOnHypertensionTreatment,
isOnDiabetesTreatment = isOnDiabetesTreatment,
hasHadHeartAttack = hasHadHeartAttack,
hasHadStroke = hasHadStroke,
hasHadKidneyDisease = hasHadKidneyDisease,
hasDiabetes = diagnosedWithDiabetes,
hasHypertension = diagnosedWithHypertension,
isSmoking = isSmoking,
isUsingSmokelessTobacco = isUsingSmokelessTobacco,
cholesterol = cholesterol,
hypertensionDiagnosedAt = hypertensionDiagnosedAt,
diabetesDiagnosedAt = diabetesDiagnosedAt,
createdAt = createdAt,
updatedAt = updatedAt,
deletedAt = deletedAt)
}

@Dao
interface RoomDao {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,30 +35,7 @@ class MedicalHistorySync @Inject constructor(
}

private fun toRequest(histories: List<MedicalHistory>): MedicalHistoryPushRequest {
val payloads = histories
.map {
it.run {
MedicalHistoryPayload(
uuid = uuid,
patientUuid = patientUuid,
diagnosedWithHypertension = diagnosedWithHypertension,
isOnTreatmentForHypertension = isOnHypertensionTreatment,
isOnDiabetesTreatment = isOnDiabetesTreatment,
hasHadHeartAttack = hasHadHeartAttack,
hasHadStroke = hasHadStroke,
hasHadKidneyDisease = hasHadKidneyDisease,
hasDiabetes = diagnosedWithDiabetes,
hasHypertension = diagnosedWithHypertension,
isSmoking = isSmoking,
isUsingSmokelessTobacco = isUsingSmokelessTobacco,
cholesterol = cholesterol,
hypertensionDiagnosedAt = hypertensionDiagnosedAt,
diabetesDiagnosedAt = diabetesDiagnosedAt,
createdAt = createdAt,
updatedAt = updatedAt,
deletedAt = deletedAt)
}
}
val payloads = histories.map { it.toPayload() }
return MedicalHistoryPushRequest(payloads)
}
}
17 changes: 17 additions & 0 deletions app/src/main/java/org/simple/clinic/overdue/Appointment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,23 @@ data class Appointment(
val creationFacilityUuid: UUID?
) : Parcelable {

fun toPayload(): AppointmentPayload {
return AppointmentPayload(
uuid = uuid,
patientUuid = patientUuid,
facilityUuid = facilityUuid,
creationFacilityUuid = creationFacilityUuid,
date = scheduledDate,
status = status,
cancelReason = cancelReason,
remindOn = remindOn,
agreedToVisit = agreedToVisit,
appointmentType = appointmentType,
createdAt = createdAt,
updatedAt = updatedAt,
deletedAt = deletedAt)
}

fun wasCancelledBecauseOfInvalidPhoneNumber(): Boolean = status == Status.Cancelled && cancelReason == AppointmentCancelReason.InvalidPhoneNumber

sealed class Status : Parcelable {
Expand Down
21 changes: 1 addition & 20 deletions app/src/main/java/org/simple/clinic/overdue/AppointmentSync.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,7 @@ class AppointmentSync @Inject constructor(
}

private fun toRequest(appointments: List<Appointment>): AppointmentPushRequest {
val payloads = appointments
.map {
it.run {
AppointmentPayload(
uuid = uuid,
patientUuid = patientUuid,
facilityUuid = facilityUuid,
creationFacilityUuid = creationFacilityUuid,
date = scheduledDate,
status = status,
cancelReason = cancelReason,
remindOn = remindOn,
agreedToVisit = agreedToVisit,
appointmentType = appointmentType,
createdAt = createdAt,
updatedAt = updatedAt,
deletedAt = deletedAt)
}
}
.toList()
val payloads = appointments.map { it.toPayload() }
return AppointmentPushRequest(payloads)
}
}
13 changes: 13 additions & 0 deletions app/src/main/java/org/simple/clinic/patient/PatientPhoneNumber.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import androidx.room.PrimaryKey
import androidx.room.Query
import io.reactivex.Flowable
import kotlinx.parcelize.Parcelize
import org.simple.clinic.patient.sync.PatientPhoneNumberPayload
import java.time.Instant
import java.util.UUID

Expand Down Expand Up @@ -47,6 +48,18 @@ data class PatientPhoneNumber(
val deletedAt: Instant?
) : Parcelable {

fun toPayload(): PatientPhoneNumberPayload {
return PatientPhoneNumberPayload(
uuid = uuid,
number = number,
type = phoneType,
active = active,
createdAt = createdAt,
updatedAt = updatedAt,
deletedAt = deletedAt
)
}

fun withNumber(number: String): PatientPhoneNumber =
copy(number = number)

Expand Down
31 changes: 30 additions & 1 deletion app/src/main/java/org/simple/clinic/patient/PatientRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import org.simple.clinic.facility.Facility
import org.simple.clinic.medicalhistory.Answer
import org.simple.clinic.overdue.Appointment.AppointmentType.Manual
import org.simple.clinic.overdue.Appointment.Status.Scheduled
import org.simple.clinic.patient.Answer.*
import org.simple.clinic.patient.Answer.Unanswered
import org.simple.clinic.patient.PatientSearchCriteria.Name
import org.simple.clinic.patient.PatientSearchCriteria.NumericCriteria
import org.simple.clinic.patient.SyncStatus.DONE
Expand Down Expand Up @@ -741,6 +741,35 @@ class PatientRepository @Inject constructor(
}
}

fun fetchCompleteMedicalRecord(): List<CompleteMedicalRecord> {
val patientProfiles = database.patientDao().allPatientProfiles()
return patientProfiles.mapNotNull { patientProfile ->
try {
val patientUuid = patientProfile.patientUuid
val medicalHistory = database.medicalHistoryDao().historyForPatientImmediate(patientUuid)
val appointments = database.appointmentDao().getAllAppointmentsForPatient(patientUuid)
val bloodPressures = database.bloodPressureDao().allBloodPressuresRecordedSinceImmediate(
patientUuid,
Instant.EPOCH
)
val bloodSugars = database.bloodSugarDao().allBloodSugarsImmediate(patientUuid)

val prescribedDrugs = database.prescriptionDao().forPatientImmediate(patientUuid)

CompleteMedicalRecord(
patient = patientProfile,
medicalHistory = medicalHistory,
appointments = appointments,
bloodPressures = bloodPressures,
bloodSugars = bloodSugars,
prescribedDrugs = prescribedDrugs
)
} catch (_: Exception) {
null
}
}
}

fun addIdentifiersToPatient(
patientUuid: UUID,
businessIds: List<BusinessId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.simple.clinic.patient.medicalRecords

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import org.simple.clinic.patient.CompleteMedicalRecord
import org.simple.clinic.patient.onlinelookup.api.CompleteMedicalRecordPayload

@JsonClass(generateAdapter = true)
data class CompleteMedicalRecordsPushRequest(

@Json(name = "patients")
val patients: List<CompleteMedicalRecordPayload>
)
Loading
Loading