/// <reference path="../.ref/js/santedb.js" />
// Convert an age to a date
function ageToDate(age, onDate) {
return moment(onDate).subtract({years: age}).startOf('day').toDate();
}
/// Convert a date to an age
function dateToAge(date, onDate) {
return moment(onDate).diff(date, 'years', false);
}
const ENCOUNTER_FLOW = {
EXTENSION_URL: 'http://santedb.org/emr/extensions/encounter-flow',
CHECKED_IN: 'A63E9BCC-BE32-4EA6-A43F-1F3C771114D4',
WAIT_OBSERVATION: 'AEDF62BB-48F5-437E-914D-36E0CD57B8F8',
WAIT_SPECIALIST: 'F5201716-8AA2-4BB9-B574-763E87E3372D'
};
const ADT_REGISTRATION_TYPES = {
BIRTH: 'f562e322-17ca-11eb-adc1-0242ac120002',
DEATH: 'f562e458-17ca-11eb-adc1-0242ac120002',
ADMIT: 'f562e624-17ca-11eb-adc1-0242ac120002'
}
const TEMPLATE_IDS = {
SupplementAdministration: 'feac9b2d-e560-4b75-ac77-921bf0eceee8',
BirthRegistration: 'c521e96f-3e5e-4347-8279-5228b4b68be6',
DeathRegistration: '627b5b71-ba67-484b-811c-9ef00ec4d5f0',
NewbornInformation: 'b0ca8509-6c0d-403a-b381-5485fdce5794',
CauseOfDeath: '3fc9cce1-b9bb-4a9d-b054-2d19fa34da72',
Patient: '81bc8c96-2f02-4c3f-9e2a-50fba42984a7',
BirthLocation: 'b7548aa1-97a6-4c3a-b735-2d1dbf2898f8',
BirthDeliveryMethod: '5d31af1e-8bd5-4e22-a1cb-299a7a91ccbb',
BirthDeliveryOutcome: '6f48110f-c5e7-47a1-ae02-00ef94c1edcc',
BirthWeight: '20691188-f1ca-4d06-90a4-8f857c293853',
BodyHeight: 'e052a85e-b7fb-4808-aa5c-14147abd5fe8',
PregnancyHistory: 'ea4e5cfb-fb49-434f-8b5e-c8f027f18775',
ClinicalDeath: '740bd62b-54bf-4bba-8546-954cdb5bb63a',
VerificationStatus: '637be9d0-1d17-46b6-abce-35f90fb0eb9a',
ImmunizationAdministration: '50ac9b2d-e560-4b75-ac77-921bf0eceee8'
}
/**
* @class
* @static
* @constructor
* @summary SanteEMR Binding Class
* @description This is a wrapper class that encapsulates the functionality of SanteEMR
*/
function SanteEMRWrapper() {
/**
* @method
* @private
* @summary Prepares the {@link:encounter} for submission by processing reference extensions and extracting the components
* @param {PatientEncounter} encounter The encounter to prepare
* @return {Bundle} The bundled encounter submission
*/
async function _bundleVisit(encounter) {
encounter = new PatientEncounter(angular.copy(encounter));
// Process extensions
if(encounter.extension) {
Object.keys(encounter.extension).forEach(url => {
encounter.extension[url] = encounter.extension[url].map(ext => {
if(ext.$type) // reference
{
return SanteDB.application.encodeReferenceExtension(ext.$type, ext.id);
}
return ext;
});
});
}
encounter.operation = BatchOperationType.UpdateInt;
encounter = await prepareActForSubmission(encounter);
return bundleRelatedObjects(encounter, [ "Informant", "RecordTarget", "Location", "Performer", "Authororiginator", "_HasComponent", "Fulfills" ]);
}
/**
* @method
* @summary Perform an analysis of the actions in the {@link:encounter} and return the detected issues
* @param {PatientEncounter} encounter The encounter containing data to be analysed
* @returns {Array} The array of detected issues
*/
this.analyzeVisit = async function(encounter) {
try {
var bundle = await _bundleVisit(encounter);
var result = await SanteDB.resources.bundle.invokeOperationAsync(null, "analyze", {
target: bundle
});
return result;
}
catch(e) {
throw new Exception("EmrException", "Could not analyze the submitted visit", null, e);
}
}
/**
* @method
* @memberof SanteEMRWrapper
* @param {string} patientId The patient identifier to show the checkin modal for
*/
this.showCheckin = function(patientId) {
var checkinModal = angular.element("#checkinModal");
if(checkinModal == null) {
console.warn("Have not included the checkin-modal.html file");
return;
}
checkinModal.scope().patientId = patientId;
$("#checkinModal").modal('show');
}
/**
* @method
* @memberof SanteEMRWrapper
* @param {string} encounter The encounter or encounter id to be discharged
* @param {timeout} $timeout The scope timeout service
*/
this.showDischarge = function(encounter, $timeout) {
var dischargeModal = angular.element("#dischargeModal");
if(dischargeModal == null) {
console.warn("Have not included the discharge-modal.html file");
}
SanteEMR.analyzeVisit(encounter).then(r => {
var enc = angular.copy(encounter);
enc._issues = r;
var scope = dischargeModal.scope();
$timeout(() => {
scope.encounter = enc;
$("#dischargeModal").modal('show');
})
});
}
/**
* @method
* @memberof SanteEMRWrapper
* @param {string} encounter The encounter object to show the modal for
*/
this.showRequeue = function(encounter) {
var requeueModal = angular.element("#returnModal");
if(requeueModal == null) {
console.warn("Have not included the return-waiting-modal.html file");
return;
}
requeueModal.scope().encounter = encounter;
$("#returnModal").modal('show');
}
/**
* @summary Determines whether the patient has an open encounter or not
* @param {Patient} patient The patient which is supposed to have the open encounter
* @returns The updated patient with a populated tag if the encounter is open
*/
this.patientHasOpenEncounter = async function (patient) {
if (patient.id) {
try {
var encounters = await SanteDB.resources.patientEncounter.findAsync({ moodConcept: ActMoodKeys.Eventoccurrence, statusConcept: StatusKeys.Active, "participation[RecordTarget].player": patient.id, _count: 0, _includeTotal: true });
if (encounters.totalResults > 0) {
patient.tag = patient.tag || {};
patient.tag.$hasEncounter = true;
}
}
catch (e) { }
}
return patient;
}
/**
* @summary Resolves the template icon for the specified act/entity template
* @param {string} templateId The template mnemonic to resolve the icon for
* @returns The resolved icon
*/
this.resolveTemplateIcon = function(templateId) {
var template = SanteDB.application.getTemplateMetadata(templateId);
if(template) {
return template.icon;
}
else {
return "fa-notes-medical";
}
}
/**
* @summary Resolve the summary template (one line summary) for the template
* @param {string} templateId The template mnemonic to resolve the summary for
* @returns {String} The location of the summary template
*/
this.resolveSummaryTemplate = function(templateId) {
var templateValue = SanteDB.application.resolveTemplateSummary(templateId);
if(templateValue == null) {
return "/org.santedb.uicore/partials/act/noTemplate.html"
}
return templateValue;
}
/**
* @summary Save the encounter
* @method
* @param {PatientEncounter} encounter The encounter that is to be saved
* @returns {PatientEncounter} The updated encounter
*/
this.saveVisitAsync = async function(encounter) {
try {
var submissionBundle = await _bundleVisit(encounter);
// Is the current user listed as a performer?
var myUserId = await SanteDB.authentication.getCurrentUserEntityId();
// For each entry which is being updated set the performer
submissionBundle.resource.filter(act => act.operation != BatchOperationType.IgnoreInt && act.operation != BatchOperationType.Ignore).forEach(act => {
act.participation = act.participation || {};
var participationType = "Performer";
if(act.tag && act.tag.isBackEntry && act.tag.isBackEntry[0] != "True") {
participationType = "DataEnterer";
}
act.participation = act.participation || {};
act.participation[participationType] = act.participation[participationType] || [];
if(act.participation[participationType].find(o=>o.player == myUserId) == null) {
act.participation[participationType].push(new ActParticipation({
player: myUserId
}));
}
});
submissionBundle = await SanteDB.resources.bundle.insertAsync(submissionBundle);
return submissionBundle.resource.find(o=>o.$type == "PatientEncounter");
}
catch(e) {
throw new Exception("EmrException", e.message, null, e);
}
}
/**
* @summary Starts a visit given the input parameters provided
* @param {string} templateId The visit template (encounter template) which is to be started, this dictates the input form and the structure of the visit
* @param {string} carePathway The care pathway in which this visit fits (used for generating the CDSS actions)
* @param {string} recordTargetId The identification of the record target to which the visit is intended
* @param {ActRelationship} fulfills An array of {@link:ActRelationship} objects which represent the encounter in the care plan that this visit fulfills
* @param {ActRelationship} fulfillmentComponents An array of {@link:ActRelationship} objects which reprensets the proposals from the stored care plan which this visit is fulfilling
* @param {ActParticipation} informantPtcpt The informant / guardian on the act
* @returns {PatientEncounter} The constructed and saved {@link:PatientEncounter}
*/
this.startVisitAsync = async function(templateId, carePathway, recordTargetId, fulfills, fulfillmentComponents, informantPtcpt) {
try {
var submission = new Bundle({ resource: [] });
// Template
var template = await SanteDB.application.getTemplateContentAsync(templateId, {
recordTargetId: recordTargetId,
facilityId: await SanteDB.authentication.getCurrentFacilityId(),
userEntityId: await SanteDB.authentication.getCurrentUserEntityId()
});
var encounter = new PatientEncounter(template);
encounter.id = encounter.id || SanteDB.application.newGuid();
encounter.relationship = encounter.relationship || {};
encounter.relationship.HasComponent = encounter.relationship.HasComponent || [];
encounter.relationship.Fulfills = fulfills;
// Ensure the appropriate keys are set
encounter.startTime = encounter.actTime = new Date();
encounter.statusConcept = StatusKeys.Active;
encounter.extension = encounter.extension || {};
encounter.extension[ENCOUNTER_FLOW.EXTENSION_URL] = [ SanteDB.application.encodeReferenceExtension(Concept.name, ENCOUNTER_FLOW.CHECKED_IN) ];
// Set the status
// Compute the actions to be performed
var actions = await SanteDB.resources.patient.invokeOperationAsync(recordTargetId, "generate-careplan", {
pathway: carePathway,
//firstOnly: true,
encounter: template.templateModel.mnemonic,
period: moment().format("YYYY-MM-DD"),
_includeBackentry: true
}, undefined, "min");
actions.relationship.HasComponent.forEach(comp => {
var ar = new ActRelationship({
relationshipType: comp.relationshipType,
target: comp.target || comp.targetModel.id || SanteDB.application.newGuid(),
targetModel: comp.targetModel,
source: encounter.id
});
encounter.relationship.HasComponent.push(ar);
comp.targetModel.id = comp.targetModel.id || ar.target;
comp.targetModel.moodConcept = encounter.moodConcept;
delete comp.targetModel.moodConceptModel;
comp.targetModel.statusConcept = encounter.statusConcept;
delete comp.targetModel.statusConceptModel;
// Fulfillment for the target model
if (comp.targetModel && comp.targetModel.protocol) {
var fulfillment = fulfillmentComponents.find(o => {
var targetAct = o.targetModel;
return targetAct.protocol.find(p => comp.targetModel.protocol.find(p2 => p2.protocol == p.protocol && p2.sequence == p.sequence))
});
if (fulfillment) {
comp.targetModel.relationship = comp.targetModel.relationship || {};
comp.targetModel.relationship.Fulfills = comp.targetModel.relationship.Fulfills || [];
comp.targetModel.relationship.Fulfills.push(new ActRelationship({
target: fulfillment.target
}));
}
}
});
if(informantPtcpt) {
encounter.participation = encounter.participation || {};
encounter.participation.Informant = encounter.participation.Informant || [];
encounter.participation.Informant.push(informantPtcpt);
if(informantPtcpt.playerModel && informantPtcpt.player != informantPtcpt.playerModel.id ) {
delete informantPtcpt.playerModel;
}
}
encounter = await prepareActForSubmission(encounter);
submission = bundleRelatedObjects(encounter, [ "Informant", "RecordTarget", "Location", "Authororiginator" ]);
if(informantPtcpt && informantPtcpt.playerModel && informantPtcpt.player == informantPtcpt.playerModel.id ) {
informantPtcpt.playerModel = await prepareEntityForSubmission(informantPtcpt.playerModel, true);
submission.resource.push(informantPtcpt.playerModel);
submission.resource.push(new EntityRelationship(
informantPtcpt.playerModel.relationship.$other[0]
));
delete informantPtcpt.playerModel.relationship.$other;
delete informantPtcpt.playerModel;
}
// Now we want to submit
var submittedBundle = await SanteDB.resources.bundle.insertAsync(submission);
return submittedBundle.resource.find(o=>o.$type == "PatientEncounter");
}
catch(e) {
throw new Exception("EmrException", e.message, null, e);
}
}
}
/**
* @type {SanteEMRWrapper}
* @global
*/
var SanteEMR = new SanteEMRWrapper();
// Helper functions
Patient.prototype.age = function(measure) {
return moment().diff(this.dateOfBirth, measure || 'years', false);
}
Patient.prototype.hasCondition = function(conditionTypeConcept)
{
}