///<summary>Inserts one OIDExternal into the database. Provides option to use the existing priKey.</summary> public static long Insert(OIDExternal oIDExternal, bool useExistingPK) { if (!useExistingPK && PrefC.RandomKeys) { oIDExternal.OIDExternalNum = ReplicationServers.GetKey("oidexternal", "OIDExternalNum"); } string command = "INSERT INTO oidexternal ("; if (useExistingPK || PrefC.RandomKeys) { command += "OIDExternalNum,"; } command += "IDType,IDInternal,IDExternal,rootExternal) VALUES("; if (useExistingPK || PrefC.RandomKeys) { command += POut.Long(oIDExternal.OIDExternalNum) + ","; } command += "'" + POut.String(oIDExternal.IDType.ToString()) + "'," + POut.Long(oIDExternal.IDInternal) + "," + "'" + POut.String(oIDExternal.IDExternal) + "'," + "'" + POut.String(oIDExternal.rootExternal) + "')"; if (useExistingPK || PrefC.RandomKeys) { Db.NonQ(command); } else { oIDExternal.OIDExternalNum = Db.NonQ(command, true, "OIDExternalNum", "oIDExternal"); } return(oIDExternal.OIDExternalNum); }
///<summary>Inserts one OIDExternal into the database. Returns the new priKey.</summary> public static long Insert(OIDExternal oIDExternal) { if (DataConnection.DBtype == DatabaseType.Oracle) { oIDExternal.OIDExternalNum = DbHelper.GetNextOracleKey("oidexternal", "OIDExternalNum"); int loopcount = 0; while (loopcount < 100) { try { return(Insert(oIDExternal, true)); } catch (Oracle.DataAccess.Client.OracleException ex) { if (ex.Number == 1 && ex.Message.ToLower().Contains("unique constraint") && ex.Message.ToLower().Contains("violated")) { oIDExternal.OIDExternalNum++; loopcount++; } else { throw ex; } } } throw new ApplicationException("Insert failed. Could not generate primary key."); } else { return(Insert(oIDExternal, false)); } }
///<summary>Converts a DataTable to a list of objects.</summary> public static List <OIDExternal> TableToList(DataTable table) { List <OIDExternal> retVal = new List <OIDExternal>(); OIDExternal oIDExternal; for (int i = 0; i < table.Rows.Count; i++) { oIDExternal = new OIDExternal(); oIDExternal.OIDExternalNum = PIn.Long(table.Rows[i]["OIDExternalNum"].ToString()); string iDType = table.Rows[i]["IDType"].ToString(); if (iDType == "") { oIDExternal.IDType = (IdentifierType)0; } else { try{ oIDExternal.IDType = (IdentifierType)Enum.Parse(typeof(IdentifierType), iDType); } catch { oIDExternal.IDType = (IdentifierType)0; } } oIDExternal.IDInternal = PIn.Long(table.Rows[i]["IDInternal"].ToString()); oIDExternal.IDExternal = PIn.String(table.Rows[i]["IDExternal"].ToString()); oIDExternal.rootExternal = PIn.String(table.Rows[i]["rootExternal"].ToString()); retVal.Add(oIDExternal); } return(retVal); }
///<summary>Inserts one OIDExternal into the database. Provides option to use the existing priKey. Doesn't use the cache.</summary> public static long InsertNoCache(OIDExternal oIDExternal, bool useExistingPK) { bool isRandomKeys = Prefs.GetBoolNoCache(PrefName.RandomPrimaryKeys); string command = "INSERT INTO oidexternal ("; if (!useExistingPK && isRandomKeys) { oIDExternal.OIDExternalNum = ReplicationServers.GetKeyNoCache("oidexternal", "OIDExternalNum"); } if (isRandomKeys || useExistingPK) { command += "OIDExternalNum,"; } command += "IDType,IDInternal,IDExternal,rootExternal) VALUES("; if (isRandomKeys || useExistingPK) { command += POut.Long(oIDExternal.OIDExternalNum) + ","; } command += "'" + POut.String(oIDExternal.IDType.ToString()) + "'," + POut.Long(oIDExternal.IDInternal) + "," + "'" + POut.String(oIDExternal.IDExternal) + "'," + "'" + POut.String(oIDExternal.rootExternal) + "')"; if (useExistingPK || isRandomKeys) { Db.NonQ(command); } else { oIDExternal.OIDExternalNum = Db.NonQ(command, true, "OIDExternalNum", "oIDExternal"); } return(oIDExternal.OIDExternalNum); }
///<summary>Converts a DataTable to a list of objects.</summary> public static List <OIDExternal> TableToList(DataTable table) { List <OIDExternal> retVal = new List <OIDExternal>(); OIDExternal oIDExternal; foreach (DataRow row in table.Rows) { oIDExternal = new OIDExternal(); oIDExternal.OIDExternalNum = PIn.Long(row["OIDExternalNum"].ToString()); string iDType = row["IDType"].ToString(); if (iDType == "") { oIDExternal.IDType = (OpenDentBusiness.IdentifierType) 0; } else { try{ oIDExternal.IDType = (OpenDentBusiness.IdentifierType)Enum.Parse(typeof(OpenDentBusiness.IdentifierType), iDType); } catch { oIDExternal.IDType = (OpenDentBusiness.IdentifierType) 0; } } oIDExternal.IDInternal = PIn.Long(row["IDInternal"].ToString()); oIDExternal.IDExternal = PIn.String(row["IDExternal"].ToString()); oIDExternal.rootExternal = PIn.String(row["rootExternal"].ToString()); retVal.Add(oIDExternal); } return(retVal); }
///<summary>Updates one OIDExternal in the database.</summary> public static void Update(OIDExternal oIDExternal) { string command = "UPDATE oidexternal SET " + "IDType = '" + POut.String(oIDExternal.IDType.ToString()) + "', " + "IDInternal = " + POut.Long(oIDExternal.IDInternal) + ", " + "IDExternal = '" + POut.String(oIDExternal.IDExternal) + "', " + "rootExternal = '" + POut.String(oIDExternal.rootExternal) + "' " + "WHERE OIDExternalNum = " + POut.Long(oIDExternal.OIDExternalNum); Db.NonQ(command); }
///<summary>Inserts one OIDExternal into the database. Returns the new priKey. Doesn't use the cache.</summary> public static long InsertNoCache(OIDExternal oIDExternal) { if (DataConnection.DBtype == DatabaseType.MySql) { return(InsertNoCache(oIDExternal, false)); } else { if (DataConnection.DBtype == DatabaseType.Oracle) { oIDExternal.OIDExternalNum = DbHelper.GetNextOracleKey("oidexternal", "OIDExternalNum"); //Cacheless method } return(InsertNoCache(oIDExternal, true)); } }
public void ComposeNewRxDoseSpot() { string additionalHeaders = "Content-Type: application/x-www-form-urlencoded\r\n"; string doseSpotUrl = Introspection.GetOverride(Introspection.IntrospectionEntity.DoseSpotSingleSignOnURL, "https://my.dosespot.com/LoginSingleSignOn.aspx"); #if DEBUG doseSpotUrl = "https://my.staging.dosespot.com/LoginSingleSignOn.aspx?b=2"; #endif OIDExternal oidPatID = DoseSpot.GetDoseSpotPatID(PatCur.PatNum); if (oidPatID == null) { browser.DocumentCompleted += new System.Windows.Forms.WebBrowserDocumentCompletedEventHandler(this.GetPatientIdFromWebBrowser); } browser.Navigate(doseSpotUrl, "", PostDataBytes, additionalHeaders); }
///<summary>Updates one OIDExternal in the database. Uses an old object to compare to, and only alters changed fields. This prevents collisions and concurrency problems in heavily used tables. Returns true if an update occurred.</summary> public static bool Update(OIDExternal oIDExternal, OIDExternal oldOIDExternal) { string command = ""; if (oIDExternal.IDType != oldOIDExternal.IDType) { if (command != "") { command += ","; } command += "IDType = '" + POut.String(oIDExternal.IDType.ToString()) + "'"; } if (oIDExternal.IDInternal != oldOIDExternal.IDInternal) { if (command != "") { command += ","; } command += "IDInternal = " + POut.Long(oIDExternal.IDInternal) + ""; } if (oIDExternal.IDExternal != oldOIDExternal.IDExternal) { if (command != "") { command += ","; } command += "IDExternal = '" + POut.String(oIDExternal.IDExternal) + "'"; } if (oIDExternal.rootExternal != oldOIDExternal.rootExternal) { if (command != "") { command += ","; } command += "rootExternal = '" + POut.String(oIDExternal.rootExternal) + "'"; } if (command == "") { return(false); } command = "UPDATE oidexternal SET " + command + " WHERE OIDExternalNum = " + POut.Long(oIDExternal.OIDExternalNum); Db.NonQ(command); return(true); }
///<summary>Returns true if Update(OIDExternal,OIDExternal) would make changes to the database. ///Does not make any changes to the database and can be called before remoting role is checked.</summary> public static bool UpdateComparison(OIDExternal oIDExternal, OIDExternal oldOIDExternal) { if (oIDExternal.IDType != oldOIDExternal.IDType) { return(true); } if (oIDExternal.IDInternal != oldOIDExternal.IDInternal) { return(true); } if (oIDExternal.IDExternal != oldOIDExternal.IDExternal) { return(true); } if (oIDExternal.rootExternal != oldOIDExternal.rootExternal) { return(true); } return(false); }
///<summary>Converts a DataTable to a list of objects.</summary> public static List<OIDExternal> TableToList(DataTable table){ List<OIDExternal> retVal=new List<OIDExternal>(); OIDExternal oIDExternal; for(int i=0;i<table.Rows.Count;i++) { oIDExternal=new OIDExternal(); oIDExternal.OIDExternalNum= PIn.Long (table.Rows[i]["OIDExternalNum"].ToString()); string iDType=table.Rows[i]["IDType"].ToString(); if(iDType==""){ oIDExternal.IDType =(IdentifierType)0; } else try{ oIDExternal.IDType =(IdentifierType)Enum.Parse(typeof(IdentifierType),iDType); } catch{ oIDExternal.IDType =(IdentifierType)0; } oIDExternal.IDInternal = PIn.Long (table.Rows[i]["IDInternal"].ToString()); oIDExternal.IDExternal = PIn.String(table.Rows[i]["IDExternal"].ToString()); oIDExternal.rootExternal = PIn.String(table.Rows[i]["rootExternal"].ToString()); retVal.Add(oIDExternal); } return retVal; }
private void GetPatientIdFromWebBrowser(object sender, WebBrowserDocumentCompletedEventArgs e) { WebBrowser myWebBrowser = sender as WebBrowser; if (e.Url != null && !string.IsNullOrEmpty(e.Url.Query)) { int doseSpotPatID; string patIdStr = DoseSpot.GetQueryParameterFromQueryString(e.Url.Query, "PatientId"); if ((!string.IsNullOrEmpty(patIdStr)) && int.TryParse(patIdStr.Trim(), out doseSpotPatID)) { if (doseSpotPatID != 0) { OIDExternal oidExternalForPat = new OIDExternal(); oidExternalForPat.rootExternal = DoseSpot.GetDoseSpotRoot() + "." + POut.Int((int)IdentifierType.Patient); oidExternalForPat.IDExternal = doseSpotPatID.ToString(); oidExternalForPat.IDInternal = PatCur.PatNum; oidExternalForPat.IDType = IdentifierType.Patient; OIDExternals.Insert(oidExternalForPat); } } } myWebBrowser.DocumentCompleted -= new System.Windows.Forms.WebBrowserDocumentCompletedEventHandler(this.GetPatientIdFromWebBrowser); }
///<summary>Inserts one OIDExternal into the database. Returns the new priKey.</summary> public static long Insert(OIDExternal oIDExternal){ if(DataConnection.DBtype==DatabaseType.Oracle) { oIDExternal.OIDExternalNum=DbHelper.GetNextOracleKey("oidexternal","OIDExternalNum"); int loopcount=0; while(loopcount<100){ try { return Insert(oIDExternal,true); } catch(Oracle.DataAccess.Client.OracleException ex){ if(ex.Number==1 && ex.Message.ToLower().Contains("unique constraint") && ex.Message.ToLower().Contains("violated")){ oIDExternal.OIDExternalNum++; loopcount++; } else{ throw ex; } } } throw new ApplicationException("Insert failed. Could not generate primary key."); } else { return Insert(oIDExternal,false); } }
public static void ProcessPRB(Patient pat,HL7DefSegment segDef,SegmentHL7 seg,MessageHL7 msg) { int probActionOrder=-1; int probCodeOrder=-1; int probStartDateOrder=-1; int probStopDateOrder=-1; int probUniqueIdOrder=-1; for(int i=0;i<segDef.hl7DefFields.Count;i++) { int itemOrder=segDef.hl7DefFields[i].OrdinalPos; switch(segDef.hl7DefFields[i].FieldName) { case "problemAction": probActionOrder=itemOrder; continue; case "problemCode": probCodeOrder=itemOrder; continue; case "problemStartDate": probStartDateOrder=itemOrder; continue; case "problemStopDate": probStopDateOrder=itemOrder; continue; case "problemUniqueId": probUniqueIdOrder=itemOrder; continue; default: continue; } } //we need these 3 items defined in order to process the problem if(probActionOrder<0 || probCodeOrder<0 || probUniqueIdOrder<0) { EventLog.WriteEntry("OpenDentHL7","The PRB segment was not processed. The segment must have an action, code, and unique ID defined.",EventLogEntryType.Information); return; } //get values from defined locations within the segment and validate info //We only add or update problems. Other actions like delete or correct are not yet supported if(seg.GetFieldComponent(probActionOrder).ToLower()!="ad" && seg.GetFieldComponent(probActionOrder).ToLower()!="up") { EventLog.WriteEntry("OpenDentHL7","The PRB segment was not processed. The action codes supported are 'AD' for add or 'UP' for update.",EventLogEntryType.Information); return; } long probDefNum=DiseaseDefs.GetNumFromSnomed(PIn.String(seg.GetFieldComponent(probCodeOrder,0))); //The problem must be a SNOMEDCT code, identified by the coding system table 0396 value "SNM" in component 3 of the CWE problem code field //There must be a disease def setup with the SNOMEDCT code in the problem list or we will ignore this problem if(seg.GetFieldComponent(probCodeOrder,2).ToLower()!="snm" || probDefNum==0) { EventLog.WriteEntry("OpenDentHL7","The PRB segment was not processed. " +"The code is not attached to an existing problem definition or is not a SNOMEDCT code.",EventLogEntryType.Information); return; } string probIdExternal=seg.GetFieldComponent(probUniqueIdOrder,0); string probRootExternal=seg.GetFieldComponent(probUniqueIdOrder,2); if(probIdExternal=="" || probRootExternal=="") { EventLog.WriteEntry("OpenDentHL7","The PRB segment was not processed. " +" The problem does not have a unique ID with assigning authority root.",EventLogEntryType.Information); return; } //If problem external ID and root is in the database, but is used to identify an object other than a problem, do not process the segment OIDExternal probOidExt=OIDExternals.GetByRootAndExtension(probRootExternal,probIdExternal); if(probOidExt!=null && probOidExt.IDType!=IdentifierType.Problem) { EventLog.WriteEntry("OpenDentHL7","The PRB segment was not processed. " +"The problem has a unique ID with assigning authority root that has already been used to identify an object of type " +probOidExt.IDType.ToString()+".",EventLogEntryType.Information); return; } long diseaseNum=0; if(probOidExt!=null) {//exists in oidexternal table and is of type Problem, so IDInternal is a DiseaseNum diseaseNum=probOidExt.IDInternal; } Disease probCur=new Disease(); probCur.DiseaseNum=diseaseNum;//probNum could be 0 if new //The problem referenced by the external root and ID is already linked in the oidexternal table, get the problem to update //Also make sure the problem linked by oidexternal table is for the patient identified in the PID segment if(diseaseNum!=0) { probCur=Diseases.GetOne(diseaseNum); if(probCur==null || probCur.PatNum!=pat.PatNum) {//should never be null if in the oidexternal table EventLog.WriteEntry("OpenDentHL7","The PRB segment was not processed. " +"The problem referenced and the patient in the PID segment do not match.",EventLogEntryType.Information); return; } } DateTime dateProbStart=DateTime.MinValue; if(probStartDateOrder>-1) { dateProbStart=FieldParser.DateTimeParse(seg.GetFieldComponent(probStartDateOrder)); } DateTime dateProbStop=DateTime.MinValue; if(probStopDateOrder>-1) { dateProbStop=FieldParser.DateTimeParse(seg.GetFieldComponent(probStopDateOrder)); } //The patient may already have an active problem with this DiseaseDefNum, but it is not referenced by this problem GUID //Mark the existing problem inactive and add a new one with StartDate of today //Add an entry in the oidexternal table that will point the problem GUID to this new problem. List<Disease> listProbsForPat=Diseases.GetDiseasesForPatient(pat.PatNum,probDefNum,true); int markedInactiveCount=0; for(int p=0;p<listProbsForPat.Count;p++) { if(listProbsForPat[p].DiseaseNum==diseaseNum) {//probNum may be 0 if there was not an existing problem referenced by the GUID in the message continue; } listProbsForPat[p].ProbStatus=ProblemStatus.Inactive; Diseases.Update(listProbsForPat[p]); markedInactiveCount++; } if(_isVerboseLogging && markedInactiveCount>0) { EventLog.WriteEntry("OpenDentHL7","Updated "+markedInactiveCount.ToString()+" problems to a status of inactive due to an incoming PRB segment.",EventLogEntryType.Information); } Disease probOld=probCur.Copy(); probCur.PatNum=pat.PatNum; probCur.DiseaseDefNum=probDefNum; probCur.ProbStatus=ProblemStatus.Active; probCur.DateStart=dateProbStart;//could be '0001-01-01' if not present or not the correct format, handled by FieldParser.DateTimeParse probCur.DateStop=dateProbStop;//could be '0001-01-01' if not present or not the correct format, handled by FieldParser.DateTimeParse if(probCur.DiseaseNum==0) {//new problem //insert new problem probCur.DiseaseNum=Diseases.Insert(probCur); if(_isVerboseLogging) { EventLog.WriteEntry("OpenDentHL7","Inserted a new problem for patient "+pat.GetNameFLnoPref()+" due to an incoming PRB segment.",EventLogEntryType.Information); } //using DiseaseNum from newly inserted problem, link to the external GUID and root in the oidexternals table probOidExt=new OIDExternal(); probOidExt.IDType=IdentifierType.Problem; probOidExt.IDInternal=probCur.DiseaseNum; probOidExt.IDExternal=probIdExternal; probOidExt.rootExternal=probRootExternal; OIDExternals.Insert(probOidExt); if(_isVerboseLogging) { EventLog.WriteEntry("OpenDentHL7","Added an external problem ID to the oidexternals table due to an incoming PRB segment.\r\nDiseaesNum: " +probCur.DiseaseNum.ToString()+", External problem ID: "+probOidExt.IDExternal+", External root: "+probOidExt.rootExternal+".",EventLogEntryType.Information); } } else {//the segment is for an existing problem, update fields if necessary Diseases.Update(probCur,probOld); if(_isVerboseLogging) { EventLog.WriteEntry("OpenDentHL7","Updated an existing problem for patient "+pat.GetNameFLnoPref()+" due to an incoming PRB segment.",EventLogEntryType.Information); } } return; }
///<summary>Updates one OIDExternal in the database.</summary> public static void Update(OIDExternal oIDExternal){ string command="UPDATE oidexternal SET " +"IDType = '"+POut.String(oIDExternal.IDType.ToString())+"', " +"IDInternal = "+POut.Long (oIDExternal.IDInternal)+", " +"IDExternal = '"+POut.String(oIDExternal.IDExternal)+"', " +"rootExternal = '"+POut.String(oIDExternal.rootExternal)+"' " +"WHERE OIDExternalNum = "+POut.Long(oIDExternal.OIDExternalNum); Db.NonQ(command); }
///<summary>Updates one OIDExternal in the database. Uses an old object to compare to, and only alters changed fields. This prevents collisions and concurrency problems in heavily used tables. Returns true if an update occurred.</summary> public static bool Update(OIDExternal oIDExternal,OIDExternal oldOIDExternal){ string command=""; if(oIDExternal.IDType != oldOIDExternal.IDType) { if(command!=""){ command+=",";} command+="IDType = '"+POut.String(oIDExternal.IDType.ToString())+"'"; } if(oIDExternal.IDInternal != oldOIDExternal.IDInternal) { if(command!=""){ command+=",";} command+="IDInternal = "+POut.Long(oIDExternal.IDInternal)+""; } if(oIDExternal.IDExternal != oldOIDExternal.IDExternal) { if(command!=""){ command+=",";} command+="IDExternal = '"+POut.String(oIDExternal.IDExternal)+"'"; } if(oIDExternal.rootExternal != oldOIDExternal.rootExternal) { if(command!=""){ command+=",";} command+="rootExternal = '"+POut.String(oIDExternal.rootExternal)+"'"; } if(command==""){ return false; } command="UPDATE oidexternal SET "+command +" WHERE OIDExternalNum = "+POut.Long(oIDExternal.OIDExternalNum); Db.NonQ(command); return true; }
///<summary>Inserts one OIDExternal into the database. Provides option to use the existing priKey.</summary> public static long Insert(OIDExternal oIDExternal,bool useExistingPK){ if(!useExistingPK && PrefC.RandomKeys) { oIDExternal.OIDExternalNum=ReplicationServers.GetKey("oidexternal","OIDExternalNum"); } string command="INSERT INTO oidexternal ("; if(useExistingPK || PrefC.RandomKeys) { command+="OIDExternalNum,"; } command+="IDType,IDInternal,IDExternal,rootExternal) VALUES("; if(useExistingPK || PrefC.RandomKeys) { command+=POut.Long(oIDExternal.OIDExternalNum)+","; } command+= "'"+POut.String(oIDExternal.IDType.ToString())+"'," + POut.Long (oIDExternal.IDInternal)+"," +"'"+POut.String(oIDExternal.IDExternal)+"'," +"'"+POut.String(oIDExternal.rootExternal)+"')"; if(useExistingPK || PrefC.RandomKeys) { Db.NonQ(command); } else { oIDExternal.OIDExternalNum=Db.NonQ(command,true); } return oIDExternal.OIDExternalNum; }
///<summary>This field could be a CWE data type or a XCN data type, depending on if it came from an AIG segment, an AIP segment, or a PV1 segment. The AIG segment would have this as a CWE data type in the format ProvID^LName, FName^^Abbr. For the AIP and PV1 segments, the data type is XCN and the format would be ProvID^LName^FName^^^Abbr. The ProvID is used first. This will contain the root OID and ProvNum extension. If it has the OD root OID for a provider, the number is assumed to be the OD ProvNum and used to find the provider. If the root OID is not the OD root, it is used for on oidexternal table lookup. If the provider is not found from the ID in either the provider table or the oidexternals table, then an attempt is made to find the provider by name and abbreviation. This will return 0 if the field or segName are null or if no provider can be found. A new provider will not be inserted with the information provided if not found by ProvID or name and abbr. This field is repeatable, so we will check all repetitions for valid provider ID's or name/abbr combinations.</summary> public static long ProvParse(FieldHL7 field,SegmentNameHL7 segName,bool isVerbose) { long provNum=0; List<OIDExternal> listOidExt=new List<OIDExternal>(); if(field==null) { return 0; } #region Attempt to Get Provider From ProvIDs //Example of an ID using the hierarchic designation would be 2.16.840.1.113883.3.4337.1486.6566.3.1 //Where 2.16.840.1.113883.3.4337.1486.6566 is the office oidroot, the .3 is to identify this as a provider //2.16.840.1.113883.3.4337.1486.6566.3 would be the office's oidinternal entry for IDType=Provider //The .1 is "."+ProvNum, where the ProvNum in this example is 1 and is considered the extension //We will strip off the ProvNum and if it is connected to the office's oidinternal entry for a provider, we will use it as the OD ProvNum //If it is attached to a different hierarchic root, we will try to find it in the oidexternals table linked to an OD ProvNum string [] provIdHierarch=field.GetComponentVal(0).Split(new string[] {"."},StringSplitOptions.RemoveEmptyEntries); string strProvId=""; string strProvIdRoot=""; if(provIdHierarch.Length>1) {//must have a root and an ID strProvId=provIdHierarch[provIdHierarch.Length-1]; strProvIdRoot=field.GetComponentVal(0).Substring(0,field.GetComponentVal(0).Length-strProvId.Length-1);//-1 for the last "." } if(strProvId!="" && strProvIdRoot!="") { if(strProvIdRoot==OIDInternals.GetForType(IdentifierType.Provider).IDRoot) {//The office's root OID for a provider object, ProvId should be the OD ProvNum try { if(Providers.GetProv(PIn.Long(strProvId))!=null) { provNum=PIn.Long(strProvId);//if component is empty string, provNum will be 0 } } catch(Exception ex) { //PIn.Long failed to convert the component to a long, provNum will remain 0 and we will attempt to get by name and abbr below } } else {//there was a ProvID and a ProvID root, but the root is not the office's root OID for a provider object, check the oidexternals table OIDExternal oidExtProv=OIDExternals.GetByRootAndExtension(strProvIdRoot,strProvId); if(oidExtProv==null) {//add to the list of oid's to add to the oidexternal table if we find a provider OIDExternal oidExtCur=new OIDExternal(); oidExtCur.IDType=IdentifierType.Provider; oidExtCur.rootExternal=strProvIdRoot; oidExtCur.IDExternal=strProvId; //oidExtCur.IDInteral may not have been found yet listOidExt.Add(oidExtCur); } if(oidExtProv!=null && oidExtProv.IDType==IdentifierType.Provider) { //possibly some other validation of name match? provNum=oidExtProv.IDInternal; } } } for(int i=0;i<field.ListRepeatFields.Count;i++) {//could be repetitions of this field with other IDs strProvId=""; strProvIdRoot=""; provIdHierarch=field.ListRepeatFields[i].GetComponentVal(0).Split(new string[] { "." },StringSplitOptions.RemoveEmptyEntries); if(provIdHierarch.Length<2) {//must be a root and an ID continue; } strProvId=provIdHierarch[provIdHierarch.Length-1]; strProvIdRoot=field.ListRepeatFields[i].GetComponentVal(0).Substring(0,field.ListRepeatFields[i].GetComponentVal(0).Length-strProvId.Length-1);//-1 for the last "." if(strProvId=="" || strProvIdRoot=="") { continue; } if(provNum==0 && strProvIdRoot==OIDInternals.GetForType(IdentifierType.Provider).IDRoot) {//The office's root OID for a provider object, ProvId should be the OD ProvNum try { if(Providers.GetProv(PIn.Long(strProvId))!=null) { provNum=PIn.Long(strProvId);//if component is empty string, provNum will be 0 } } catch(Exception ex) { //PIn.Long failed to convert the component to a long, provNum will remain 0 and we will attempt to get by name and abbr below } } else if(strProvIdRoot!=OIDInternals.GetForType(IdentifierType.Provider).IDRoot) {//there was a ProvID and a ProvID root, but the root is not the office's root OID for a provider object, check the oidexternals table OIDExternal oidExtProv=OIDExternals.GetByRootAndExtension(strProvIdRoot,strProvId); if(oidExtProv==null) {//add to the list of oid's to add to the oidexternal table if we find a provider OIDExternal oidExtCur=new OIDExternal(); oidExtCur.IDType=IdentifierType.Provider; oidExtCur.rootExternal=strProvIdRoot; oidExtCur.IDExternal=strProvId; //oidExtCur.IDInteral may not have been found yet listOidExt.Add(oidExtCur); } else { if(provNum==0 && oidExtProv.IDType==IdentifierType.Provider) { //possibly some other validation of name match? provNum=oidExtProv.IDInternal; } } } } if(provNum>0) { string verboseMsg=""; for(int i=0;i<listOidExt.Count;i++) { listOidExt[i].IDInternal=provNum; OIDExternals.Insert(listOidExt[i]); verboseMsg+="\r\nProvNum: "+provNum.ToString()+", External root: "+strProvIdRoot+", External Provider ID: "+strProvId; } if(isVerbose) { EventLog.WriteEntry("OpenDentHL7","Added an external provider ID to the oidexternals table due to an incoming " +segName.ToString()+" segment."+verboseMsg+".",EventLogEntryType.Information); } return provNum; } #endregion Attempt to Get Provider From ProvIDs #region Attempt to Get Provider From Name and Abbr //Couldn't find the provider with the ProvNum provided, we will attempt to find by FName, LName, and Abbr string provLName=""; string provFName=""; string provAbbr=""; if(segName==SegmentNameHL7.AIG) {//AIG is the data type CWE with format ProvNum^LName, FName^^Abbr //GetComponentVal will return an empty string if the index is greater than the number of the components for this field minus 1 string[] components=field.GetComponentVal(1).Split(new char[] {' '},StringSplitOptions.RemoveEmptyEntries); if(components.Length>0) { provLName=components[0].TrimEnd(','); } if(components.Length>1) { provFName=components[1]; } provAbbr=field.GetComponentVal(3); } else if(segName==SegmentNameHL7.AIP || segName==SegmentNameHL7.PV1) {//AIP and PV1 are the data type XCN with the format ProvNum^LName^FName^^^Abbr provLName=field.GetComponentVal(1); provFName=field.GetComponentVal(2); provAbbr=field.GetComponentVal(5); } if(provAbbr!="") { List<Provider> listProvs=Providers.GetProvsByFLName(provLName,provFName); for(int i=0;i<listProvs.Count;i++) { if(listProvs[i].Abbr.ToLower()==provAbbr.ToLower()) { //There should be only one provider with this Abbr, although we only warn them about the duplication and allow them to have more than one with the same Abbr. //With the LName, FName, and Abbr we can be more certain we retrieve the correct provider. provNum=listProvs[i].ProvNum; } } } //provider not found by provID, or first name/abbr combination, try the name/abbr combos in the repetitions. for(int i=0;i<field.ListRepeatFields.Count;i++) {//could be repetitions of this field with other IDs if(provNum>0) { break; } provLName=""; provFName=""; provAbbr=""; if(segName==SegmentNameHL7.AIG) { string[] components=field.ListRepeatFields[i].GetComponentVal(1).Split(new char[] { ' ' },StringSplitOptions.RemoveEmptyEntries); if(components.Length>0) { provLName=components[0].TrimEnd(','); } if(components.Length>1) { provFName=components[1]; } provAbbr=field.ListRepeatFields[i].GetComponentVal(3); } else if(segName==SegmentNameHL7.AIP || segName==SegmentNameHL7.PV1) {//AIP and PV1 are the data type XCN with the format ProvNum^LName^FName^^^Abbr provLName=field.ListRepeatFields[i].GetComponentVal(1); provFName=field.ListRepeatFields[i].GetComponentVal(2); provAbbr=field.ListRepeatFields[i].GetComponentVal(5); } if(provAbbr=="") { continue;//there has to be a LName, FName, and Abbr if we are trying to match without a ProvNum. LName and FName empty string check happens in GetProvsByFLName } List<Provider> listProvs=Providers.GetProvsByFLName(provLName,provFName); for(int p=0;p<listProvs.Count;p++) { if(listProvs[p].Abbr.ToLower()==provAbbr.ToLower()) { //There should be only one provider with this Abbr, although we only warn them about the duplication and allow them to have more than one with the same Abbr. //With the LName, FName, and Abbr we can be more certain we retrieve the correct provider. provNum=listProvs[p].ProvNum; break; } } } if(provNum>0) { string verboseMsg=""; for(int i=0;i<listOidExt.Count;i++) { listOidExt[i].IDInternal=provNum; OIDExternals.Insert(listOidExt[i]); verboseMsg+="\r\nProvNum: "+provNum.ToString()+", External root: "+strProvIdRoot+", External Provider ID: "+strProvId; } if(isVerbose) { EventLog.WriteEntry("OpenDentHL7","Added an external provider ID to the oidexternals table due to an incoming " +segName.ToString()+" segment."+verboseMsg+".",EventLogEntryType.Information); } } #endregion Attempt to Get Provider From Name and Abbr return provNum; }
///<summary>Inserts one OIDExternal into the database. Returns the new priKey. Doesn't use the cache.</summary> public static long InsertNoCache(OIDExternal oIDExternal) { return(InsertNoCache(oIDExternal, false)); }
//public static void ProcessPD1() { // return; //} public static void ProcessPID(Patient pat,HL7DefSegment segDef,SegmentHL7 seg,MessageHL7 msg) { Patient patOld=pat.Copy(); char escapeChar='\\'; if(msg.Delimiters.Length>2) {//it is possible they did not send all 4 of the delimiter chars, in which case we will use the default \ escapeChar=msg.Delimiters[2]; } char subcompChar='&'; if(msg.Delimiters.Length>3) {//it is possible they did not send all 4 of the delimiter chars, in which case we will use the default & subcompChar=msg.Delimiters[3]; } for(int i=0;i<segDef.hl7DefFields.Count;i++) { int itemOrder=segDef.hl7DefFields[i].OrdinalPos; switch(segDef.hl7DefFields[i].FieldName) { case "pat.addressCityStateZip": pat.Address=seg.GetFieldComponent(itemOrder,0); pat.Address2=seg.GetFieldComponent(itemOrder,1); pat.City=seg.GetFieldComponent(itemOrder,2); pat.State=seg.GetFieldComponent(itemOrder,3); pat.Zip=seg.GetFieldComponent(itemOrder,4); string strAddrNote=FieldParser.StringNewLineParse(seg.GetFieldComponent(itemOrder,19),escapeChar); if(strAddrNote!="") { pat.AddrNote=strAddrNote; } continue; case "pat.birthdateTime": pat.Birthdate=FieldParser.DateTimeParse(seg.GetFieldComponent(itemOrder)); continue; case "pat.ChartNumber": pat.ChartNumber=seg.GetFieldComponent(itemOrder); continue; case "pat.FeeSched": if(Programs.IsEnabled(ProgramName.eClinicalWorks) && ProgramProperties.GetPropVal(ProgramName.eClinicalWorks,"FeeSchedulesSetManually")=="1") { //if using eCW and FeeSchedulesSetManually continue;//do not process fee sched field, manually set by user } else { pat.FeeSched=FieldParser.FeeScheduleParse(seg.GetFieldComponent(itemOrder)); } continue; case "pat.Gender": pat.Gender=FieldParser.GenderParse(seg.GetFieldComponent(itemOrder)); continue; case "pat.HmPhone": if(seg.GetFieldComponent(itemOrder,2)=="" && seg.GetField(itemOrder).ListRepeatFields.Count==0) {//must be eCW, process the old way //either no component 2 or left blank, and there are no repetitions, process the old way //the first repetition is considered the primary number, but if the primary number is not sent then it will be blank followed by the list of other numbers pat.HmPhone=FieldParser.PhoneParse(seg.GetFieldComponent(itemOrder)); } else { //XTN data type, repeatable. //Component 2 values: PH-Phone, FX-Fax, MD-Modem, CP-Cell Phone, Internet-Internet Address, X.400-X.400 email address, TDD-Tel Device for the Deaf, TTY-Teletypewriter. //We will support PH for pat.HmPhone, CP for pat.WirelessPhone, and Internet for pat.Email //Component 5 is area code, 6 is number //Component 3 will be Email if the type is Internet //Example: ^PRN^PH^^^503^3635432~^PRN^Internet^[email protected] FieldHL7 phField=seg.GetField(itemOrder); if(phField==null) { continue; } string strPhType=seg.GetFieldComponent(itemOrder,2); string strPh=seg.GetFieldComponent(itemOrder,5)+seg.GetFieldComponent(itemOrder,6); string strEmail=seg.GetFieldComponent(itemOrder,3); for(int p=-1;p<phField.ListRepeatFields.Count;p++) { if(p>=0) { strPhType=phField.ListRepeatFields[p].GetComponentVal(2); strPh=phField.ListRepeatFields[p].GetComponentVal(5)+phField.ListRepeatFields[p].GetComponentVal(6); strEmail=phField.ListRepeatFields[p].GetComponentVal(3); } switch(strPhType) { case "PH": pat.HmPhone=FieldParser.PhoneParse(strPh); continue; case "CP": pat.WirelessPhone=FieldParser.PhoneParse(strPh); continue; case "Internet": pat.Email=strEmail; continue; default: continue; } } } continue; case "pat.nameLFM": pat.LName=seg.GetFieldComponent(itemOrder,0); pat.FName=seg.GetFieldComponent(itemOrder,1); pat.MiddleI=seg.GetFieldComponent(itemOrder,2); pat.Title=seg.GetFieldComponent(itemOrder,4); continue; case "pat.PatNum": //pat.PatNum guaranteed to not be 0, a new patient will be inserted if the field component for PatNum is not an int, is 0, or is blank //if(pat.PatNum!=0 && pat.PatNum!=PIn.Long(seg.GetFieldComponent(intItemOrder))) { //if field component is a valid number and the patient located is not the same as the patient with the PatNum in the segment, then throw the exception, message will fail. long patNumFromPid=0; try { patNumFromPid=PIn.Long(seg.GetFieldComponent(itemOrder)); } catch(Exception ex) { //do nothing, patNumFromPID will remain 0 } if(patNumFromPid!=0 && pat.PatNum!=patNumFromPid) { throw new Exception("Invalid PatNum"); } continue; case "pat.Position": pat.Position=FieldParser.MaritalStatusParse(seg.GetFieldComponent(itemOrder)); continue; case "pat.Race": //PID.10 is a CWE data type, with 9 defined components. The first three are CodeValue^CodeDescription^CodeSystem //This field can repeat, so we will need to add an entry in the patientrace table for each repetition //The race code system is going to be CDCREC and the values match our enum. //The code description is not saved, we only require CodeValue and CodeSystem=CDCREC. Descriptions come from the CDCREC table for display in OD. //Example race component: string strRaceCode=seg.GetFieldComponent(itemOrder,0); string strRaceCodeSystem=seg.GetFieldComponent(itemOrder,2); //if there aren't 3 components to the race field or if the code system is not CDCREC, then parse the old way by matching to string name, i.e. white=PatientRaceOld.White if(strRaceCodeSystem!="CDCREC") {//must be eCW, process the old way PatientRaceOld patientRaceOld=FieldParser.RaceParseOld(strRaceCode); //Converts deprecated PatientRaceOld enum to list of PatRaces, and adds them to the PatientRaces table for the patient. PatientRaces.Reconcile(pat.PatNum,PatientRaces.GetPatRacesFromPatientRaceOld(patientRaceOld)); } else { FieldHL7 fieldRace=seg.GetField(itemOrder); if(fieldRace==null) { continue; } List<PatRace> listPatRaces=new List<PatRace>(); listPatRaces.Add(FieldParser.RaceParse(strRaceCode)); for(int r=0;r<fieldRace.ListRepeatFields.Count;r++) { strRaceCode=fieldRace.ListRepeatFields[r].GetComponentVal(0); strRaceCodeSystem=fieldRace.ListRepeatFields[r].GetComponentVal(2); if(strRaceCodeSystem=="CDCREC") { listPatRaces.Add(FieldParser.RaceParse(strRaceCode)); } } PatientRaces.Reconcile(pat.PatNum,listPatRaces); } continue; case "pat.SSN": pat.SSN=seg.GetFieldComponent(itemOrder); continue; case "pat.WkPhone": if(seg.GetFieldComponent(itemOrder,2)=="PH") { //Component 2 value: PH-Phone //Component 5 is area code, 6 is number //Example: ^WPN^PH^^^503^3635432 pat.WkPhone=FieldParser.PhoneParse(seg.GetFieldComponent(itemOrder,5)+seg.GetFieldComponent(itemOrder,6)); continue; } //either no component 2 or left blank, process the old way pat.WkPhone=FieldParser.PhoneParse(seg.GetFieldComponent(itemOrder));//must be eCW continue; case "patientIds"://Not used by eCW //We will store the ID's from other software in the oidexternal table. These ID's will be sent in outbound msgs and used to locate a patient for subsequent inbound msgs. //Example: |1234^3^M11^&2.16.840.1.113883.3.4337.1486.6566.2&HL7^PI~7684^8^M11^&Other.Software.OID&OIDType^PI| //field component values will be the first repetition, repeats will be in field.ListRepeatFields //1234=PatNum, 3=check digit, M11=using check digit scheme, the assigning authority universal ID is the offices oidinternal.IDRoot for IDType Patient, //HL7=ID type, PI=identifier type code for: Patient internal identifier (a number that is unique to a patient within an assigning authority). FieldHL7 fieldCur=seg.GetField(itemOrder); //get the id with the assigning authority equal to the internal OID root stored in their database for a patient object string strPatOIDRoot=OIDInternals.GetForType(IdentifierType.Patient).IDRoot; if(strPatOIDRoot=="") { //if they have not set their internal OID root, we cannot identify which repetition in this field contains the OD PatNum EventLog.WriteEntry("OpenDentHL7","Could not process the patientIdList field due to the internal OID for the patient object missing.",EventLogEntryType.Information); continue; } string strPatIdCur=""; string strPatIdRootCur=""; string[] arrayPatIdSubComps=fieldCur.GetComponentVal(3).Split(new char[] { subcompChar },StringSplitOptions.None); if(arrayPatIdSubComps.Length>1 //so the next line doesn't throw an exception && arrayPatIdSubComps[1].ToLower()!=strPatOIDRoot.ToLower()//not our PatNum && fieldCur.GetComponentVal(4).ToLower()=="pi")//PI=patient internal identifier; a number that is unique to a patient within an assigning authority { int intCheckDigit=-1; try { intCheckDigit=PIn.Int(fieldCur.GetComponentVal(1)); } catch(Exception ex) { //checkDigit will remain -1 } //if using the M10 or M11 check digit algorithm and it passes the respective test, or not using either algorithm, then use the ID if((fieldCur.GetComponentVal(2).ToLower()=="m10" && intCheckDigit!=-1 && M10CheckDigit(fieldCur.GetComponentVal(0))==intCheckDigit)//using M10 scheme and the check digit is valid and matches calc || (fieldCur.GetComponentVal(2).ToLower()=="m11" && intCheckDigit!=-1 && M11CheckDigit(fieldCur.GetComponentVal(0))==intCheckDigit)//using M11 scheme and the check digit is valid and matches calc || (fieldCur.GetComponentVal(2).ToLower()!="m10" && fieldCur.GetComponentVal(2).ToLower()!="m11"))//not using either check digit scheme { //Either passed the check digit test or not using the M10 or M11 check digit scheme, so trust the ID to be valid strPatIdCur=fieldCur.GetComponentVal(0); strPatIdRootCur=arrayPatIdSubComps[1]; } } OIDExternal oidExtCur=new OIDExternal(); string verboseMsg=""; if(strPatIdCur!="" && strPatIdRootCur!="" && OIDExternals.GetByRootAndExtension(strPatIdRootCur,strPatIdCur)==null) { oidExtCur.IDType=IdentifierType.Patient; oidExtCur.IDInternal=pat.PatNum; oidExtCur.IDExternal=strPatIdCur; oidExtCur.rootExternal=strPatIdRootCur; OIDExternals.Insert(oidExtCur); verboseMsg+="\r\nExternal patient ID: "+oidExtCur.IDExternal+", External root: "+oidExtCur.rootExternal; } for(int r=0;r<fieldCur.ListRepeatFields.Count;r++) { arrayPatIdSubComps=fieldCur.ListRepeatFields[r].GetComponentVal(3).Split(new char[] { subcompChar },StringSplitOptions.None); if(arrayPatIdSubComps.Length<2) {//there must be at least 2 sub-components in the assigning authority component so we can identify whose ID this is continue; } if(arrayPatIdSubComps[1].ToLower()==strPatOIDRoot.ToLower() //if this is the OD patient OID root || fieldCur.ListRepeatFields[r].GetComponentVal(4).ToLower()!="pi")//or not a patient identifier, then skip this repetition { continue; } int intCheckDigit=-1; try { intCheckDigit=PIn.Int(fieldCur.ListRepeatFields[r].GetComponentVal(1)); } catch(Exception ex) { //checkDigit will remain -1 } if(fieldCur.ListRepeatFields[r].GetComponentVal(2).ToLower()=="m10" && (intCheckDigit==-1 || M10CheckDigit(fieldCur.ListRepeatFields[r].GetComponentVal(0))!=intCheckDigit))//using M11 scheme and either an invalid check digit was received or it doesn't match our calc { continue; } if(fieldCur.ListRepeatFields[r].GetComponentVal(2).ToLower()=="m11" && (intCheckDigit==-1 || M11CheckDigit(fieldCur.ListRepeatFields[r].GetComponentVal(0))!=intCheckDigit))//using M11 scheme and either an invalid check digit was received or it doesn't match our calc { continue; } //if not using the M10 or M11 check digit scheme or if the check digit is good, trust the ID in component 0 to be valid and store as IDExternal strPatIdCur=fieldCur.ListRepeatFields[r].GetComponentVal(0); strPatIdRootCur=arrayPatIdSubComps[1]; if(strPatIdCur!="" && strPatIdRootCur!="" && OIDExternals.GetByRootAndExtension(strPatIdRootCur,strPatIdCur)==null) { oidExtCur=new OIDExternal(); oidExtCur.IDType=IdentifierType.Patient; oidExtCur.IDInternal=pat.PatNum; oidExtCur.IDExternal=strPatIdCur; oidExtCur.rootExternal=strPatIdRootCur; OIDExternals.Insert(oidExtCur); verboseMsg+="\r\nExternal patient ID: "+oidExtCur.IDExternal+", External root: "+oidExtCur.rootExternal; } } if(_isVerboseLogging && verboseMsg.Length>0) { EventLog.WriteEntry("OpenDentHL7","Added the following external patient ID(s) to the oidexternals table due to an incoming PID segment for PatNum: " +pat.PatNum+"."+verboseMsg+".",EventLogEntryType.Information); } continue; default: continue; } } Patients.Update(pat,patOld); if(_isVerboseLogging) { EventLog.WriteEntry("OpenDentHL7","Updated patient "+pat.GetNameFLnoPref()+" due to an incoming PID segment.",EventLogEntryType.Information); } return; }
///<summary>This field could be a CWE data type or a XCN data type, depending on if it came from an AIG segment, an AIP segment, or a PV1 segment. The AIG segment would have this as a CWE data type in the format ProvID^LName, FName^^Abbr. For the AIP and PV1 segments, the data type is XCN and the format would be ProvID^LName^FName^^^Abbr. The ProvID is used first. This will contain the root OID and ProvNum extension. If it has the OD root OID for a provider, the number is assumed to be the OD ProvNum and used to find the provider. If the root OID is not the OD root, it is used for on oidexternal table lookup. If the provider is not found from the ID in either the provider table or the oidexternals table, then an attempt is made to find the provider by name and abbreviation. This will return 0 if the field or segName are null or if no provider can be found. A new provider will not be inserted with the information provided if not found by ProvID or name and abbr. This field is repeatable, so we will check all repetitions for valid provider ID's or name/abbr combinations.</summary> public static long ProvParse(FieldHL7 field, SegmentNameHL7 segName, bool isVerbose) { long provNum = 0; List <OIDExternal> listOidExt = new List <OIDExternal>(); if (field == null) { return(0); } #region Attempt to Get Provider From ProvIDs //Example of an ID using the hierarchic designation would be 2.16.840.1.113883.3.4337.1486.6566.3.1 //Where 2.16.840.1.113883.3.4337.1486.6566 is the office oidroot, the .3 is to identify this as a provider //2.16.840.1.113883.3.4337.1486.6566.3 would be the office's oidinternal entry for IDType=Provider //The .1 is "."+ProvNum, where the ProvNum in this example is 1 and is considered the extension //We will strip off the ProvNum and if it is connected to the office's oidinternal entry for a provider, we will use it as the OD ProvNum //If it is attached to a different hierarchic root, we will try to find it in the oidexternals table linked to an OD ProvNum string [] provIdHierarch = field.GetComponentVal(0).Split(new string[] { "." }, StringSplitOptions.RemoveEmptyEntries); string strProvId = ""; string strProvIdRoot = ""; if (provIdHierarch.Length > 1) //must have a root and an ID { strProvId = provIdHierarch[provIdHierarch.Length - 1]; strProvIdRoot = field.GetComponentVal(0).Substring(0, field.GetComponentVal(0).Length - strProvId.Length - 1); //-1 for the last "." } if (strProvId != "" && strProvIdRoot != "") { if (strProvIdRoot == OIDInternals.GetForType(IdentifierType.Provider).IDRoot) //The office's root OID for a provider object, ProvId should be the OD ProvNum { try { if (Providers.GetProv(PIn.Long(strProvId)) != null) { provNum = PIn.Long(strProvId); //if component is empty string, provNum will be 0 } } catch (Exception ex) { ex.DoNothing(); //PIn.Long failed to convert the component to a long, provNum will remain 0 and we will attempt to get by name and abbr below } } else //there was a ProvID and a ProvID root, but the root is not the office's root OID for a provider object, check the oidexternals table { OIDExternal oidExtProv = OIDExternals.GetByRootAndExtension(strProvIdRoot, strProvId); if (oidExtProv == null) //add to the list of oid's to add to the oidexternal table if we find a provider { OIDExternal oidExtCur = new OIDExternal(); oidExtCur.IDType = IdentifierType.Provider; oidExtCur.rootExternal = strProvIdRoot; oidExtCur.IDExternal = strProvId; //oidExtCur.IDInteral may not have been found yet listOidExt.Add(oidExtCur); } if (oidExtProv != null && oidExtProv.IDType == IdentifierType.Provider) { //possibly some other validation of name match? provNum = oidExtProv.IDInternal; } } } for (int i = 0; i < field.ListRepeatFields.Count; i++) //could be repetitions of this field with other IDs { strProvId = ""; strProvIdRoot = ""; provIdHierarch = field.ListRepeatFields[i].GetComponentVal(0).Split(new string[] { "." }, StringSplitOptions.RemoveEmptyEntries); if (provIdHierarch.Length < 2) //must be a root and an ID { continue; } strProvId = provIdHierarch[provIdHierarch.Length - 1]; strProvIdRoot = field.ListRepeatFields[i].GetComponentVal(0).Substring(0, field.ListRepeatFields[i].GetComponentVal(0).Length - strProvId.Length - 1); //-1 for the last "." if (strProvId == "" || strProvIdRoot == "") { continue; } if (provNum == 0 && strProvIdRoot == OIDInternals.GetForType(IdentifierType.Provider).IDRoot) //The office's root OID for a provider object, ProvId should be the OD ProvNum { try { if (Providers.GetProv(PIn.Long(strProvId)) != null) { provNum = PIn.Long(strProvId); //if component is empty string, provNum will be 0 } } catch (Exception ex) { ex.DoNothing(); //PIn.Long failed to convert the component to a long, provNum will remain 0 and we will attempt to get by name and abbr below } } else if (strProvIdRoot != OIDInternals.GetForType(IdentifierType.Provider).IDRoot) //there was a ProvID and a ProvID root, but the root is not the office's root OID for a provider object, check the oidexternals table { OIDExternal oidExtProv = OIDExternals.GetByRootAndExtension(strProvIdRoot, strProvId); if (oidExtProv == null) //add to the list of oid's to add to the oidexternal table if we find a provider { OIDExternal oidExtCur = new OIDExternal(); oidExtCur.IDType = IdentifierType.Provider; oidExtCur.rootExternal = strProvIdRoot; oidExtCur.IDExternal = strProvId; //oidExtCur.IDInteral may not have been found yet listOidExt.Add(oidExtCur); } else { if (provNum == 0 && oidExtProv.IDType == IdentifierType.Provider) { //possibly some other validation of name match? provNum = oidExtProv.IDInternal; } } } } if (provNum > 0) { string verboseMsg = ""; for (int i = 0; i < listOidExt.Count; i++) { listOidExt[i].IDInternal = provNum; OIDExternals.Insert(listOidExt[i]); verboseMsg += "\r\nProvNum: " + provNum.ToString() + ", External root: " + strProvIdRoot + ", External Provider ID: " + strProvId; } if (isVerbose) { EventLog.WriteEntry("OpenDentHL7", "Added an external provider ID to the oidexternals table due to an incoming " + segName.ToString() + " segment." + verboseMsg + ".", EventLogEntryType.Information); } return(provNum); } #endregion Attempt to Get Provider From ProvIDs #region Attempt to Get Provider From Name and Abbr //Couldn't find the provider with the ProvNum provided, we will attempt to find by FName, LName, and Abbr string provLName = ""; string provFName = ""; string provAbbr = ""; if (segName == SegmentNameHL7.AIG) //AIG is the data type CWE with format ProvNum^LName, FName^^Abbr //GetComponentVal will return an empty string if the index is greater than the number of the components for this field minus 1 { string[] components = field.GetComponentVal(1).Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (components.Length > 0) { provLName = components[0].TrimEnd(','); } if (components.Length > 1) { provFName = components[1]; } provAbbr = field.GetComponentVal(3); } else if (segName == SegmentNameHL7.AIP || segName == SegmentNameHL7.PV1) //AIP and PV1 are the data type XCN with the format ProvNum^LName^FName^^^Abbr { provLName = field.GetComponentVal(1); provFName = field.GetComponentVal(2); provAbbr = field.GetComponentVal(5); } if (provAbbr != "") { List <Provider> listProvs = Providers.GetProvsByFLName(provLName, provFName); for (int i = 0; i < listProvs.Count; i++) { if (listProvs[i].Abbr.ToLower() == provAbbr.ToLower()) { //There should be only one provider with this Abbr, although we only warn them about the duplication and allow them to have more than one with the same Abbr. //With the LName, FName, and Abbr we can be more certain we retrieve the correct provider. provNum = listProvs[i].ProvNum; } } } //provider not found by provID, or first name/abbr combination, try the name/abbr combos in the repetitions. for (int i = 0; i < field.ListRepeatFields.Count; i++) //could be repetitions of this field with other IDs { if (provNum > 0) { break; } provLName = ""; provFName = ""; provAbbr = ""; if (segName == SegmentNameHL7.AIG) { string[] components = field.ListRepeatFields[i].GetComponentVal(1).Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (components.Length > 0) { provLName = components[0].TrimEnd(','); } if (components.Length > 1) { provFName = components[1]; } provAbbr = field.ListRepeatFields[i].GetComponentVal(3); } else if (segName == SegmentNameHL7.AIP || segName == SegmentNameHL7.PV1) //AIP and PV1 are the data type XCN with the format ProvNum^LName^FName^^^Abbr { provLName = field.ListRepeatFields[i].GetComponentVal(1); provFName = field.ListRepeatFields[i].GetComponentVal(2); provAbbr = field.ListRepeatFields[i].GetComponentVal(5); } if (provAbbr == "") { continue; //there has to be a LName, FName, and Abbr if we are trying to match without a ProvNum. LName and FName empty string check happens in GetProvsByFLName } List <Provider> listProvs = Providers.GetProvsByFLName(provLName, provFName); for (int p = 0; p < listProvs.Count; p++) { if (listProvs[p].Abbr.ToLower() == provAbbr.ToLower()) { //There should be only one provider with this Abbr, although we only warn them about the duplication and allow them to have more than one with the same Abbr. //With the LName, FName, and Abbr we can be more certain we retrieve the correct provider. provNum = listProvs[p].ProvNum; break; } } } if (provNum > 0) { string verboseMsg = ""; for (int i = 0; i < listOidExt.Count; i++) { listOidExt[i].IDInternal = provNum; OIDExternals.Insert(listOidExt[i]); verboseMsg += "\r\nProvNum: " + provNum.ToString() + ", External root: " + strProvIdRoot + ", External Provider ID: " + strProvId; } if (isVerbose) { EventLog.WriteEntry("OpenDentHL7", "Added an external provider ID to the oidexternals table due to an incoming " + segName.ToString() + " segment." + verboseMsg + ".", EventLogEntryType.Information); } } #endregion Attempt to Get Provider From Name and Abbr return(provNum); }
///<summary>If relationship is self, this method does nothing. A new pat will later change guarantor to be same as patnum. </summary> public static void ProcessGT1(Patient pat,HL7DefSegment segDef,SegmentHL7 seg,MessageHL7 msg) { char escapeChar='\\'; if(msg.Delimiters.Length>2) {//it is possible they did not send all 4 of the delimiter chars, in which case we will use the default \ escapeChar=msg.Delimiters[2]; } char subcompChar='&'; if(msg.Delimiters.Length>3) { subcompChar=msg.Delimiters[3]; } #region Get And Validate Definition Field Ordinals //Find the position of the guarNum, guarChartNum, guarName, and guarBirthdate in this HL7 segment based on the definition of a GT1 int guarPatNumOrdinal=-1; int guarChartNumOrdinal=-1; int guarNameOrdinal=-1; int guarBirthdateOrdinal=-1; int guarIdsOrdinal=-1; string patOidRoot=""; for(int i=0;i<segDef.hl7DefFields.Count;i++) { switch(segDef.hl7DefFields[i].FieldName) { case "guar.PatNum": guarPatNumOrdinal=segDef.hl7DefFields[i].OrdinalPos; continue; case "guar.ChartNumber": guarChartNumOrdinal=segDef.hl7DefFields[i].OrdinalPos; continue; case "guar.nameLFM": guarNameOrdinal=segDef.hl7DefFields[i].OrdinalPos; continue; case "guar.birthdateTime": guarBirthdateOrdinal=segDef.hl7DefFields[i].OrdinalPos; continue; case "guarIdList": //get the id with the assigning authority equal to the internal OID root stored in their database for a patient object patOidRoot=OIDInternals.GetForType(IdentifierType.Patient).IDRoot; if(patOidRoot=="") { //if they have not set their internal OID root, we cannot identify which repetition in this field contains the OD PatNum, guarIdListOrdinal will remain -1 continue; } guarIdsOrdinal=segDef.hl7DefFields[i].OrdinalPos; continue; default://not supported continue; } } //If neither guar.PatNum nor guar.ChartNumber are included in this GT1 definition log a message in the event log and return if(guarPatNumOrdinal==-1 && guarChartNumOrdinal==-1 && guarIdsOrdinal==-1) { _hl7MsgCur.Note="Guarantor not processed. guar.PatNum, guar.ChartNumber, or guarIdList must be included in the GT1 definition. PatNum of patient: "+pat.PatNum.ToString(); HL7Msgs.Update(_hl7MsgCur); EventLog.WriteEntry("OpenDentHL7","Guarantor not processed. guar.PatNum, guar.ChartNumber, or guarIdList must be included in the GT1 definition. " +"PatNum of patient: "+pat.PatNum.ToString(),EventLogEntryType.Information); return; } //If guar.nameLFM is not included in this GT1 definition log a message in the event log and return if(guarNameOrdinal==-1) { _hl7MsgCur.Note="Guarantor not processed due to guar.nameLFM not included in the GT1 definition. Patnum of patient: "+pat.PatNum.ToString(); HL7Msgs.Update(_hl7MsgCur); EventLog.WriteEntry("OpenDentHL7","Guarantor not processed due to guar.nameLFM not included in the GT1 definition. Patnum of patient: " +pat.PatNum.ToString(),EventLogEntryType.Information); return; } //If the first or last name are not included in this GT1 segment, log a message in the event log and return if(seg.GetFieldComponent(guarNameOrdinal,0)=="" || seg.GetFieldComponent(guarNameOrdinal,1)=="") { _hl7MsgCur.Note="Guarantor not processed due to missing first or last name. PatNum of patient: "+pat.PatNum.ToString(); HL7Msgs.Update(_hl7MsgCur); EventLog.WriteEntry("OpenDentHL7","Guarantor not processed due to missing first or last name. PatNum of patient: " +pat.PatNum.ToString(),EventLogEntryType.Information); return; } #endregion Get And Validate Definition Field Ordinals #region Get guar.PatNum, guar.ChartNumber, and guarIdList IDs //Only process GT1 if guar.PatNum, guar.ChartNumber, or guarIdList is included and both guar.LName and guar.FName are included long guarPatNum=0; string guarChartNum=""; long guarPatNumFromIds=0; List<OIDExternal> listOids=new List<OIDExternal>(); if(guarPatNumOrdinal!=-1) { try { guarPatNum=PIn.Long(seg.GetFieldFullText(guarPatNumOrdinal)); } catch(Exception ex) { //do nothing, guarPatNum will remain 0 } } if(guarChartNumOrdinal!=-1) { guarChartNum=seg.GetFieldComponent(guarChartNumOrdinal); } if(guarIdsOrdinal!=-1) {//if OIDInternal root for a patient object is not defined, guarIdListOrdinal will be -1 FieldHL7 fieldGuarIds=seg.GetField(guarIdsOrdinal); //Example field: |1234^3^M11^&2.16.840.1.113883.3.4337.1486.6566.2&HL7^PI~7684^8^M11^&Other.Software.OID&OIDType^PI| //field component values will be the first repetition, repeats will be in field.ListRepeatFields string[] arrayGuarIdSubComps=fieldGuarIds.GetComponentVal(3).Split(new char[] { subcompChar },StringSplitOptions.None); if(arrayGuarIdSubComps.Length>1 //must be at least two subcomponents in the assigning authority component so we can identify whose ID this is && fieldGuarIds.GetComponentVal(4).ToLower()=="pi") //PI=patient internal identifier; a number that is unique to a patient within an assigning authority { int intCheckDigit=-1; try { intCheckDigit=PIn.Int(fieldGuarIds.GetComponentVal(1)); } catch(Exception ex) { //checkDigit will remain -1 } //if using the M10 or M11 check digit algorithm and it passes the respective test, or not using either algorithm //and only if there's at least 2 components in field 3 for the assigning authority so we can identify whose ID we are dealing with, then use the ID if((fieldGuarIds.GetComponentVal(2).ToLower()=="m10" && intCheckDigit!=-1 && M10CheckDigit(fieldGuarIds.GetComponentVal(0))==intCheckDigit)//using M10 scheme and the check digit is valid and matches calc || (fieldGuarIds.GetComponentVal(2).ToLower()=="m11" && intCheckDigit!=-1 && M11CheckDigit(fieldGuarIds.GetComponentVal(0))==intCheckDigit)//using M11 scheme and the check digit is valid and matches calc || (fieldGuarIds.GetComponentVal(2).ToLower()!="m10" && fieldGuarIds.GetComponentVal(2).ToLower()!="m11"))//not using either check digit scheme { if(arrayGuarIdSubComps[1].ToLower()==patOidRoot.ToLower()) { try { guarPatNumFromIds=PIn.Long(fieldGuarIds.GetComponentVal(0)); } catch(Exception ex) { //do nothing, guarPatNumFromList will remain 0 } } else { OIDExternal oidCur=new OIDExternal(); oidCur.IDType=IdentifierType.Patient; oidCur.IDExternal=fieldGuarIds.GetComponentVal(0); oidCur.rootExternal=arrayGuarIdSubComps[1]; listOids.Add(oidCur); } } } for(int r=0;r<fieldGuarIds.ListRepeatFields.Count;r++) { arrayGuarIdSubComps=fieldGuarIds.ListRepeatFields[r].GetComponentVal(3).Split(new char[] { subcompChar },StringSplitOptions.None); if(arrayGuarIdSubComps.Length<2) {//must be at least two sub-components to the assigning authority component so we can identify whose ID this is continue; } if(fieldGuarIds.ListRepeatFields[r].GetComponentVal(4).ToLower()!="pi") { continue; } int intCheckDigit=-1; try { intCheckDigit=PIn.Int(fieldGuarIds.ListRepeatFields[r].GetComponentVal(1)); } catch(Exception ex) { //checkDigit will remain -1 } if(fieldGuarIds.ListRepeatFields[r].GetComponentVal(2).ToLower()=="m10" && (intCheckDigit==-1 || M10CheckDigit(fieldGuarIds.ListRepeatFields[r].GetComponentVal(0))!=intCheckDigit))//using M10 scheme and either invalid check digit or doesn't match calc { continue; } if(fieldGuarIds.ListRepeatFields[r].GetComponentVal(2).ToLower()=="m11" && (intCheckDigit==-1 || M11CheckDigit(fieldGuarIds.ListRepeatFields[r].GetComponentVal(0))!=intCheckDigit))//using M11 scheme and either invalid check digit or doesn't match calc { continue; } //if not using the M10 or M11 check digit scheme or if the check digit is good, trust the ID in component 0 to be valid and attempt to use if(arrayGuarIdSubComps[1].ToLower()==patOidRoot.ToLower()) { try { guarPatNumFromIds=PIn.Long(fieldGuarIds.ListRepeatFields[r].GetComponentVal(0)); } catch(Exception ex) { //do nothing, guarPatNumFromList will remain 0 } } else { OIDExternal oidCur=new OIDExternal(); oidCur.IDType=IdentifierType.Patient; oidCur.IDExternal=fieldGuarIds.ListRepeatFields[r].GetComponentVal(0); oidCur.rootExternal=arrayGuarIdSubComps[1]; listOids.Add(oidCur); } } } if(guarPatNum==0 && guarChartNum=="" && guarPatNumFromIds==0 && listOids.Count==0) {//because we have an example where they sent us this (position 2 (guarPatNumOrder or guarChartNumOrder for eCW) is empty): GT1|1||^^||^^^^|||||||| _hl7MsgCur.Note="Guarantor not processed due to missing guar.PatNum, guar.ChartNumber, and guarIdList. One of those numbers must be included. " +"PatNum of patient: "+pat.PatNum.ToString(); HL7Msgs.Update(_hl7MsgCur); EventLog.WriteEntry("OpenDentHL7","Guarantor not processed due to missing guar.PatNum, guar.ChartNumber, and guarIdList. " +"One of those numbers must be included. PatNum of patient: "+pat.PatNum.ToString(),EventLogEntryType.Information); return; } #endregion Get guar.PatNum, guar.ChartNumber, and guarIdList IDs if(guarPatNum==pat.PatNum || (guarChartNum!="" && guarChartNum==pat.ChartNumber) || guarPatNumFromIds==pat.PatNum) {//if relationship is self return; } #region Get Patient from Ids //Guar must be someone else Patient guar=null; Patient guarOld=null; //Find guarantor by guar.PatNum if defined and in this segment if(guarPatNum!=0) { guar=Patients.GetPat(guarPatNum); } else if(guarPatNumFromIds!=0) { guar=Patients.GetPat(guarPatNumFromIds); } else if(guarChartNum!="") {//guarPatNum and guarPatNumFromList was 0 so try to get guar by guar.ChartNumber or name and birthdate //try to find guarantor using chartNumber guar=Patients.GetPatByChartNumber(guarChartNum); if(guar==null) { //try to find the guarantor by using name and birthdate string strGuarLName=seg.GetFieldComponent(guarNameOrdinal,0); string strGuarFName=seg.GetFieldComponent(guarNameOrdinal,1); DateTime guarBirthdate=FieldParser.DateTimeParse(seg.GetFieldFullText(guarBirthdateOrdinal)); long guarNamePatNum=Patients.GetPatNumByNameAndBirthday(strGuarLName,strGuarFName,guarBirthdate); if(guarNamePatNum==0) {//guarantor does not exist in OD //so guar will still be null, triggering creation of new guarantor further down. } else { guar=Patients.GetPat(guarNamePatNum); guarOld=guar.Copy(); guar.ChartNumber=guarChartNum;//from now on, we will be able to find guar by chartNumber Patients.Update(guar,guarOld); } } } long guarExtPatNum=0; if(guar==null) {//As a last resort, use the external IDs for this patient in the oidexternal to find the guar //Use the external OIDs stored in the oidexternal table to find the patient //Only trust the external IDs if all OIDs refer to the same patient for(int i=0;i<listOids.Count;i++) { OIDExternal oidExternalCur=OIDExternals.GetByRootAndExtension(listOids[i].rootExternal,listOids[i].IDExternal); if(oidExternalCur==null || oidExternalCur.IDInternal==0 || oidExternalCur.IDType!=IdentifierType.Patient) {//must have an IDType of Patient continue; } if(guarExtPatNum==0) { guarExtPatNum=oidExternalCur.IDInternal; } else if(guarExtPatNum!=oidExternalCur.IDInternal) {//the current ID refers to a different patient than a previously found ID, don't trust the external IDs guarExtPatNum=0; break; } } if(guarExtPatNum>0) {//will be 0 if not in the OIDExternal table or no external IDs supplied or more than one supplied and they point to different pats (ambiguous) guar=Patients.GetPat(guarExtPatNum); } } //At this point we have a guarantor located in OD or guar=null so guar is new patient bool isNewGuar=(guar==null); if(isNewGuar) {//then we need to add guarantor to db guar=new Patient(); if(guarChartNum!="") { guar.ChartNumber=guarChartNum; } if(guarPatNum!=0) { guar.PatNum=guarPatNum; guar.Guarantor=guarPatNum; } else if(guarPatNumFromIds!=0) { guar.PatNum=guarPatNumFromIds; guar.Guarantor=guarPatNumFromIds; } else if(guarExtPatNum!=0) { guar.PatNum=guarExtPatNum; guar.Guarantor=guarExtPatNum; } guar.PriProv=PrefC.GetLong(PrefName.PracticeDefaultProv); guar.BillingType=PrefC.GetLong(PrefName.PracticeDefaultBillType); } else { guarOld=guar.Copy(); } #endregion Get Patient from Ids #region Update Guarantor Data //Now that we have our guarantor, process the GT1 segment for(int i=0;i<segDef.hl7DefFields.Count;i++) { int itemOrder=segDef.hl7DefFields[i].OrdinalPos; switch(segDef.hl7DefFields[i].FieldName) { case "guar.addressCityStateZip": guar.Address=seg.GetFieldComponent(itemOrder,0); guar.Address2=seg.GetFieldComponent(itemOrder,1); guar.City=seg.GetFieldComponent(itemOrder,2); guar.State=seg.GetFieldComponent(itemOrder,3); guar.Zip=seg.GetFieldComponent(itemOrder,4); guar.AddrNote=FieldParser.StringNewLineParse(seg.GetFieldComponent(itemOrder,19),escapeChar); continue; case "guar.birthdateTime": guar.Birthdate=FieldParser.DateTimeParse(seg.GetFieldComponent(itemOrder)); continue; case "guar.ChartNumber": guar.ChartNumber=seg.GetFieldComponent(itemOrder); continue; case "guar.Gender": guar.Gender=FieldParser.GenderParse(seg.GetFieldComponent(itemOrder)); continue; case "guar.HmPhone": if(seg.GetFieldComponent(itemOrder,2)=="" && seg.GetField(itemOrder).ListRepeatFields.Count==0) { //either no component 2 or left blank, and there are no repetitions, process the old way //the first repetition is considered the primary number, but if the primary number is not sent then it will be blank followed by the list of other numbers guar.HmPhone=FieldParser.PhoneParse(seg.GetFieldComponent(itemOrder)); } else { //XTN data type, repeatable. //Component 2 values: PH-Phone, FX-Fax, MD-Modem, CP-Cell Phone, Internet-Internet Address, X.400-X.400 email address, TDD-Tel Device for the Deaf, TTY-Teletypewriter. //We will support PH for pat.HmPhone, CP for pat.WirelessPhone, and Internet for pat.Email //Component 5 is area code, 6 is number //Component 3 will be Email if the type is Internet //Example: ^PRN^PH^^^503^3635432~^PRN^Internet^[email protected] FieldHL7 fieldPh=seg.GetField(itemOrder); if(fieldPh==null) { continue; } string strPhType=seg.GetFieldComponent(itemOrder,2); string strPh=seg.GetFieldComponent(itemOrder,5)+seg.GetFieldComponent(itemOrder,6); string strEmail=seg.GetFieldComponent(itemOrder,3); for(int p=-1;p<fieldPh.ListRepeatFields.Count;p++) { if(p>=0) { strPhType=fieldPh.ListRepeatFields[p].GetComponentVal(2); strPh=fieldPh.ListRepeatFields[p].GetComponentVal(5)+fieldPh.ListRepeatFields[p].GetComponentVal(6); strEmail=fieldPh.ListRepeatFields[p].GetComponentVal(3); } switch(strPhType) { case "PH": guar.HmPhone=FieldParser.PhoneParse(strPh); continue; case "CP": guar.WirelessPhone=FieldParser.PhoneParse(strPh); continue; case "Internet": guar.Email=strEmail; continue; default: continue; } } } continue; case "guar.nameLFM": guar.LName=seg.GetFieldComponent(itemOrder,0); guar.FName=seg.GetFieldComponent(itemOrder,1); guar.MiddleI=seg.GetFieldComponent(itemOrder,2); guar.Title=seg.GetFieldComponent(itemOrder,4); continue; //case "guar.PatNum": Maybe do nothing?? case "guar.SSN": guar.SSN=seg.GetFieldComponent(itemOrder); continue; case "guar.WkPhone": if(seg.GetFieldComponent(itemOrder,2)=="PH") { //Component 2 value: PH-Phone //Component 5 is area code, 6 is number //Example: ^WPN^PH^^^503^3635432 guar.WkPhone=FieldParser.PhoneParse(seg.GetFieldComponent(itemOrder,5)+seg.GetFieldComponent(itemOrder,6)); continue; } //either no component 2 or left blank, process the old way guar.WkPhone=FieldParser.PhoneParse(seg.GetFieldComponent(itemOrder)); continue; default: continue; } } if(isNewGuar) { if(guar.PatNum==0) { guarOld=guar.Copy(); guar.PatNum=Patients.Insert(guar,false); guar.Guarantor=guar.PatNum; Patients.Update(guar,guarOld); } else { guar.PatNum=Patients.Insert(guar,true); } if(_isVerboseLogging) { EventLog.WriteEntry("OpenDentHL7","Inserted patient "+guar.GetNameFLnoPref()+" when processing a GT1 segment.",EventLogEntryType.Information); } } else { Patients.Update(guar,guarOld); if(_isVerboseLogging) { EventLog.WriteEntry("OpenDentHL7","Updated patient "+guar.GetNameFLnoPref()+" when processing a GT1 segment.",EventLogEntryType.Information); } } #endregion Update Guarantor Data pat.Guarantor=guar.PatNum; //store external IDs in the oidexternal table if they do not already exist in the table string strVerboseMsg=""; for(int i=0;i<listOids.Count;i++) { if(listOids[i].IDExternal==""//not a good external ID OR || listOids[i].rootExternal==""//not a good external root OR || OIDExternals.GetByRootAndExtension(listOids[i].rootExternal,listOids[i].IDExternal)!=null)//already exists in the oidexternal table { continue; } listOids[i].IDInternal=guar.PatNum; OIDExternals.Insert(listOids[i]); strVerboseMsg+="\r\nExternal patient ID: "+listOids[i].IDExternal+", External root: "+listOids[i].rootExternal; } if(_isVerboseLogging && strVerboseMsg.Length>0) { EventLog.WriteEntry("OpenDentHL7","Added the following external patient ID(s) to the oidexternals table due to an incoming GT1 segment for PatNum: "+guar.PatNum+"." +strVerboseMsg+".",EventLogEntryType.Information); } return; }
///<summary>Appointment request segment. Included in inbound SRM, Schedule Request Messages. When OD is the filler application, this will identify the appointment that the placer or auxiliary aplication is trying to update. We only support event S03 - Appt Modification requests or event S04 - Appt Cancellation requests for now.</summary> public static void ProcessARQ(Patient pat,Appointment apt,HL7DefSegment segDef,SegmentHL7 seg,MessageHL7 msg) { long aptNum=0; string externAptId=""; string externRoot=""; for(int i=0;i<segDef.hl7DefFields.Count;i++) { int intItemOrder=segDef.hl7DefFields[i].OrdinalPos; switch(segDef.hl7DefFields[i].FieldName) { case "apt.externalAptID": externAptId=seg.GetFieldComponent(intItemOrder,0); externRoot=seg.GetFieldComponent(intItemOrder,2); continue; case "apt.AptNum": try { aptNum=PIn.Long(seg.GetFieldComponent(intItemOrder,0)); } catch(Exception ex) { //do nothing, aptNum will remain 0 } if(apt!=null && apt.AptNum!=aptNum) { //an appointment was located from the inbound message, but the AptNum on the appointment is not the same as the AptNum in this ARQ segment (should never happen) throw new Exception("Invalid appointment number."); } continue; default: continue; } } if(externAptId!="" && externRoot!="" && OIDExternals.GetByRootAndExtension(externRoot,externAptId)==null) { OIDExternal oidExtCur=new OIDExternal(); oidExtCur.IDType=IdentifierType.Appointment; oidExtCur.IDInternal=apt.AptNum; oidExtCur.IDExternal=externAptId; oidExtCur.rootExternal=externRoot; OIDExternals.Insert(oidExtCur); if(_isVerboseLogging) { EventLog.WriteEntry("OpenDentHL7","Added an external appointment ID to the oidexternals table due to an incoming ARQ segment.\r\n" +"AptNum: "+apt.AptNum.ToString()+", External AptID: "+externAptId+", External root: "+externRoot+".",EventLogEntryType.Information); } } _hl7MsgCur.AptNum=apt.AptNum; HL7Msgs.Update(_hl7MsgCur); if(_isVerboseLogging) { EventLog.WriteEntry("OpenDentHL7","Updated hl7msg to include AptNum for "+pat.GetNameFLnoPref()+" due to an incoming SCH segment.",EventLogEntryType.Information); } _aptProcessed=apt; return; }
private static Appointment _aptProcessed;//return value for ProcessSeg if an appointment was processed //Open \\SERVERFILES\storage\OPEN DENTAL\Programmers Documents\Standards (X12, ADA, etc)\HL7\Version2.6\V26_CH02_Control_M4_JAN2007.doc //At the top of page 33, there are rules for the recipient. //Basically, they state that parsing should not fail just because there are extra unexpected items. //And parsing should also not fail if expected items are missing. public static void Process(MessageHL7 msg,bool isVerboseLogging) { _hl7MsgCur=new HL7Msg(); _hl7MsgCur.HL7Status=HL7MessageStatus.InFailed;//it will be marked InProcessed once data is inserted. _hl7MsgCur.MsgText=msg.ToString(); _hl7MsgCur.PatNum=0; _hl7MsgCur.AptNum=0; List<HL7Msg> listHL7Existing=HL7Msgs.GetOneExisting(_hl7MsgCur); if(listHL7Existing.Count>0) {//This message is already in the db _hl7MsgCur.HL7MsgNum=listHL7Existing[0].HL7MsgNum; HL7Msgs.UpdateDateTStamp(_hl7MsgCur); msg.ControlId=HL7Msgs.GetControlId(_hl7MsgCur); return; } else { //Insert as InFailed until processing is complete. Update once complete, PatNum will have correct value, AptNum will have correct value if SIU message or 0 if ADT, and status changed to InProcessed HL7Msgs.Insert(_hl7MsgCur); } _isVerboseLogging=isVerboseLogging; _isNewPat=false; HL7Def def=HL7Defs.GetOneDeepEnabled(); if(def==null) { _hl7MsgCur.Note="Could not process HL7 message. No HL7 definition is enabled."; HL7Msgs.Update(_hl7MsgCur); throw new Exception("Could not process HL7 message. No HL7 definition is enabled."); } if(def.InternalType==HL7InternalType.eCWFull || def.InternalType==HL7InternalType.eCWTight || def.InternalType==HL7InternalType.eCWStandalone) { _isEcwHL7Def=true; } HL7DefMessage hl7defmsg=null; for(int i=0;i<def.hl7DefMessages.Count;i++) { if(def.hl7DefMessages[i].MessageType==msg.MsgType && def.hl7DefMessages[i].InOrOut==InOutHL7.Incoming) {//Ignoring event type for now, we will treat all ADT's and SIU's the same hl7defmsg=def.hl7DefMessages[i]; break; } } if(hl7defmsg==null) {//No message definition matches this message's MessageType and is Incoming _hl7MsgCur.Note="Could not process HL7 message. No definition for this type of message in the enabled HL7Def."; HL7Msgs.Update(_hl7MsgCur); throw new Exception("Could not process HL7 message. No definition for this type of message in the enabled HL7Def."); } string chartNum=null; long patNum=0; long patNumFromIds=0; //if we cannot locate the patient by the supplied patnum, either from the PatNum field or the list of IDs in PID.3, then we will use the external IDs to attempt to locate the pat //we will only trust these external IDs to return the correct patient if they all refer to a single patient //we may want to do some other checking to ensure that the patient referred to is the right patient? List<OIDExternal> listOids=new List<OIDExternal>(); DateTime birthdate=DateTime.MinValue; string patLName=null; string patFName=null; #region GetPatientIDs #region PID segmentsLoop //Identify the location of the PID segment based on the message definition //Get patient in question, incoming messages must have a PID segment so use that to find the pat in question int pidOrder=-1; int pidDefOrder=-1; //get the def's PID segment order and the defined intItemOrder of the PID segment in the message for(int i=0;i<hl7defmsg.hl7DefSegments.Count;i++) { if(hl7defmsg.hl7DefSegments[i].SegmentName!=SegmentNameHL7.PID) { continue; } pidDefOrder=i; pidOrder=hl7defmsg.hl7DefSegments[i].ItemOrder; //we found the PID segment in the def, make sure it exists in the msg if(msg.Segments.Count<=pidOrder //If the number of segments in the message is less than the item order of the PID segment || msg.Segments[pidOrder].GetField(0).ToString()!="PID") //Or if the segment we expect to be the PID segment is not the PID segment { _hl7MsgCur.Note="Could not process the HL7 message due to missing PID segment."; HL7Msgs.Update(_hl7MsgCur); throw new Exception("Could not process HL7 message. Could not process the HL7 message due to missing PID segment."); } //if we get here, we've located the PID segment location within the message based on the def and it exists in the message break; } #endregion PID segmentsLoop #region PID fieldsLoop //Using the identified location of the PID segment, loop through the fields and find each identifier defined for(int f=0;f<hl7defmsg.hl7DefSegments[pidDefOrder].hl7DefFields.Count;f++) {//Go through fields of PID segment and get patnum, chartnum, patient name, and/or birthdate to locate patient if(hl7defmsg.hl7DefSegments[pidDefOrder].hl7DefFields[f].FieldName=="pat.ChartNumber") { int chartNumOrdinal=hl7defmsg.hl7DefSegments[pidDefOrder].hl7DefFields[f].OrdinalPos; chartNum=msg.Segments[pidOrder].GetField(chartNumOrdinal).ToString(); } else if(hl7defmsg.hl7DefSegments[pidDefOrder].hl7DefFields[f].FieldName=="pat.PatNum") { int patNumOrdinal=hl7defmsg.hl7DefSegments[pidDefOrder].hl7DefFields[f].OrdinalPos; try { patNum=PIn.Long(msg.Segments[pidOrder].GetField(patNumOrdinal).ToString()); } catch(Exception ex) { //do nothing, patNum will remain 0 } } else if(hl7defmsg.hl7DefSegments[pidDefOrder].hl7DefFields[f].FieldName=="pat.birthdateTime") { int patBdayOrdinal=hl7defmsg.hl7DefSegments[pidDefOrder].hl7DefFields[f].OrdinalPos; birthdate=FieldParser.DateTimeParse(msg.Segments[pidOrder].GetField(patBdayOrdinal).ToString()); } else if(hl7defmsg.hl7DefSegments[pidDefOrder].hl7DefFields[f].FieldName=="pat.nameLFM") { int patNameOrdinal=hl7defmsg.hl7DefSegments[pidDefOrder].hl7DefFields[f].OrdinalPos; patLName=msg.Segments[pidOrder].GetFieldComponent(patNameOrdinal,0); patFName=msg.Segments[pidOrder].GetFieldComponent(patNameOrdinal,1); } #region patientIdList else if(hl7defmsg.hl7DefSegments[pidDefOrder].hl7DefFields[f].FieldName=="patientIds") { //get the id with the assigning authority equal to the internal OID root stored in their database for a patient object string patOIDRoot=OIDInternals.GetForType(IdentifierType.Patient).IDRoot; if(patOIDRoot=="") { //if they have not set their internal OID root, we cannot identify which repetition in this field contains the OD PatNum continue; } int patIdListOrdinal=hl7defmsg.hl7DefSegments[pidDefOrder].hl7DefFields[f].OrdinalPos; FieldHL7 fieldPatIds=msg.Segments[pidOrder].GetField(patIdListOrdinal); //Example: |1234^3^M11^&2.16.840.1.113883.3.4337.1486.6566.2&HL7^PI~7684^8^M11^&Other.Software.OID&OIDType^MR| //field component values will be the first repetition, repeats will be in field.ListRepeatFields //field 4 is an HD data type, which is composed of 3 subcomponents separated by the "&". //subcomponent 1 is the universal ID for the assigning authority //subcomponent 2 is the universal ID type for the assigning authority char subcompSeparator='&'; if(msg.Delimiters.Length>3) { subcompSeparator=msg.Delimiters[3]; } string[] arrayPatIdSubComps=fieldPatIds.GetComponentVal(3).Split(new char[] { subcompSeparator },StringSplitOptions.None); if(arrayPatIdSubComps.Length>1//there must be at least 2 sub-components in the assigning authority component so we can identify whose ID this is && fieldPatIds.GetComponentVal(4).ToLower()=="pi")//PI=patient internal identifier; a number that is unique to a patient within an assigning authority { int checkDigit=-1; try { checkDigit=PIn.Int(fieldPatIds.GetComponentVal(1)); } catch(Exception ex) { //checkDigit will remain -1 } //if using the M10 or M11 check digit algorithm and it passes the respective test, or not using either algorithm, then use the ID if((fieldPatIds.GetComponentVal(2).ToLower()=="m10" && checkDigit!=-1 && M10CheckDigit(fieldPatIds.GetComponentVal(0))==checkDigit)//using M10 scheme and the check digit is valid and matches calc || (fieldPatIds.GetComponentVal(2).ToLower()=="m11" && checkDigit!=-1 && M11CheckDigit(fieldPatIds.GetComponentVal(0))==checkDigit)//using M11 scheme and the check digit is valid and matches calc || (fieldPatIds.GetComponentVal(2).ToLower()!="m10" && fieldPatIds.GetComponentVal(2).ToLower()!="m11"))//not using either check digit scheme { if(arrayPatIdSubComps[1].ToLower()==patOIDRoot.ToLower()) { try { patNumFromIds=PIn.Long(fieldPatIds.GetComponentVal(0)); } catch(Exception ex) { //do nothing, patNumFromList will remain 0 } } else { OIDExternal oidCur=new OIDExternal(); oidCur.IDType=IdentifierType.Patient; oidCur.IDExternal=fieldPatIds.GetComponentVal(0); oidCur.rootExternal=arrayPatIdSubComps[1]; listOids.Add(oidCur); } } } //patNumFromList will be 0 if the first repetition is not the OD patient id or if the check digit is incorrect based on the specified algorithm if(patNumFromIds!=0) { continue; } for(int r=0;r<fieldPatIds.ListRepeatFields.Count;r++) { arrayPatIdSubComps=fieldPatIds.ListRepeatFields[r].GetComponentVal(3).ToLower().Split(new char[] { subcompSeparator },StringSplitOptions.None); if(arrayPatIdSubComps.Length<2) {//there must be at least 2 sub-components in the assigning authority component so we can identify whose ID this is continue; } if(fieldPatIds.ListRepeatFields[r].GetComponentVal(4).ToLower()!="pi") { continue; } int checkDigit=-1; try { checkDigit=PIn.Int(fieldPatIds.ListRepeatFields[r].GetComponentVal(1)); } catch(Exception ex) { //checkDigit will remain -1 } if(fieldPatIds.ListRepeatFields[r].GetComponentVal(2).ToLower()=="m10" && (checkDigit==-1 || M10CheckDigit(fieldPatIds.ListRepeatFields[r].GetComponentVal(0))!=checkDigit))//using M10 scheme and either invalid check digit or doesn't match calc { continue; } if(fieldPatIds.ListRepeatFields[r].GetComponentVal(2).ToLower()=="m11" && (checkDigit==-1 || M11CheckDigit(fieldPatIds.ListRepeatFields[r].GetComponentVal(0))!=checkDigit))//using M11 scheme and either invalid check digit or doesn't match calc { continue; } //if not using the M10 or M11 check digit scheme or if the check digit is good, trust the ID in component 0 to be valid and attempt to use if(arrayPatIdSubComps[1]==patOIDRoot.ToLower()) { if(patNumFromIds==0) { try { patNumFromIds=PIn.Long(fieldPatIds.ListRepeatFields[r].GetComponentVal(0)); } catch(Exception ex) { //do nothing, patNumFromList will remain 0 } } } else { OIDExternal oidCur=new OIDExternal(); oidCur.IDType=IdentifierType.Patient; oidCur.IDExternal=fieldPatIds.ListRepeatFields[r].GetComponentVal(0); oidCur.rootExternal=arrayPatIdSubComps[1]; listOids.Add(oidCur); } } } #endregion patientIdList } if(_isEcwHL7Def && (patLName=="" || patFName=="")) { EventLog.WriteEntry("OpenDentHL7","Message not processed due to missing first or last name. PatNum:"+patNum.ToString() ,EventLogEntryType.Information); _hl7MsgCur.Note="Message not processed due to missing first or last name. PatNum:"+patNum.ToString(); HL7Msgs.Update(_hl7MsgCur); return; } #endregion PID fieldsLoop #endregion GetPatientIDs //We now have patnum, chartnum, patname, and/or birthdate so locate pat Patient pat=null; Patient patOld=null; //pat will be null if patNum==0 pat=Patients.GetPat(patNum); if(pat==null && patNumFromIds>0) { pat=Patients.GetPat(patNumFromIds); } //If ChartNumber is a field in their defined PID segment, and they are not using eCWTight or Full internal type //Use the ChartNumber followed by Name and Birthdate to try to locate the patient for this message if chartNum is not null if(def.InternalType!=HL7InternalType.eCWFull && def.InternalType!=HL7InternalType.eCWTight && pat==null && chartNum!=null) { pat=Patients.GetPatByChartNumber(chartNum); //If not using eCWTight or Full integration, if pat is still null we need to try to locate patient by name and birthdate if(pat==null) { long patNumByName=Patients.GetPatNumByNameAndBirthday(patLName,patFName,birthdate); if(patNumByName>0) { pat=Patients.GetPat(patNumByName); } } if(pat!=null) { patOld=pat.Copy(); pat.ChartNumber=chartNum;//from now on, we will be able to find pat by chartNumber Patients.Update(pat,patOld); if(_isVerboseLogging) { EventLog.WriteEntry("OpenDentHL7","Updated patient "+pat.GetNameFLnoPref()+" to include ChartNumber.",EventLogEntryType.Information); } } } //Use the external OIDs stored in the oidexternal table to find the patient //Only trust the external IDs if all OIDs refer to the same patient long patNumFromExtIds=0; if(pat==null) { for(int i=0;i<listOids.Count;i++) { OIDExternal oidExternalCur=OIDExternals.GetByRootAndExtension(listOids[i].rootExternal,listOids[i].IDExternal); if(oidExternalCur==null || oidExternalCur.IDInternal==0 || oidExternalCur.IDType!=IdentifierType.Patient) {//must have an IDType of patient continue; } if(patNumFromExtIds==0) { patNumFromExtIds=oidExternalCur.IDInternal; } else if(patNumFromExtIds!=oidExternalCur.IDInternal) {//the current ID refers to a different patient than a previously found ID, don't trust the external IDs patNumFromExtIds=0; break; } } if(patNumFromExtIds>0) {//will be 0 if not in the OIDExternal table or no external IDs supplied or more than one supplied and they point to different pats (ambiguous) pat=Patients.GetPat(patNumFromExtIds); } } _isNewPat=pat==null; if(!_isEcwHL7Def && msg.MsgType==MessageTypeHL7.SRM && _isNewPat) {//SRM messages must refer to existing appointments, so there must be an existing patient as well MessageHL7 hl7SRR=MessageConstructor.GenerateSRR(pat,null,msg.EventType,msg.ControlId,false,msg.AckEvent);//use false to indicate AE - Application Error in SRR.MSA segment HL7Msg hl7Msg=new HL7Msg(); hl7Msg.HL7Status=HL7MessageStatus.OutPending;//it will be marked outSent by the HL7 service. hl7Msg.MsgText=hl7SRR.ToString(); hl7Msg.Note="Could not process HL7 SRM message due to an invalid or missing patient ID in the PID segment."; HL7Msgs.Insert(hl7Msg); throw new Exception("Could not process HL7 SRM message due to an invalid or missing patient ID in the PID segment."); } if(_isNewPat) { pat=new Patient(); if(chartNum!=null) { pat.ChartNumber=chartNum; } if(patNum!=0) { pat.PatNum=patNum; pat.Guarantor=patNum; } else if(patNumFromIds!=0) { pat.PatNum=patNumFromIds; pat.Guarantor=patNumFromIds; } else if(patNumFromExtIds!=0) { pat.PatNum=patNumFromExtIds; pat.Guarantor=patNumFromExtIds; } pat.PriProv=PrefC.GetLong(PrefName.PracticeDefaultProv); pat.BillingType=PrefC.GetLong(PrefName.PracticeDefaultBillType); } else { patOld=pat.Copy(); } long aptNum=0; //If this is a message that contains an ARQ or SCH segment, loop through the fields to find the AptNum. Pass it to other segment parsing methods that require it. //If this is an SRM message, and an AptNum is not included or no appointment with this AptNum is in the OD db, do not process the message. //We only accept SRM messages for appointments that already exist in the OD db. for(int i=0;i<hl7defmsg.hl7DefSegments.Count;i++) { if(hl7defmsg.hl7DefSegments[i].SegmentName!=SegmentNameHL7.SCH//SIU messages will have the SCH segment, used for eCW or other interfaces where OD is an auxiliary application && hl7defmsg.hl7DefSegments[i].SegmentName!=SegmentNameHL7.ARQ)//SRM messages will have the ARQ segment, used for interfaces where OD is the filler application { continue; } //we found the SCH or ARQ segment int segOrder=hl7defmsg.hl7DefSegments[i].ItemOrder; for(int j=0;j<hl7defmsg.hl7DefSegments[i].hl7DefFields.Count;j++) {//Go through fields of SCH or ARQ segment and get AptNum if(hl7defmsg.hl7DefSegments[i].hl7DefFields[j].FieldName=="apt.AptNum") { int aptNumOrdinal=hl7defmsg.hl7DefSegments[i].hl7DefFields[j].OrdinalPos; try { aptNum=PIn.Long(msg.Segments[segOrder].GetFieldComponent(aptNumOrdinal,0).ToString()); } catch(Exception ex) {//PIn.Long will throw an exception if a value is not able to be parsed into a long //do nothing, aptNum will remain 0 } break; } } if(aptNum>0) { break; } } Appointment aptCur=Appointments.GetOneApt(aptNum);//if aptNum=0, aptCur will be null //SRM messages are only for interfaces where OD is considered the 'filler' application //Not valid for eCW where OD is considered an 'auxiliary' application and we receive SIU messages instead. if(!_isEcwHL7Def && msg.MsgType==MessageTypeHL7.SRM) { MessageHL7 hl7Srr=null; HL7Msg hl7Msg=null; if(aptCur==null) { hl7Srr=MessageConstructor.GenerateSRR(pat,aptCur,msg.EventType,msg.ControlId,false,msg.AckEvent);//use false to indicate AE - Application Error in SRR.MSA segment hl7Msg=new HL7Msg(); hl7Msg.HL7Status=HL7MessageStatus.OutPending;//it will be marked outSent by the HL7 service. hl7Msg.MsgText=hl7Srr.ToString(); hl7Msg.PatNum=pat.PatNum; hl7Msg.Note="Could not process HL7 SRM message due to an invalid or missing appointment ID in the ARQ segment. Appointment ID attempted: "+aptNum.ToString(); HL7Msgs.Insert(hl7Msg); throw new Exception("Could not process HL7 SRM message due to an invalid or missing appointment ID in the ARQ segment. Appointment ID attempted: "+aptNum.ToString()); } if(pat.PatNum!=aptCur.PatNum) {//an SRM must refer to a valid appt, therefore the patient cannot be new and must have a PatNum hl7Srr=MessageConstructor.GenerateSRR(pat,aptCur,msg.EventType,msg.ControlId,false,msg.AckEvent);//use false to indicate AE - Application Error in SRR.MSA segment hl7Msg=new HL7Msg(); hl7Msg.AptNum=aptCur.AptNum; hl7Msg.HL7Status=HL7MessageStatus.OutPending;//it will be marked outSent by the HL7 service. hl7Msg.MsgText=hl7Srr.ToString(); hl7Msg.PatNum=pat.PatNum; hl7Msg.Note="Could not process HL7 SRM message.\r\n" +"The patient identified in the PID segment is not the same as the patient on the appointment identified in the ARQ segment.\r\n" +"Appointment PatNum: "+aptCur.PatNum.ToString()+". PID segment PatNum: "+pat.PatNum.ToString()+"."; HL7Msgs.Insert(hl7Msg); throw new Exception("Could not process HL7 SRM message.\r\n" +"The patient identified in the PID segment is not the same as the patient on the appointment identified in the ARQ segment.\r\n" +"Appointment PatNum: "+aptCur.PatNum.ToString()+". PID segment PatNum: "+pat.PatNum.ToString()+"."); } } //We now have a patient object , either loaded from the db or new, and an appointment (could be null) so process this message for this patient //We need to insert the pat to get a patnum so we can compare to guar patnum to see if relationship to guar is self if(_isNewPat) { if(pat.PatNum==0) {//Only eCWTight or eCWFull internal types will allow the HL7 message to dictate our PatNums. pat.PatNum=Patients.Insert(pat,false); } else { pat.PatNum=Patients.Insert(pat,true); } if(_isVerboseLogging) { EventLog.WriteEntry("OpenDentHL7","Inserted patient "+pat.GetNameFLnoPref(),EventLogEntryType.Information); } patOld=pat.Copy(); } //Update hl7msg table with correct PatNum for this message _hl7MsgCur.PatNum=pat.PatNum; HL7Msgs.Update(_hl7MsgCur); if(aptCur!=null && pat.PatNum!=aptCur.PatNum) { throw new Exception("Appointment does not match patient "+pat.GetNameFLnoPref()+", apt.PatNum: "+aptCur.PatNum.ToString()+", pat.PatNum: "+pat.PatNum.ToString()); } for(int i=0;i<hl7defmsg.hl7DefSegments.Count;i++) { try { List<SegmentHL7> listSegments=new List<SegmentHL7>(); if(hl7defmsg.hl7DefSegments[i].CanRepeat) { listSegments=msg.GetSegments(hl7defmsg.hl7DefSegments[i].SegmentName,!hl7defmsg.hl7DefSegments[i].IsOptional); } else { SegmentHL7 seg=msg.GetSegment(hl7defmsg.hl7DefSegments[i].SegmentName,!hl7defmsg.hl7DefSegments[i].IsOptional); if(seg==null) {//null if segment was not found but is optional continue; } listSegments.Add(seg); } for(int j=0;j<listSegments.Count;j++) {//normally only 1 or 0 in the list, but if it is a repeatable segment the list may contain multiple segments to process if(_isVerboseLogging) { EventLog.WriteEntry("OpenDentHL7","Process segment "+hl7defmsg.hl7DefSegments[i].SegmentName.ToString(),EventLogEntryType.Information); } ProcessSeg(pat,aptCur,hl7defmsg.hl7DefSegments[i],listSegments[j],msg); if(hl7defmsg.hl7DefSegments[i].SegmentName==SegmentNameHL7.SCH) { aptCur=_aptProcessed; } } } catch(ApplicationException ex) {//Required segment was missing, or other error. _hl7MsgCur.Note="Could not process this HL7 message. "+ex; HL7Msgs.Update(_hl7MsgCur); if(!_isEcwHL7Def && msg.MsgType==MessageTypeHL7.SRM) {//SRM messages require sending an SRR response, this will be with Ack Code AE - Application Error MessageHL7 hl7Srr=MessageConstructor.GenerateSRR(pat,aptCur,msg.EventType,msg.ControlId,false,msg.AckEvent);//use false to indicate AE - Application Error in SRR.MSA segment HL7Msg hl7Msg=new HL7Msg(); hl7Msg.AptNum=aptCur.AptNum; hl7Msg.HL7Status=HL7MessageStatus.OutPending;//it will be marked outSent by the HL7 service. hl7Msg.MsgText=hl7Srr.ToString(); hl7Msg.Note="Could not process an HL7 SRM message. Send SRR for the request. "+ex; hl7Msg.PatNum=pat.PatNum; HL7Msgs.Insert(hl7Msg); } throw new Exception("Could not process an HL7 message. "+ex); } } //We have processed the message so now update the patient if(pat.FName=="" || pat.LName=="") { EventLog.WriteEntry("OpenDentHL7","Patient demographics not processed due to missing first or last name. PatNum:"+pat.PatNum.ToString() ,EventLogEntryType.Information); _hl7MsgCur.Note="Patient demographics not processed due to missing first or last name. PatNum:"+pat.PatNum.ToString(); HL7Msgs.Update(_hl7MsgCur); } else { if(_isNewPat && pat.Guarantor==0) { pat.Guarantor=pat.PatNum; } Patients.Update(pat,patOld); if(_isVerboseLogging) { EventLog.WriteEntry("OpenDentHL7","Updated patient "+pat.GetNameFLnoPref(),EventLogEntryType.Information); } _hl7MsgCur.HL7Status=HL7MessageStatus.InProcessed; HL7Msgs.Update(_hl7MsgCur); } //Schedule Request Messages require a Schedule Request Response if the data requested to be changed was successful //We only allow changing the appt note, setting the dentist and hygienist, updating the confirmation status, and changing the ClinicNum. //We also allow setting the appt status to broken if the EventType is S04 - Request Appointment Cancellation. //We will generate the SRR if we get here, since we will have processed the message properly. //The SRM will be ACK'd after returning from this Process method in ServiceHL7. //Our SRR will also be ACK'd by the receiving software. if(!_isEcwHL7Def && msg.MsgType==MessageTypeHL7.SRM) { if(msg.AckEvent=="S04") { Appointment aptOld=aptCur.Clone(); aptCur.AptStatus=ApptStatus.Broken; Appointments.Update(aptCur,aptOld); if(_isVerboseLogging) { EventLog.WriteEntry("OpenDentHL7","Appointment broken due to inbound SRM message with event type S04 for patient "+pat.GetNameFLnoPref(),EventLogEntryType.Information); } } MessageHL7 hl7Srr=MessageConstructor.GenerateSRR(pat,aptCur,msg.EventType,msg.ControlId,true,msg.AckEvent); HL7Msg hl7Msg=new HL7Msg(); hl7Msg.AptNum=aptCur.AptNum; hl7Msg.HL7Status=HL7MessageStatus.OutPending;//it will be marked outSent by the HL7 service. hl7Msg.MsgText=hl7Srr.ToString(); hl7Msg.PatNum=pat.PatNum; HL7Msgs.Insert(hl7Msg); } }
///<summary>Finds a patient from the information in the PID segment, using the PID segment definition in the enabled HL7 def. ///Will return null if a patient cannot be found using the information in the PID segment. If an alternate patient ID is ///provided and the name and birthdate in the PID segment match the name and birthdate of the patient located, ///it will be stored in the oidexternal table linked to the patient's PatNum.</summary> public static Patient GetPatFromPID(HL7DefSegment pidSegDef,SegmentHL7 pidSeg) { //PID.2 should contain the OpenDental PatNum. //If there is no patient with PatNum=PID.2, we will attempt to find the value in PID.4 in the oidexternals table IDExternal column //with root MedLabv2_3.Patient and type IdentifierType.Patient. //The PatNum found in the oidexternals table will only be trusted if the name and birthdate of the pat match the name and birthdate in the msg. //If there is no entry in the oidexternals table, we will attempt to match by name and birthdate alone. //If a patient is found, an entry will be made in the oidexternals table with IDExternal=PID.4, rootExternal=MedLabv2_3.Patient, //IDType=IdentifierType.Patient, and IDInternal=PatNum if one does not already exist. long patNum=0; long patNumFromAlt=0; string altPatID=""; string patLName=""; string patFName=""; DateTime birthdate=DateTime.MinValue; //Go through fields of PID segment and get patnum, altPatNum, patient name, and birthdate to locate patient for(int i=0;i<pidSegDef.hl7DefFields.Count;i++) { HL7DefField fieldDefCur=pidSegDef.hl7DefFields[i]; OIDExternal oidCur=null; switch(fieldDefCur.FieldName) { case "altPatID": altPatID=pidSeg.GetFieldComponent(fieldDefCur.OrdinalPos); if(altPatID=="") { continue; } oidCur=OIDExternals.GetByRootAndExtension(HL7InternalType.MedLabv2_3.ToString()+".Patient",altPatID); if(oidCur==null || oidCur.IDType!=IdentifierType.Patient) { //not in the oidexternals table or the oidexternal located is not for a patient object, patNumFromAlt will remain 0 continue; } patNumFromAlt=oidCur.IDInternal; continue; case "patBirthdateAge": //LabCorp sends the birthdate and age in years, months, and days like yyyyMMdd^YYY^MM^DD birthdate=FieldParser.DateTimeParse(pidSeg.GetFieldComponent(fieldDefCur.OrdinalPos)); continue; default: continue; case "pat.nameLFM": patLName=pidSeg.GetFieldComponent(fieldDefCur.OrdinalPos,0); patFName=pidSeg.GetFieldComponent(fieldDefCur.OrdinalPos,1); continue; case "pat.PatNum": try { patNum=PIn.Long(pidSeg.GetFieldComponent(fieldDefCur.OrdinalPos)); } catch(Exception ex) { //do nothing, patNum will remain 0 } continue; } } //We now have patNum, patNumFromAlt, patLName, patFName, and birthdate so locate pat Patient patCur=Patients.GetPat(patNum);//will be null if not found or patNum=0 #region Upsert the oidexternals Table //insert/update the altPatID and labPatID in the oidexternals table if a patient was found and the patient's name and birthdate match the message if(patCur!=null && IsMatchNameBirthdate(patCur,patLName,patFName,birthdate)) { //the altPatID field was populated in the PID segment (usually PID.4) and the altPatID isn't stored in the oidexternals table as IDExternal if(altPatID!="") { if(patNumFromAlt==0) { //if the altPatID isn't stored in the oidexternals table as IDExternal, insert OIDExternal oidCur=new OIDExternal(); oidCur.IDType=IdentifierType.Patient; oidCur.IDInternal=patCur.PatNum; oidCur.rootExternal=HL7InternalType.MedLabv2_3.ToString()+".Patient"; oidCur.IDExternal=altPatID; OIDExternals.Insert(oidCur); } else if(patCur.PatNum!=patNumFromAlt) { //else if patCur.PatNum is different than the IDInternal stored in the oidexternals table, update OIDExternal oidCur=OIDExternals.GetByRootAndExtension(HL7InternalType.MedLabv2_3.ToString()+".Patient",altPatID); oidCur.IDInternal=patCur.PatNum; OIDExternals.Update(oidCur); } } } #endregion Upsert the oidexternals Table //patCur is null so try to find a patient from the altPatID (PID.4) if(patCur==null && patNumFromAlt>0) { patCur=Patients.GetPat(patNumFromAlt); //We will only trust the altPatID if the name and birthdate of the patient match the name and birthdate in the message. if(!IsMatchNameBirthdate(patCur,patLName,patFName,birthdate)) { patCur=null; } } //if we haven't located a patient yet, attempt with name and birthdate if(patCur==null && patLName!="" && patFName!="" && birthdate>DateTime.MinValue) { long patNumByName=Patients.GetPatNumByNameAndBirthday(patLName,patFName,birthdate); if(patNumByName>0) { patCur=Patients.GetPat(patNumByName); } } return patCur; }
public static void ProcessPR1(Patient pat,HL7DefSegment segDef,SegmentHL7 seg,MessageHL7 msg) { string strToothNum="";//could contain the tooth range in comma-delimited list string strSurf="";//used for surf/quad/sext/arch string strOidExt=""; string strOidExtRoot=""; ProcedureCode procCode=null; DateTime dateProc=DateTime.MinValue; Hashtable hashProcedureCodes=ProcedureCodeC.GetHList(); for(int i=0;i<segDef.hl7DefFields.Count;i++) { int itemOrder=segDef.hl7DefFields[i].OrdinalPos; switch(segDef.hl7DefFields[i].FieldName) { case "proccode.ProcCode": //ProcCode^descript (ignored)^CD2^^^^2014^^Layman Term (ignored) //Example: D1351^^CD2^^^^2014 //must be CDT code (CD2 is code system abbr according to HL7 documentation) and must be version 2014 if(seg.GetFieldComponent(itemOrder,2).ToLower()!="cd2" || seg.GetFieldComponent(itemOrder,6)!="2014") { EventLog.WriteEntry("OpenDentHL7","A procedure was not added for patient "+pat.GetNameFLnoPref() +". Only CDT codes from code system version 2014 are currently allowed in the PR1 segment. The code system name provided was " +seg.GetFieldComponent(itemOrder,2)+" and the code system version provided was "+seg.GetFieldComponent(itemOrder,6)+".",EventLogEntryType.Information); return; } string strProcCode=seg.GetFieldComponent(itemOrder,0); if(!hashProcedureCodes.ContainsKey(strProcCode)) {//code does not exist in proc code list, write entry in event log an return EventLog.WriteEntry("OpenDentHL7","A procedure was not added for patient "+pat.GetNameFLnoPref() +". The code supplied in the PR1 segment does not exist in the database. The code provided was "+strProcCode+".",EventLogEntryType.Information); return; } procCode=(ProcedureCode)hashProcedureCodes[strProcCode]; continue; case "proc.procDateTime": dateProc=FieldParser.DateTimeParse(seg.GetFieldComponent(itemOrder)); continue; case "proc.toothSurfRange": char subcompSeparator='&'; if(msg.Delimiters.Length>3) { subcompSeparator=msg.Delimiters[3]; } string[] listSubComponents=seg.GetFieldComponent(itemOrder).Split(new char[] { subcompSeparator },StringSplitOptions.None); if(listSubComponents.Length>0) { strToothNum=listSubComponents[0]; } if(listSubComponents.Length>1) { strSurf=listSubComponents[1]; } continue; case "proc.uniqueId": //Id^^UniversalId strOidExt=seg.GetFieldComponent(itemOrder,0); strOidExtRoot=seg.GetFieldComponent(itemOrder,2); if(strOidExt!="" && strOidExtRoot!="" && OIDExternals.GetByRootAndExtension(strOidExtRoot,strOidExt)!=null) { //the universal ID and root are already in the oidexternals table, do not insert another procedure, must be a duplicate EventLog.WriteEntry("OpenDentHL7","A procedure was not added for patient "+pat.GetNameFLnoPref() +". The universal ID and root supplied in the PR1 segment refers to an existing procedure." +" Inserting another would result in a duplicate procedure. The ID provided was " +strOidExt+", the root was "+strOidExtRoot+".",EventLogEntryType.Information); return; } continue; default: continue; } } if(procCode==null) { EventLog.WriteEntry("OpenDentHL7","A procedure was not added for patient "+pat.GetNameFLnoPref() +". No procedure code was defined for a PR1 segment or was missing.",EventLogEntryType.Information); return; } Procedure procCur=new Procedure(); #region Validate/Convert/Set Treatment Area switch(procCode.TreatArea) { case TreatmentArea.Arch: if(strSurf!="L" && strSurf!="U") { EventLog.WriteEntry("OpenDentHL7","A procedure was not added for patient "+pat.GetNameFLnoPref()+". The treatment area for the code " +procCode.ProcCode+" is arch but the arch of "+strSurf+" is invalid.",EventLogEntryType.Information); return; } procCur.Surf=strSurf; break; case TreatmentArea.Quad: if(strSurf!="UL" && strSurf!="UR" && strSurf!="LL" && strSurf!="LR") { EventLog.WriteEntry("OpenDentHL7","A procedure was not added for patient "+pat.GetNameFLnoPref()+". The treatment area for the code " +procCode.ProcCode+" is quadrant but the quadrant of "+strSurf+" is invalid.",EventLogEntryType.Information); return; } procCur.Surf=strSurf; break; case TreatmentArea.Sextant: bool isValidSextant=false; if(strSurf=="1" || strSurf=="2" || strSurf=="3" || strSurf=="4" || strSurf=="5" || strSurf=="6") { isValidSextant=true; } if(CultureInfo.CurrentCulture.Name.EndsWith("CA")) {//Canadian. en-CA or fr-CA if(strSurf=="03" || strSurf=="04" || strSurf=="05" || strSurf=="06" || strSurf=="07" || strSurf=="08") { isValidSextant=true; } } if(!isValidSextant) { EventLog.WriteEntry("OpenDentHL7","A procedure was not added for patient "+pat.GetNameFLnoPref()+". The treatment area for the code " +procCode.ProcCode+" is sextant but the sextant of "+strSurf+" is invalid.",EventLogEntryType.Information); return; } procCur.Surf=strSurf; break; case TreatmentArea.Surf: if(!Tooth.IsValidEntry(strToothNum)) { EventLog.WriteEntry("OpenDentHL7","A procedure was not added for patient "+pat.GetNameFLnoPref()+". The treatment area for the code " +procCode.ProcCode+" is surface but the tooth number of "+strToothNum+" is invalid.",EventLogEntryType.Information); } procCur.ToothNum=Tooth.FromInternat(strToothNum); string strSurfTidy=Tooth.SurfTidyFromDisplayToDb(strSurf,procCur.ToothNum); if(strSurfTidy=="" || strSurf=="") { EventLog.WriteEntry("OpenDentHL7","A procedure was not added for patient "+pat.GetNameFLnoPref()+". The treatment area for the code " +procCode.ProcCode+" is surface but the surface of "+strSurf+" is invalid.",EventLogEntryType.Information); return; } procCur.Surf=strSurfTidy; break; case TreatmentArea.Tooth: if(!Tooth.IsValidEntry(strToothNum)) { EventLog.WriteEntry("OpenDentHL7","A procedure was not added for patient "+pat.GetNameFLnoPref()+". The treatment area for the code " +procCode.ProcCode+" is tooth but the tooth number of "+strToothNum+" is invalid.",EventLogEntryType.Information); return; } procCur.ToothNum=Tooth.FromInternat(strToothNum); break; case TreatmentArea.ToothRange: //break up the list of tooth numbers supplied and validate and convert them into universal tooth numbers for inserting into the db string[] listToothNums=strToothNum.Split(new char[] { ',' },StringSplitOptions.RemoveEmptyEntries); for(int i=0;i<listToothNums.Length;i++) { if(!Tooth.IsValidEntry(listToothNums[i])) { EventLog.WriteEntry("OpenDentHL7","A procedure was not added for patient "+pat.GetNameFLnoPref()+". The treatment area for the code " +procCode.ProcCode+" is tooth range but the tooth number of "+listToothNums[i]+" is invalid.",EventLogEntryType.Information); return; } if(Tooth.IsPrimary(Tooth.FromInternat(listToothNums[i]))) { EventLog.WriteEntry("OpenDentHL7","A procedure was not added for patient "+pat.GetNameFLnoPref()+". The treatment area for the code "+procCode.ProcCode +" is tooth range but the tooth number of "+listToothNums[i]+" is a primary tooth number and therefore not allowed.",EventLogEntryType.Information); } listToothNums[i]=Tooth.FromInternat(listToothNums[i]); } procCur.ToothNum=string.Join(",",listToothNums); break; //We won't validate or use the tooth number or surface fields if the treatment are of the proccode is mouth or none case TreatmentArea.None: case TreatmentArea.Mouth: default: break; } #endregion Validate/Convert/Set Treatment Area Procedures.SetDateFirstVisit(dateProc,1,pat);//wait until after validating, might not insert the proc, don't return after this point procCur.PatNum=pat.PatNum; procCur.CodeNum=procCode.CodeNum; procCur.ProcDate=dateProc; procCur.DateTP=dateProc; procCur.ProcStatus=ProcStat.TP; procCur.ProvNum=pat.PriProv; if(procCode.ProvNumDefault!=0) { procCur.ProvNum=procCode.ProvNumDefault; } else if(procCode.IsHygiene && pat.SecProv!=0) { procCur.ProvNum=pat.SecProv; } procCur.Note=""; procCur.ClinicNum=pat.ClinicNum; procCur.BaseUnits=procCode.BaseUnits; procCur.SiteNum=pat.SiteNum; procCur.RevCode=procCode.RevenueCodeDefault; procCur.DiagnosticCode=PrefC.GetString(PrefName.ICD9DefaultForNewProcs); List<PatPlan> listPatPlan=PatPlans.Refresh(pat.PatNum); List<Benefit> listBen=Benefits.Refresh(listPatPlan,new List<InsSub>()); #region Set ProcFee From Fee Schedule //check if it's a medical procedure bool isMed=false; procCur.MedicalCode=procCode.MedicalCode; if(procCur.MedicalCode!=null && procCur.MedicalCode!="") { isMed=true; } //Get fee schedule for medical or dental long feeSch; if(isMed) { feeSch=Fees.GetMedFeeSched(pat,new List<InsPlan>(),listPatPlan,new List<InsSub>()); } else { feeSch=Fees.GetFeeSched(pat,new List<InsPlan>(),listPatPlan,new List<InsSub>()); } //Get the fee amount for medical or dental double insfee; if(PrefC.GetBool(PrefName.MedicalFeeUsedForNewProcs) && isMed) { insfee=Fees.GetAmount0(ProcedureCodes.GetCodeNum(procCur.MedicalCode),feeSch); } else { insfee=Fees.GetAmount0(procCode.CodeNum,feeSch); } InsPlan priplan=null; if(listPatPlan.Count>0) { priplan=InsPlans.GetPlan(InsSubs.GetSub(listPatPlan[0].InsSubNum,new List<InsSub>()).PlanNum,new List<InsPlan>()); } if(priplan!=null && priplan.PlanType=="p" && !isMed) {//PPO Provider patProv=Providers.GetProv(pat.PriProv); if(patProv==null) { patProv=Providers.GetProv(PrefC.GetLong(PrefName.PracticeDefaultProv)); } double standardFee=Fees.GetAmount0(procCode.CodeNum,patProv.FeeSched); if(standardFee>insfee) { procCur.ProcFee=standardFee; } else { procCur.ProcFee=insfee; } } else { procCur.ProcFee=insfee; } #endregion Set ProcFee From Fee Schedule procCur.ProcNum=Procedures.Insert(procCur); if(_isVerboseLogging) { EventLog.WriteEntry("OpenDentHL7","Inserted a new procedure for patient "+pat.GetNameFLnoPref()+" due to an incoming PR1 segment.",EventLogEntryType.Information); } if(strOidExt!="" && strOidExtRoot!="") { OIDExternal procOidExt=new OIDExternal(); procOidExt.IDType=IdentifierType.Procedure; procOidExt.IDInternal=procCur.ProcNum; procOidExt.IDExternal=strOidExt; procOidExt.rootExternal=strOidExtRoot; OIDExternals.Insert(procOidExt); if(_isVerboseLogging) { EventLog.WriteEntry("OpenDentHL7","Added an external procedure ID to the oidexternals table due to an incoming PR1 segment.\r\nProcNum: " +procCur.ProcNum.ToString()+", External problem ID: "+procOidExt.IDExternal+", External root: "+procOidExt.rootExternal+".",EventLogEntryType.Information); } } Procedures.ComputeEstimates(procCur,pat.PatNum,new List<ClaimProc>(),true,new List<InsPlan>(),listPatPlan,listBen,pat.Age,new List<InsSub>()); return; }