Skip to content

Commit

Permalink
#1185 - scheduled visits based location filters
Browse files Browse the repository at this point in the history
  • Loading branch information
petmongrels committed Nov 17, 2023
1 parent 4a9e3f4 commit 0fd8ab6
Show file tree
Hide file tree
Showing 10 changed files with 202 additions and 85 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import BaseIntegrationTest from "./BaseIntegrationTest";
import {
EncounterType,
Encounter,
ApprovalStatus,
EntityApprovalStatus,
AddressLevel,
Expand Down Expand Up @@ -38,6 +40,13 @@ import {assert} from "chai";
import General from "../src/utility/General";
import TestEntityApprovalStatusFactory from "../test/model/approval/TestEntityApprovalStatusFactory";
import TestApprovalStatusFactory from "../test/model/approval/TestApprovalStatusFactory";
import TestEncounterFactory from "../test/model/txn/TestEncounterFactory";
import TestEncounterTypeFactory from "../test/model/TestEncounterTypeFactory";
import moment from "moment";

function getCount(reportCardService, card, reportFilters) {
return reportCardService.getReportCardCount(card, reportFilters).primaryValue
}

class ReportCardServiceIntegrationTest extends BaseIntegrationTest {
approvedCard;
Expand All @@ -54,6 +63,7 @@ class ReportCardServiceIntegrationTest extends BaseIntegrationTest {
db.create(Settings, TestSettingsFactory.createWithDefaults({}));

this.subjectType = db.create(SubjectType, TestSubjectTypeFactory.createWithDefaults({type: SubjectType.types.Person, name: 'Beneficiary'}));
const encounterType = db.create(EncounterType, TestEncounterTypeFactory.create({name: "Bar"}));
const form = db.create(Form, TestFormFactory.createWithDefaults({formType: Form.formTypes.IndividualProfile}));
const formElementGroup = db.create(FormElementGroup, TestFormElementGroupFactory.create({form: form}));
db.create(FormElement, TestFormElementFactory.create({
Expand All @@ -67,34 +77,63 @@ class ReportCardServiceIntegrationTest extends BaseIntegrationTest {
db.create(FormMapping, TestFormMappingFactory.createWithDefaults({subjectType: this.subjectType, form: form}));
db.create(OrganisationConfig, TestOrganisationConfigFactory.createWithDefaults({}));
const approvalStatus = db.create(ApprovalStatus, TestApprovalStatusFactory.create({}));
const subjectUuid = General.randomUUID();
const entityApprovalStatus = db.create(EntityApprovalStatus, TestEntityApprovalStatusFactory.create({
const pendingStatus = db.create(ApprovalStatus, TestApprovalStatusFactory.create({status: ApprovalStatus.statuses.Pending}));
const subjectId = General.randomUUID();
const encounterId = General.randomUUID();

const subjectEAS = db.create(EntityApprovalStatus, TestEntityApprovalStatusFactory.create({
entityType: EntityApprovalStatus.entityType.Subject,
entityUUID: subjectUuid,
entityUUID: subjectId,
entityTypeUuid: this.subjectType.uuid,
approvalStatus: approvalStatus
}));
db.create(Individual, TestSubjectFactory.createWithDefaults({
const encEAS = db.create(EntityApprovalStatus, TestEntityApprovalStatusFactory.create({
entityType: EntityApprovalStatus.entityType.Encounter,
entityUUID: encounterId,
entityTypeUuid: encounterType.uuid,
approvalStatus: pendingStatus
}));
const subject = db.create(Individual, TestSubjectFactory.createWithDefaults({
subjectType: this.subjectType,
address: this.addressLevel,
firstName: "foo",
lastName: "bar",
observations: [TestObsFactory.create({concept: this.concept, valueJSON: JSON.stringify(this.concept.getValueWrapperFor("ABC"))})],
approvalStatuses: [entityApprovalStatus]
approvalStatuses: [subjectEAS]
}));

db.create(Encounter, TestEncounterFactory.create({
uuid: encounterId,
earliestVisitDateTime: moment().add(-2, "day").toDate(),
maxVisitDateTime: moment().add(2, "day").toDate(),
encounterType: encounterType,
approvalStatuses: [encEAS],
subject: subject
}));

const approvedCardType = db.create(StandardReportCardType, TestStandardReportCardTypeFactory.create({name: StandardReportCardType.type.Approved}));
this.approvedCard = db.create(ReportCard, TestReportCardFactory.create({name: "foo", standardReportCardType: approvedCardType}));
const scheduledVisitsCardType = db.create(StandardReportCardType, TestStandardReportCardTypeFactory.create({name: StandardReportCardType.type.ScheduledVisits}));
this.approvedCard = db.create(ReportCard, TestReportCardFactory.create({name: "a", standardReportCardType: approvedCardType}));
this.scheduledVisitsCard = db.create(ReportCard, TestReportCardFactory.create({name: "b", standardReportCardType: scheduledVisitsCardType}));
});
}

getResultForApprovalCardsType() {
const reportCardService = this.getService(ReportCardService);
assert.equal(1, reportCardService.getReportCardCount(this.approvedCard, []).primaryValue);
assert.equal(1, getCount(reportCardService, this.approvedCard, []));
let filterValues = [TestDashboardReportFilterFactory.create({type: CustomFilter.type.Address, filterValue: [this.addressLevel]})];
assert.equal(1, reportCardService.getReportCardCount(this.approvedCard, filterValues).primaryValue);
assert.equal(1, getCount(reportCardService, this.approvedCard, filterValues));
filterValues = [TestDashboardReportFilterFactory.create({type: CustomFilter.type.Address, filterValue: [this.addressLevel2]})];
assert.equal(0, reportCardService.getReportCardCount(this.approvedCard, filterValues).primaryValue);
assert.equal(0, getCount(reportCardService, this.approvedCard, filterValues));
filterValues = [TestDashboardReportFilterFactory.create({type: CustomFilter.type.Address, filterValue: [this.addressLevel, this.addressLevel2]})];
assert.equal(1, getCount(reportCardService, this.approvedCard, filterValues));
}

getCountForDefaultCardsType() {
const reportCardService = this.getService(ReportCardService);
assert.equal(1, getCount(reportCardService, this.scheduledVisitsCard, []));
assert.equal(1, getCount(reportCardService, this.scheduledVisitsCard, [TestDashboardReportFilterFactory.create({type: CustomFilter.type.Address, filterValue: [this.addressLevel]})]));
assert.equal(0, getCount(reportCardService, this.scheduledVisitsCard, [TestDashboardReportFilterFactory.create({type: CustomFilter.type.Address, filterValue: [this.addressLevel2]})]));
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/openchs-android/src/GlobalContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class GlobalContext {

async initialiseGlobalContext(appStore, realmFactory) {
this.db = await realmFactory.createRealm();
this.db.setLogQueries(true);
this.beanRegistry.init(this.db);
this.reduxStore = appStore.create(this.beanRegistry.beansMap);
this.beanRegistry.setReduxStore(this.reduxStore);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import UserInfoService from "../../service/UserInfoService";
import DashboardCacheService from "../../service/DashboardCacheService";
import {firebaseEvents, logEvent} from "../../utility/Analytics";
import LocalCacheService from '../../service/LocalCacheService';
import RealmQueryService from "../../service/query/RealmQueryService";

function getApplicableEncounterTypes(state) {
return _.isEmpty(state.selectedGeneralEncounterTypes) ? state.selectedEncounterTypes : state.selectedGeneralEncounterTypes;
Expand Down Expand Up @@ -51,10 +52,6 @@ class MyDashboardActions {
.reduce((acc, f) => f.compositeFn(acc), individuals);
}

static orQuery(array) {
return array.length > 0 ? '( ' + array.join(' OR ') + ' )' : ''
}

static commonIndividuals = (otherFilteredIndividuals, customFilteredIndividualsUUIDs, isTotal = false) => {
const getIndividualUUID = (indInfo) => isTotal ? indInfo.uuid : indInfo.individual.uuid;
return ((_.isEmpty(customFilteredIndividualsUUIDs) || _.isEmpty(otherFilteredIndividuals)) ?
Expand Down Expand Up @@ -100,7 +97,7 @@ class MyDashboardActions {
allIndividuals,
dueChecklist
] = state.returnEmpty ? [[], [], [], [], [], [], [],[]] : (fetchFromDB ? [
MyDashboardActions.commonIndividuals(individualService.allScheduledVisitsIn(state.date.value, encountersFilters, generalEncountersFilters, queryProgramEncounter, queryGeneralEncounter), state.individualUUIDs),
MyDashboardActions.commonIndividuals(individualService.allScheduledVisitsIn(state.date.value, [], encountersFilters, generalEncountersFilters, queryProgramEncounter, queryGeneralEncounter), state.individualUUIDs),
MyDashboardActions.commonIndividuals(individualService.allOverdueVisitsIn(state.date.value, encountersFilters, generalEncountersFilters, queryProgramEncounter, queryGeneralEncounter), state.individualUUIDs),
MyDashboardActions.commonIndividuals(individualService.recentlyCompletedVisitsIn(state.date.value, encountersFilters, generalEncountersFilters, queryProgramEncounter, queryGeneralEncounter), state.individualUUIDs),
MyDashboardActions.commonIndividuals(individualService.recentlyRegistered(state.date.value, individualFilters, state.selectedPrograms, getApplicableEncounterTypes(state)), state.individualUUIDs),
Expand Down Expand Up @@ -283,55 +280,55 @@ class MyDashboardActions {
}

const restIndividualFilters = [
MyDashboardActions.orQuery(programQuery('$enrolment.program.uuid')),
MyDashboardActions.orQuery(visitQuery('$enrolment.encounters.encounterType.uuid')),
RealmQueryService.orQuery(programQuery('$enrolment.program.uuid')),
RealmQueryService.orQuery(visitQuery('$enrolment.encounters.encounterType.uuid')),
validEnrolmentQuery('$enrolment')
].filter(Boolean).join(" AND ");

const buildEnrolmentSubQueryForIndividual = () => _.isEmpty(restIndividualFilters) ? '' :
'SUBQUERY(enrolments, $enrolment, ' + restIndividualFilters + ' ).@count > 0';

const encounterQuery = () => _.isEmpty(MyDashboardActions.orQuery(generalVisitQueryFromIndividual)) ? '' :
'SUBQUERY(encounters, $encounter, ' + MyDashboardActions.orQuery(generalVisitQueryFromIndividual) + ' ).@count > 0';
const encounterQuery = () => _.isEmpty(RealmQueryService.orQuery(generalVisitQueryFromIndividual)) ? '' :
'SUBQUERY(encounters, $encounter, ' + RealmQueryService.orQuery(generalVisitQueryFromIndividual) + ' ).@count > 0';

const individualFilters = [
subjectTypeQuery('subjectType.uuid'),
MyDashboardActions.orQuery(genderQuery('gender.name')),
MyDashboardActions.orQuery(locationQuery('lowestAddressLevel.uuid')),
RealmQueryService.orQuery(genderQuery('gender.name')),
RealmQueryService.orQuery(locationQuery('lowestAddressLevel.uuid')),
encounterQuery(),
buildEnrolmentSubQueryForIndividual()
].filter(Boolean).join(" AND ");

const encountersFilters = [
subjectTypeQuery('programEnrolment.individual.subjectType.uuid'),
MyDashboardActions.orQuery(genderQuery('programEnrolment.individual.gender.name')),
MyDashboardActions.orQuery(locationQuery('programEnrolment.individual.lowestAddressLevel.uuid')),
MyDashboardActions.orQuery(programQuery('programEnrolment.program.uuid')),
MyDashboardActions.orQuery(visitQuery('encounterType.uuid')),
RealmQueryService.orQuery(genderQuery('programEnrolment.individual.gender.name')),
RealmQueryService.orQuery(locationQuery('programEnrolment.individual.lowestAddressLevel.uuid')),
RealmQueryService.orQuery(programQuery('programEnrolment.program.uuid')),
RealmQueryService.orQuery(visitQuery('encounterType.uuid')),
validEnrolmentQuery("programEnrolment")
].filter(Boolean).join(" AND ");

const generalEncountersFilters = [
subjectTypeQuery('individual.subjectType.uuid'),
MyDashboardActions.orQuery(genderQuery('individual.gender.name')),
MyDashboardActions.orQuery(locationQuery('individual.lowestAddressLevel.uuid')),
MyDashboardActions.orQuery(generalVisitQuery('encounterType.uuid'))
RealmQueryService.orQuery(genderQuery('individual.gender.name')),
RealmQueryService.orQuery(locationQuery('individual.lowestAddressLevel.uuid')),
RealmQueryService.orQuery(generalVisitQuery('encounterType.uuid'))
].filter(Boolean).join(" AND ");

const enrolmentFilters = [
subjectTypeQuery('individual.subjectType.uuid'),
MyDashboardActions.orQuery(genderQuery('individual.gender.name')),
MyDashboardActions.orQuery(locationQuery('individual.lowestAddressLevel.uuid')),
MyDashboardActions.orQuery(programQuery('program.uuid')),
MyDashboardActions.orQuery(visitQuery('encounters.encounterType.uuid')),
RealmQueryService.orQuery(genderQuery('individual.gender.name')),
RealmQueryService.orQuery(locationQuery('individual.lowestAddressLevel.uuid')),
RealmQueryService.orQuery(programQuery('program.uuid')),
RealmQueryService.orQuery(visitQuery('encounters.encounterType.uuid')),
'voided = false and programExitDateTime = null'
].filter(Boolean).join(" AND ");

const dueChecklistFilter = [
subjectTypeQuery('individual.subjectType.uuid'),
MyDashboardActions.orQuery(genderQuery('individual.gender.name')),
MyDashboardActions.orQuery(locationQuery('individual.lowestAddressLevel.uuid')),
MyDashboardActions.orQuery(programQuery('program.uuid')),
RealmQueryService.orQuery(genderQuery('individual.gender.name')),
RealmQueryService.orQuery(locationQuery('individual.lowestAddressLevel.uuid')),
RealmQueryService.orQuery(programQuery('program.uuid')),
'programExitDateTime = null'
].filter(Boolean).join(" AND ");

Expand Down
7 changes: 7 additions & 0 deletions packages/openchs-android/src/model/DashboardReportFilters.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
// Objects has been used in place of arrays to allow for flexibility in contract in the future.

import _ from "lodash";
import {CustomFilter} from "openchs-models";

export class DashboardReportFilter {
type;
dataType;
subjectType;
groupSubjectTypeFilter;
observationBasedFilter;
filterValue;

static getAddressFilter(reportFilters) {
return _.find(reportFilters, (x: DashboardReportFilter) => x.type === CustomFilter.type.Address);
}
}

class DashboardReportFilters {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,7 @@ import {
import _ from 'lodash';
import {DashboardReportFilter} from "../model/DashboardReportFilters";
import AddressLevel from "../views/common/AddressLevel";

const locationBasedQueries = new Map();
locationBasedQueries.set(Individual.schema.name, "lowestAddressLevel.uuid = $0");
locationBasedQueries.set(ProgramEnrolment.schema.name, "individual.lowestAddressLevel.uuid = $0");
locationBasedQueries.set(ProgramEncounter.schema.name, "programEnrolment.individual.lowestAddressLevel.uuid = $0");
locationBasedQueries.set(Encounter.schema.name, "individual.lowestAddressLevel.uuid = $0");
locationBasedQueries.set(ChecklistItem.schema.name, "checklist.programEnrolment.individual.lowestAddressLevel.uuid = $0");
import RealmQueryService from "./query/RealmQueryService";

function getEntityApprovalStatuses(service, schema, status) {
return service.getAll(schema)
Expand Down Expand Up @@ -56,12 +50,8 @@ class EntityApprovalStatusService extends BaseService {
const applicableEntitiesSchema = EntityApprovalStatus.getApprovalEntitiesSchema();
const result = _.map(applicableEntitiesSchema, (schema) => {
let entities = getEntityApprovalStatuses(this, schema, approvalStatus_status);
const addressFilter = _.find(reportFilters, (x: DashboardReportFilter) => x.type === CustomFilter.type.Address);
if (!_.isNil(addressFilter)) {
addressFilter.filterValue.forEach((x: AddressLevel) => {
entities = entities.filtered(locationBasedQueries.get(schema), x.uuid);
});
}
const addressFilter = DashboardReportFilter.getAddressFilter(reportFilters);
entities = RealmQueryService.filterBasedOnAddress(schema, entities, addressFilter)
return {title: schema, data: entities};
});
return {status: approvalStatus_status, result};
Expand Down
Loading

0 comments on commit 0fd8ab6

Please sign in to comment.