/// <summary> /// Persist the person relationship /// </summary> public SVC.Core.DataTypes.VersionedDomainIdentifier Persist(System.Data.IDbConnection conn, System.Data.IDbTransaction tx, System.ComponentModel.IComponent data, bool isUpdate) { // Is this a replacement var pp = new PersonPersister(); PersonRegistrationRef refr = data as PersonRegistrationRef; Person psn = pp.GetPerson(conn, tx, refr.AlternateIdentifiers[0], true); Person cntrPsn = data.Site.Container as Person; if (psn == null || cntrPsn == null) { throw new ConstraintException(ApplicationContext.LocaleService.GetString("DBCF00B")); } else if (psn.Id == cntrPsn.Id) { throw new ConstraintException(ApplicationContext.LocaleService.GetString("DBCF00D")); } // Load the container person from DB so we get all data Person dbCntrPsn = pp.GetPerson(conn, tx, new SVC.Core.DataTypes.DomainIdentifier() { Domain = ApplicationContext.ConfigurationService.OidRegistrar.GetOid(ClientRegistryOids.CLIENT_CRID).Oid, Identifier = cntrPsn.Id.ToString() }, true); pp.MergePersons(dbCntrPsn, cntrPsn); // Load the components for the person #if DEBUG Trace.TraceInformation("Registration reference {0} > {1}", psn.Id, dbCntrPsn.Id); #endif DbUtil.DePersistComponents(conn, psn, this, true); if (psn == null || dbCntrPsn == null) { throw new ConstraintException(ApplicationContext.LocaleService.GetString("DBCF00B")); } dbCntrPsn.Site = (data.Site.Container as IComponent).Site; var role = (refr.Site as HealthServiceRecordSite).SiteRoleType; var symbolic = (refr.Site as HealthServiceRecordSite).IsSymbolic; // If true, the replacement does not cascade and is a symbolic replacement of only the identifiers listed // Replacement? if (role == HealthServiceRecordSiteRoleType.ReplacementOf) { // First, we obsolete all records with the existing person foreach (var id in psn.AlternateIdentifiers.FindAll(o => refr.AlternateIdentifiers.Exists(a => a.Domain == o.Domain))) { id.UpdateMode = SVC.Core.DataTypes.UpdateModeType.Remove; } //psn.AlternateIdentifiers.RemoveAll(o => o.UpdateMode != SVC.Core.DataTypes.UpdateModeType.Remove); // Not symbolic, means that we do a hard replace // Symbolic replace = Just replace the reference to that identifier // Hard replace = Merge the new and old record and then replace them if (!symbolic) { #if DEBUG Trace.TraceInformation("Performing an administrative migration of identifiers and information from {0} > {1}", psn.Id, dbCntrPsn.Id); #endif // Now to copy the components of the current version down //foreach (IComponent cmp in refr.Site.Container.Components) // if (cmp != refr) // dbCntrPsn.Add((cmp as HealthServiceRecordComponent).Clone() as IComponent); // Merge the two records in memory taking the newer data // This is a merge from old to new in order to capture any data elements // that have been updated in the old that might be newer (or more accurate) than the // the new if (psn.AlternateIdentifiers == null) { dbCntrPsn.AlternateIdentifiers = new List <SVC.Core.DataTypes.DomainIdentifier>(); } else if (psn.OtherIdentifiers == null) { dbCntrPsn.OtherIdentifiers = new List <KeyValuePair <SVC.Core.DataTypes.CodeValue, SVC.Core.DataTypes.DomainIdentifier> >(); } foreach (var id in psn.AlternateIdentifiers) { // Remove the identifier from the original id.UpdateMode = SVC.Core.DataTypes.UpdateModeType.Remove; // If this is a duplicate id then don't add if (dbCntrPsn.AlternateIdentifiers.Exists(i => i.Domain == id.Domain && i.Identifier == id.Identifier)) { continue; } bool isPrivate = false; var oidData = ApplicationContext.ConfigurationService.OidRegistrar.FindData(id.Domain); if (oidData != null) { isPrivate = !(oidData.Attributes.Exists(o => o.Key == "IsMergeSurvivor") && Convert.ToBoolean(oidData.Attributes.Find(o => o.Key == "IsMergeSurvivor").Value)); } // Add to alternate identifiers dbCntrPsn.AlternateIdentifiers.Add(new SVC.Core.DataTypes.DomainIdentifier() { AssigningAuthority = id.AssigningAuthority, UpdateMode = SVC.Core.DataTypes.UpdateModeType.AddOrUpdate, IsLicenseAuthority = false, IsPrivate = isPrivate, // TODO: Make this a configuration flag (cntrPsn.AlternateIdentifiers.Exists(i=>i.Domain == id.Domain)), Identifier = id.Identifier, Domain = id.Domain }); } foreach (var id in psn.OtherIdentifiers) { // Remove the identifier from the original id.Value.UpdateMode = SVC.Core.DataTypes.UpdateModeType.Remove; // If this is a duplicate id then don't add if (dbCntrPsn.OtherIdentifiers.Exists(i => i.Value.Domain == id.Value.Domain && i.Value.Identifier == id.Value.Identifier)) { continue; } // Add to other identifiers var oth = new KeyValuePair <SVC.Core.DataTypes.CodeValue, SVC.Core.DataTypes.DomainIdentifier>( id.Key, new SVC.Core.DataTypes.DomainIdentifier() { AssigningAuthority = id.Value.AssigningAuthority, UpdateMode = SVC.Core.DataTypes.UpdateModeType.Add, IsLicenseAuthority = false, IsPrivate = (dbCntrPsn.OtherIdentifiers.Exists(i => i.Value.Domain == id.Value.Domain)), Identifier = id.Value.Identifier, Domain = id.Value.Domain }); // Copy extensions var extns = psn.FindAllExtensions(o => o.PropertyPath == String.Format("OtherIdentifiers[{0}{1}]", oth.Value.Domain, oth.Value.Identifier)); if (extns != null) { foreach (var ex in extns) { if (dbCntrPsn.FindExtension(o => o.PropertyPath == ex.PropertyPath && o.Name == ex.Name) == null) { dbCntrPsn.Add(ex); } } } dbCntrPsn.OtherIdentifiers.Add(oth); } // Make sure we don't update what we don't need to dbCntrPsn.Addresses = psn.Addresses = null; dbCntrPsn.Citizenship = psn.Citizenship = null; dbCntrPsn.Employment = psn.Employment = null; dbCntrPsn.Language = psn.Language = null; dbCntrPsn.Names = psn.Names = null; dbCntrPsn.Race = psn.Race = null; dbCntrPsn.TelecomAddresses = psn.TelecomAddresses = null; dbCntrPsn.BirthTime = psn.BirthTime = null; dbCntrPsn.DeceasedTime = psn.DeceasedTime = null; // Remove the old person from the db psn.Status = SVC.Core.ComponentModel.Components.StatusType.Obsolete; // obsolete the old person } else // migrate identifiers { foreach (var id in refr.AlternateIdentifiers) { bool isPrivate = false; var oidData = ApplicationContext.ConfigurationService.OidRegistrar.FindData(id.Domain); if (oidData != null) { isPrivate = !(oidData.Attributes.Exists(o => o.Key == "IsMergeSurvivor") && Convert.ToBoolean(oidData.Attributes.Find(o => o.Key == "IsMergeSurvivor").Value)); } dbCntrPsn.AlternateIdentifiers.Add(new SVC.Core.DataTypes.DomainIdentifier() { AssigningAuthority = id.AssigningAuthority, UpdateMode = SVC.Core.DataTypes.UpdateModeType.AddOrUpdate, IsLicenseAuthority = false, IsPrivate = isPrivate, // TODO: Make this a configuration flag (cntrPsn.AlternateIdentifiers.Exists(i=>i.Domain == id.Domain)), Identifier = id.Identifier, Domain = id.Domain }); } // set to ignore if (psn.Names != null) { foreach (var rc in psn.Names) { rc.UpdateMode = SVC.Core.DataTypes.UpdateModeType.Ignore; } } // set to ignore if (psn.Addresses != null) { foreach (var rc in psn.Addresses) { rc.UpdateMode = SVC.Core.DataTypes.UpdateModeType.Ignore; } } // set to ignore if (psn.Race != null) { foreach (var rc in psn.Race) { rc.UpdateMode = SVC.Core.DataTypes.UpdateModeType.Ignore; } } } // Now update the person //psn.Site = refr.Site; //pp.Persist(conn, tx, psn, true); // update the person record #if DEBUG Trace.TraceInformation("Step 1 : Prepare update to person #{0}", psn.Id, cntrPsn.Id, !symbolic); #endif var regEvent = this.GetRegistrationEvent(conn, tx, psn); // get the registration event var changeSummary = DbUtil.GetRegistrationEvent(refr).FindComponent(HealthServiceRecordSiteRoleType.ReasonFor | HealthServiceRecordSiteRoleType.OlderVersionOf) as ChangeSummary; if (changeSummary != null) { regEvent.RemoveAllFromRole(HealthServiceRecordSiteRoleType.ReasonFor | HealthServiceRecordSiteRoleType.OlderVersionOf); regEvent.Add(new ChangeSummary() { ChangeType = changeSummary.ChangeType, EffectiveTime = changeSummary.EffectiveTime, LanguageCode = changeSummary.LanguageCode, Status = changeSummary.Status, Timestamp = changeSummary.Timestamp }, "CHG", HealthServiceRecordSiteRoleType.ReasonFor | HealthServiceRecordSiteRoleType.OlderVersionOf, null); } if (!symbolic) { regEvent.Status = StatusType.Obsolete; // obsolete } regEvent.RemoveAllFromRole(HealthServiceRecordSiteRoleType.SubjectOf); regEvent.Add(psn, "SUBJ", HealthServiceRecordSiteRoleType.SubjectOf, null); #if DEBUG Trace.TraceInformation("Step 2 : Perform update to person #{0}", psn.Id); #endif new RegistrationEventPersister().Persist(conn, tx, regEvent, true); refr.AlternateIdentifiers.Clear(); refr.AlternateIdentifiers.Add(new SVC.Core.DataTypes.DomainIdentifier() { Domain = ApplicationContext.ConfigurationService.OidRegistrar.GetOid("CR_CID").Oid, Identifier = psn.Id.ToString() }); //pp.CreatePersonVersion(conn, tx, psn); //DbUtil.PersistComponents(conn, tx, false, this, psn); // Now, we have to prepare an event so that this all makes sense // if we de-persist the most recent version (to reflect changes made) // Store the merged new record #if DEBUG Trace.TraceInformation("Step 3 : Perform update to person #{0}", dbCntrPsn.Id); #endif pp.CreatePersonVersion(conn, tx, dbCntrPsn); // Components DbUtil.PersistComponents(conn, tx, false, this, dbCntrPsn); // Now update the backreference to up the chain it gets updated cntrPsn.VersionId = dbCntrPsn.VersionId; } // Create the link #if DEBUG Trace.TraceInformation("Creating link between persons {0} > {1}", psn.Id, dbCntrPsn.Id); #endif using (var cmd = DbUtil.CreateCommandStoredProc(conn, tx)) { cmd.CommandText = "crt_psn_lnk"; cmd.Parameters.Add(DbUtil.CreateParameterIn(cmd, "psn_id_in", DbType.Decimal, dbCntrPsn.Id)); cmd.Parameters.Add(DbUtil.CreateParameterIn(cmd, "psn_vrsn_id_in", DbType.Decimal, dbCntrPsn.VersionId)); cmd.Parameters.Add(DbUtil.CreateParameterIn(cmd, "lnk_psn_id_in", DbType.Decimal, psn.Id)); cmd.Parameters.Add(DbUtil.CreateParameterIn(cmd, "lnk_cls_in", DbType.Decimal, (decimal)role)); cmd.Parameters.Add(DbUtil.CreateParameterIn(cmd, "symbolic_in", DbType.Boolean, symbolic)); cmd.ExecuteNonQuery(); } // Send notification that duplicates were resolved //if (symbolic) //{ // // Send an duplicates resolved message // IClientNotificationService notificationService = ApplicationContext.CurrentContext.GetService(typeof(IClientNotificationService)) as IClientNotificationService; // if (notificationService != null) // notificationService.NotifyDuplicatesResolved(cntrPsn, refr.AlternateIdentifiers[0]); //} refr.Id = psn.Id; // Person identifier return(new SVC.Core.DataTypes.VersionedDomainIdentifier() { Identifier = psn.Id.ToString(), Version = psn.VersionId.ToString() }); }
/// <summary> /// Handle the PIX merge request /// </summary> private IMessage HandlePixMerge(NHapi.Model.V231.Message.ADT_A39 request, Hl7MessageReceivedEventArgs evt) { // Get config var config = this.Context.GetService(typeof(ISystemConfigurationService)) as ISystemConfigurationService; var locale = this.Context.GetService(typeof(ILocalizationService)) as ILocalizationService; var dataService = this.Context.GetService(typeof(IClientRegistryDataService)) as IClientRegistryDataService; // Create a details array List <IResultDetail> dtls = new List <IResultDetail>(); // Validate the inbound message MessageUtil.Validate((IMessage)request, config, dtls, this.Context); IMessage response = null; // Control if (request == null) { return(null); } // Data controller //DataUtil dataUtil = new DataUtil() { Context = this.Context }; AuditUtil auditUtil = new AuditUtil() { Context = this.Context }; // Construct appropriate audit List <AuditData> audit = new List <AuditData>(); try { // Create Query Data ComponentUtility cu = new ComponentUtility() { Context = this.Context }; DeComponentUtility dcu = new DeComponentUtility() { Context = this.Context }; var data = cu.CreateComponents(request, dtls); if (data == null) { throw new InvalidOperationException(locale.GetString("MSGE00A")); } // Merge var result = dataService.Merge(data, request.MSH.ProcessingID.ProcessingID.Value == "P" ? DataPersistenceMode.Production : DataPersistenceMode.Debugging); if (result == null || result.VersionId == null) { throw new InvalidOperationException(locale.GetString("DTPE001")); } List <VersionedDomainIdentifier> deletedRecordIds = new List <VersionedDomainIdentifier>(), updatedRecordIds = new List <VersionedDomainIdentifier>(); // Subjects var oidData = config.OidRegistrar.GetOid("CR_CID").Oid; foreach (Person subj in data.FindAllComponents(SVC.Core.ComponentModel.HealthServiceRecordSiteRoleType.SubjectOf)) { PersonRegistrationRef replcd = subj.FindComponent(SVC.Core.ComponentModel.HealthServiceRecordSiteRoleType.ReplacementOf) as PersonRegistrationRef; deletedRecordIds.Add(new VersionedDomainIdentifier() { Identifier = replcd.Id.ToString(), Domain = oidData }); updatedRecordIds.Add(new VersionedDomainIdentifier() { Identifier = subj.Id.ToString(), Domain = oidData }); } // Now audit audit.Add(auditUtil.CreateAuditData("ITI-8", ActionType.Delete, OutcomeIndicator.Success, evt, deletedRecordIds)); audit.Add(auditUtil.CreateAuditData("ITI-8", ActionType.Update, OutcomeIndicator.Success, evt, updatedRecordIds)); // Now process the result response = MessageUtil.CreateNack(request, dtls, this.Context, typeof(NHapi.Model.V231.Message.ACK)); MessageUtil.UpdateMSH(new NHapi.Base.Util.Terser(response), request, config); (response as NHapi.Model.V231.Message.ACK).MSH.MessageType.TriggerEvent.Value = request.MSH.MessageType.TriggerEvent.Value; (response as NHapi.Model.V231.Message.ACK).MSH.MessageType.MessageType.Value = "ACK"; } catch (Exception e) { Trace.TraceError(e.ToString()); if (!dtls.Exists(o => o.Message == e.Message || o.Exception == e)) { dtls.Add(new ResultDetail(ResultDetailType.Error, e.Message, e)); } response = MessageUtil.CreateNack(request, dtls, this.Context, typeof(NHapi.Model.V231.Message.ACK)); audit.Add(auditUtil.CreateAuditData("ITI-8", ActionType.Delete, OutcomeIndicator.EpicFail, evt, new List <VersionedDomainIdentifier>())); } finally { IAuditorService auditSvc = this.Context.GetService(typeof(IAuditorService)) as IAuditorService; if (auditSvc != null) { foreach (var aud in audit) { auditSvc.SendAudit(aud); } } } return(response); }