///<summary>Inserts one HL7Msg into the database. Returns the new priKey.</summary> internal static long Insert(HL7Msg hL7Msg) { if(DataConnection.DBtype==DatabaseType.Oracle) { hL7Msg.HL7MsgNum=DbHelper.GetNextOracleKey("hl7msg","HL7MsgNum"); int loopcount=0; while(loopcount<100){ try { return Insert(hL7Msg,true); } catch(Oracle.DataAccess.Client.OracleException ex){ if(ex.Number==1 && ex.Message.ToLower().Contains("unique constraint") && ex.Message.ToLower().Contains("violated")){ hL7Msg.HL7MsgNum++; loopcount++; } else{ throw ex; } } } throw new ApplicationException("Insert failed. Could not generate primary key."); } else { return Insert(hL7Msg,false); } }
///<summary>Inserts one HL7Msg into the database. Provides option to use the existing priKey.</summary> internal static long Insert(HL7Msg hL7Msg,bool useExistingPK) { if(!useExistingPK && PrefC.RandomKeys) { hL7Msg.HL7MsgNum=ReplicationServers.GetKey("hl7msg","HL7MsgNum"); } string command="INSERT INTO hl7msg ("; if(useExistingPK || PrefC.RandomKeys) { command+="HL7MsgNum,"; } command+="HL7Status,MsgText,AptNum) VALUES("; if(useExistingPK || PrefC.RandomKeys) { command+=POut.Long(hL7Msg.HL7MsgNum)+","; } command+= POut.Int ((int)hL7Msg.HL7Status)+"," +DbHelper.ParamChar+"paramMsgText," + POut.Long (hL7Msg.AptNum)+")"; if(hL7Msg.MsgText==null) { hL7Msg.MsgText=""; } OdSqlParameter paramMsgText=new OdSqlParameter("paramMsgText",OdDbType.Text,hL7Msg.MsgText); if(useExistingPK || PrefC.RandomKeys) { Db.NonQ(command,paramMsgText); } else { hL7Msg.HL7MsgNum=Db.NonQ(command,true,paramMsgText); } return hL7Msg.HL7MsgNum; }
//OD accepts commandline arguments from eCW. That's handled in FormOpenDental. //public static void SendHL7(Appointment apt,Patient pat) { // OpenDentBusiness.HL7.DFT dft=new OpenDentBusiness.HL7.DFT(apt,pat); // HL7Msg msg=new HL7Msg(); // msg.AptNum=apt.AptNum; // msg.HL7Status=HL7MessageStatus.OutPending;//it will be marked outSent by the HL7 service. // msg.MsgText=dft.GenerateMessage(); // HL7Msgs.Insert(msg); //} public static void SendHL7(long aptNum, long provNum, Patient pat, string pdfDataBase64, string pdfDescription, bool justPDF, List <Procedure> listProcs) { OpenDentBusiness.HL7.EcwDFT dft = new OpenDentBusiness.HL7.EcwDFT(); dft.InitializeEcw(aptNum, provNum, pat, pdfDataBase64, pdfDescription, justPDF, listProcs); HL7Msg msg = new HL7Msg(); if (justPDF) { msg.AptNum = 0; //Prevents the appt complete button from changing to the "Revise" button prematurely. } else { msg.AptNum = aptNum; } msg.HL7Status = HL7MessageStatus.OutPending; //it will be marked outSent by the HL7 service. msg.MsgText = dft.GenerateMessage(); msg.PatNum = pat.PatNum; HL7ProcAttach hl7ProcAttach = new HL7ProcAttach(); hl7ProcAttach.HL7MsgNum = HL7Msgs.Insert(msg); if (listProcs != null) { foreach (Procedure proc in listProcs) { hl7ProcAttach.ProcNum = proc.ProcNum; HL7ProcAttaches.Insert(hl7ProcAttach); } } }
///<summary>Returns true if Update(HL7Msg,HL7Msg) 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(HL7Msg hL7Msg, HL7Msg oldHL7Msg) { if (hL7Msg.HL7Status != oldHL7Msg.HL7Status) { return(true); } if (hL7Msg.MsgText != oldHL7Msg.MsgText) { return(true); } if (hL7Msg.AptNum != oldHL7Msg.AptNum) { return(true); } //DateTStamp can only be set by MySQL if (hL7Msg.PatNum != oldHL7Msg.PatNum) { return(true); } if (hL7Msg.Note != oldHL7Msg.Note) { return(true); } return(false); }
///<summary>Inserts one HL7Msg into the database. Returns the new priKey.</summary> public static long Insert(HL7Msg hL7Msg) { if (DataConnection.DBtype == DatabaseType.Oracle) { hL7Msg.HL7MsgNum = DbHelper.GetNextOracleKey("hl7msg", "HL7MsgNum"); int loopcount = 0; while (loopcount < 100) { try { return(Insert(hL7Msg, true)); } catch (Oracle.DataAccess.Client.OracleException ex) { if (ex.Number == 1 && ex.Message.ToLower().Contains("unique constraint") && ex.Message.ToLower().Contains("violated")) { hL7Msg.HL7MsgNum++; loopcount++; } else { throw ex; } } } throw new ApplicationException("Insert failed. Could not generate primary key."); } else { return(Insert(hL7Msg, false)); } }
///<summary>Updates one HL7Msg in the database.</summary> public static void Update(HL7Msg hL7Msg) { string command = "UPDATE hl7msg SET " + "HL7Status = " + POut.Int((int)hL7Msg.HL7Status) + ", " + "MsgText = " + DbHelper.ParamChar + "paramMsgText, " + "AptNum = " + POut.Long(hL7Msg.AptNum) + ", " //DateTStamp can only be set by MySQL + "PatNum = " + POut.Long(hL7Msg.PatNum) + ", " + "Note = " + DbHelper.ParamChar + "paramNote " + "WHERE HL7MsgNum = " + POut.Long(hL7Msg.HL7MsgNum); if (hL7Msg.MsgText == null) { hL7Msg.MsgText = ""; } OdSqlParameter paramMsgText = new OdSqlParameter("paramMsgText", OdDbType.Text, POut.StringParam(hL7Msg.MsgText)); if (hL7Msg.Note == null) { hL7Msg.Note = ""; } OdSqlParameter paramNote = new OdSqlParameter("paramNote", OdDbType.Text, POut.StringParam(hL7Msg.Note)); Db.NonQ(command, paramMsgText, paramNote); }
///<summary>Updates one HL7Msg 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.</summary> public static void Update(HL7Msg hL7Msg, HL7Msg oldHL7Msg) { string command = ""; if (hL7Msg.HL7Status != oldHL7Msg.HL7Status) { if (command != "") { command += ","; } command += "HL7Status = " + POut.Int((int)hL7Msg.HL7Status) + ""; } if (hL7Msg.MsgText != oldHL7Msg.MsgText) { if (command != "") { command += ","; } command += "MsgText = " + DbHelper.ParamChar + "paramMsgText"; } if (hL7Msg.AptNum != oldHL7Msg.AptNum) { if (command != "") { command += ","; } command += "AptNum = " + POut.Long(hL7Msg.AptNum) + ""; } //DateTStamp can only be set by MySQL if (hL7Msg.PatNum != oldHL7Msg.PatNum) { if (command != "") { command += ","; } command += "PatNum = " + POut.Long(hL7Msg.PatNum) + ""; } if (hL7Msg.Note != oldHL7Msg.Note) { if (command != "") { command += ","; } command += "Note = '" + POut.String(hL7Msg.Note) + "'"; } if (command == "") { return; } if (hL7Msg.MsgText == null) { hL7Msg.MsgText = ""; } OdSqlParameter paramMsgText = new OdSqlParameter("paramMsgText", OdDbType.Text, hL7Msg.MsgText); command = "UPDATE hl7msg SET " + command + " WHERE HL7MsgNum = " + POut.Long(hL7Msg.HL7MsgNum); Db.NonQ(command, paramMsgText); }
/// <summary>Helper method to send given appt to the unscheduled list. /// Creates SecurityLogs and considers HL7.</summary> public static void SetApptUnschedHelper(Appointment appt, Patient pat = null, bool doFireApptEvent = true) { DateTime datePrevious = appt.DateTStamp; Appointments.SetAptStatus(appt, ApptStatus.UnschedList); //Appointments S-Class handles Signalods #region SecurityLogs if (appt.AptStatus != ApptStatus.Complete) //seperate log entry for editing completed appts. { SecurityLogs.MakeLogEntry(Permissions.AppointmentMove, appt.PatNum, appt.ProcDescript + ", " + appt.AptDateTime.ToString() + ", Sent to Unscheduled List", appt.AptNum, datePrevious); } else { SecurityLogs.MakeLogEntry(Permissions.AppointmentCompleteEdit, appt.PatNum, appt.ProcDescript + ", " + appt.AptDateTime.ToString() + ", Sent to Unscheduled List", appt.AptNum, datePrevious); } #endregion #region HL7 //If there is an existing HL7 def enabled, send a SIU message if there is an outbound SIU message defined if (HL7Defs.IsExistingHL7Enabled()) { if (pat == null) { pat = Patients.GetPat(appt.PatNum); } //S15 - Appt Cancellation event MessageHL7 messageHL7 = MessageConstructor.GenerateSIU(pat, Patients.GetPat(pat.Guarantor), EventTypeHL7.S15, appt); //Will be null if there is no outbound SIU message defined, so do nothing if (messageHL7 != null) { HL7Msg hl7Msg = new HL7Msg(); hl7Msg.AptNum = appt.AptNum; hl7Msg.HL7Status = HL7MessageStatus.OutPending; //it will be marked outSent by the HL7 service. hl7Msg.MsgText = messageHL7.ToString(); hl7Msg.PatNum = pat.PatNum; HL7Msgs.Insert(hl7Msg); #if DEBUG MessageBox.Show("Appointments", messageHL7.ToString()); #endif } } #endregion if (doFireApptEvent) { AppointmentEvent.Fire(ODEventType.AppointmentEdited, appt); } Recalls.SynchScheduledApptFull(appt.PatNum); }
///<summary>Inserts one HL7Msg into the database. Returns the new priKey. Doesn't use the cache.</summary> public static long InsertNoCache(HL7Msg hL7Msg) { if (DataConnection.DBtype == DatabaseType.MySql) { return(InsertNoCache(hL7Msg, false)); } else { if (DataConnection.DBtype == DatabaseType.Oracle) { hL7Msg.HL7MsgNum = DbHelper.GetNextOracleKey("hl7msg", "HL7MsgNum"); //Cacheless method } return(InsertNoCache(hL7Msg, true)); } }
///<summary>Updates one HL7Msg in the database.</summary> internal static void Update(HL7Msg hL7Msg) { string command = "UPDATE hl7msg SET " + "HL7Status= " + POut.Int((int)hL7Msg.HL7Status) + ", " + "MsgText = " + DbHelper.ParamChar + "paramMsgText, " + "AptNum = " + POut.Long(hL7Msg.AptNum) + " " + "WHERE HL7MsgNum = " + POut.Long(hL7Msg.HL7MsgNum); if (hL7Msg.MsgText == null) { hL7Msg.MsgText = ""; } OdSqlParameter paramMsgText = new OdSqlParameter("paramMsgText", OdDbType.Text, hL7Msg.MsgText); Db.NonQ(command, paramMsgText); }
///<summary>Converts a DataTable to a list of objects.</summary> public static List<HL7Msg> TableToList(DataTable table){ List<HL7Msg> retVal=new List<HL7Msg>(); HL7Msg hL7Msg; for(int i=0;i<table.Rows.Count;i++) { hL7Msg=new HL7Msg(); hL7Msg.HL7MsgNum = PIn.Long (table.Rows[i]["HL7MsgNum"].ToString()); hL7Msg.HL7Status = (OpenDentBusiness.HL7MessageStatus)PIn.Int(table.Rows[i]["HL7Status"].ToString()); hL7Msg.MsgText = PIn.String(table.Rows[i]["MsgText"].ToString()); hL7Msg.AptNum = PIn.Long (table.Rows[i]["AptNum"].ToString()); hL7Msg.DateTStamp= PIn.DateT (table.Rows[i]["DateTStamp"].ToString()); hL7Msg.PatNum = PIn.Long (table.Rows[i]["PatNum"].ToString()); hL7Msg.Note = PIn.String(table.Rows[i]["Note"].ToString()); retVal.Add(hL7Msg); } return retVal; }
///<summary>Converts a DataTable to a list of objects.</summary> internal static List <HL7Msg> TableToList(DataTable table) { List <HL7Msg> retVal = new List <HL7Msg>(); HL7Msg hL7Msg; for (int i = 0; i < table.Rows.Count; i++) { hL7Msg = new HL7Msg(); hL7Msg.HL7MsgNum = PIn.Long(table.Rows[i]["HL7MsgNum"].ToString()); hL7Msg.HL7Status = (HL7MessageStatus)PIn.Int(table.Rows[i]["HL7Status"].ToString()); hL7Msg.MsgText = PIn.String(table.Rows[i]["MsgText"].ToString()); hL7Msg.AptNum = PIn.Long(table.Rows[i]["AptNum"].ToString()); retVal.Add(hL7Msg); } return(retVal); }
///<summary>Inserts one HL7Msg into the database. Provides option to use the existing priKey. Doesn't use the cache.</summary> public static long InsertNoCache(HL7Msg hL7Msg, bool useExistingPK) { bool isRandomKeys = Prefs.GetBoolNoCache(PrefName.RandomPrimaryKeys); string command = "INSERT INTO hl7msg ("; if (!useExistingPK && isRandomKeys) { hL7Msg.HL7MsgNum = ReplicationServers.GetKeyNoCache("hl7msg", "HL7MsgNum"); } if (isRandomKeys || useExistingPK) { command += "HL7MsgNum,"; } command += "HL7Status,MsgText,AptNum,PatNum,Note) VALUES("; if (isRandomKeys || useExistingPK) { command += POut.Long(hL7Msg.HL7MsgNum) + ","; } command += POut.Int((int)hL7Msg.HL7Status) + "," + DbHelper.ParamChar + "paramMsgText," + POut.Long(hL7Msg.AptNum) + "," //DateTStamp can only be set by MySQL + POut.Long(hL7Msg.PatNum) + "," + DbHelper.ParamChar + "paramNote)"; if (hL7Msg.MsgText == null) { hL7Msg.MsgText = ""; } OdSqlParameter paramMsgText = new OdSqlParameter("paramMsgText", OdDbType.Text, POut.StringParam(hL7Msg.MsgText)); if (hL7Msg.Note == null) { hL7Msg.Note = ""; } OdSqlParameter paramNote = new OdSqlParameter("paramNote", OdDbType.Text, POut.StringParam(hL7Msg.Note)); if (useExistingPK || isRandomKeys) { Db.NonQ(command, paramMsgText, paramNote); } else { hL7Msg.HL7MsgNum = Db.NonQ(command, true, "HL7MsgNum", "hL7Msg", paramMsgText, paramNote); } return(hL7Msg.HL7MsgNum); }
//OD accepts commandline arguments from eCW. That's handled in FormOpenDental. //public static void SendHL7(Appointment apt,Patient pat) { // OpenDentBusiness.HL7.DFT dft=new OpenDentBusiness.HL7.DFT(apt,pat); // HL7Msg msg=new HL7Msg(); // msg.AptNum=apt.AptNum; // msg.HL7Status=HL7MessageStatus.OutPending;//it will be marked outSent by the HL7 service. // msg.MsgText=dft.GenerateMessage(); // HL7Msgs.Insert(msg); //} public static void SendHL7(long aptNum, long provNum, Patient pat, string pdfDataBase64, string pdfDescription, bool justPDF) { OpenDentBusiness.HL7.DFT dft = new OpenDentBusiness.HL7.DFT(); dft.InitializeEcw(aptNum, provNum, pat, pdfDataBase64, pdfDescription, justPDF); HL7Msg msg = new HL7Msg(); if (justPDF) { msg.AptNum = 0; //Prevents the appt complete button from changing to the "Revise" button prematurely. } else { msg.AptNum = aptNum; } msg.HL7Status = HL7MessageStatus.OutPending; //it will be marked outSent by the HL7 service. msg.MsgText = dft.GenerateMessage(); HL7Msgs.Insert(msg); }
///<summary>Converts a DataTable to a list of objects.</summary> public static List <HL7Msg> TableToList(DataTable table) { List <HL7Msg> retVal = new List <HL7Msg>(); HL7Msg hL7Msg; foreach (DataRow row in table.Rows) { hL7Msg = new HL7Msg(); hL7Msg.HL7MsgNum = PIn.Long(row["HL7MsgNum"].ToString()); hL7Msg.HL7Status = (OpenDentBusiness.HL7MessageStatus)PIn.Int(row["HL7Status"].ToString()); hL7Msg.MsgText = PIn.String(row["MsgText"].ToString()); hL7Msg.AptNum = PIn.Long(row["AptNum"].ToString()); hL7Msg.DateTStamp = PIn.DateT(row["DateTStamp"].ToString()); hL7Msg.PatNum = PIn.Long(row["PatNum"].ToString()); hL7Msg.Note = PIn.String(row["Note"].ToString()); retVal.Add(hL7Msg); } return(retVal); }
///<summary>Inserts one HL7Msg into the database. Provides option to use the existing priKey.</summary> public static long Insert(HL7Msg hL7Msg, bool useExistingPK) { if (!useExistingPK && PrefC.RandomKeys) { hL7Msg.HL7MsgNum = ReplicationServers.GetKey("hl7msg", "HL7MsgNum"); } string command = "INSERT INTO hl7msg ("; if (useExistingPK || PrefC.RandomKeys) { command += "HL7MsgNum,"; } command += "HL7Status,MsgText,AptNum,PatNum,Note) VALUES("; if (useExistingPK || PrefC.RandomKeys) { command += POut.Long(hL7Msg.HL7MsgNum) + ","; } command += POut.Int((int)hL7Msg.HL7Status) + "," + DbHelper.ParamChar + "paramMsgText," + POut.Long(hL7Msg.AptNum) + "," //DateTStamp can only be set by MySQL + POut.Long(hL7Msg.PatNum) + "," + "'" + POut.String(hL7Msg.Note) + "')"; if (hL7Msg.MsgText == null) { hL7Msg.MsgText = ""; } OdSqlParameter paramMsgText = new OdSqlParameter("paramMsgText", OdDbType.Text, hL7Msg.MsgText); if (useExistingPK || PrefC.RandomKeys) { Db.NonQ(command, paramMsgText); } else { hL7Msg.HL7MsgNum = Db.NonQ(command, true, paramMsgText); } return(hL7Msg.HL7MsgNum); }
///<summary>Runs in a separate thread</summary> private void OnDataReceived(IAsyncResult asyncResult) { int byteCountReceived = socketIncomingWorker.EndReceive(asyncResult); //blocks until data is recieved. char[] chars = new char[byteCountReceived]; Decoder decoder = Encoding.UTF8.GetDecoder(); decoder.GetChars(dataBufferIncoming, 0, byteCountReceived, chars, 0); //doesn't necessarily get all bytes from the buffer because buffer could be half full. strbFullMsg.Append(chars); //strbFullMsg might already have partial data //I think we are guaranteed to have received at least one char. bool isFullMsg = false; bool isMalformed = false; if (strbFullMsg.Length == 1 && strbFullMsg[0] == MLLP_ENDMSG_CHAR) //the only char in the message is the end char { strbFullMsg.Clear(); //this must be the very end of a previously processed message. Discard. isFullMsg = false; } //else if(strbFullMsg[0]!=MLLP_START_CHAR) { else if (strbFullMsg.Length > 0 && strbFullMsg[0] != MLLP_START_CHAR) { //Malformed message. isFullMsg = true; //we're going to do this so that the error gets saved in the database further down. isMalformed = true; } else if (strbFullMsg.Length >= 3 && //so that the next two lines won't crash strbFullMsg[strbFullMsg.Length - 1] == MLLP_ENDMSG_CHAR && //last char is the endmsg char. strbFullMsg[strbFullMsg.Length - 2] == MLLP_END_CHAR) //the second-to-the-last char is the end char. { //we have a complete message strbFullMsg.Remove(0, 1); //strip off the start char strbFullMsg.Remove(strbFullMsg.Length - 2, 2); //strip off the end chars isFullMsg = true; } else if (strbFullMsg.Length >= 2 && //so that the next line won't crash strbFullMsg[strbFullMsg.Length - 1] == MLLP_END_CHAR) //the last char is the end char. { //we will treat this as a complete message, because the endmsg char is optional. //if the endmsg char gets sent in a subsequent block, the code above will discard it. strbFullMsg.Remove(0, 1); //strip off the start char strbFullMsg.Remove(strbFullMsg.Length - 1, 1); //strip off the end char isFullMsg = true; } else { isFullMsg = false; //this is an incomplete message. Continue to receive more blocks. } //end of big if statement------------------------------------------------- if (!isFullMsg) { dataBufferIncoming = new byte[8]; //clear the buffer socketIncomingWorker.BeginReceive(dataBufferIncoming, 0, dataBufferIncoming.Length, SocketFlags.None, new AsyncCallback(OnDataReceived), null); return; //get another block } //Prepare to save message to database if malformed and not processed HL7Msg hl7Msg = new HL7Msg(); hl7Msg.MsgText = strbFullMsg.ToString(); strbFullMsg.Clear(); //ready for the next message bool isProcessed = true; string messageControlId = ""; string ackEvent = ""; if (isMalformed) { hl7Msg.HL7Status = HL7MessageStatus.InFailed; hl7Msg.Note = "This message is malformed so it was not processed."; HL7Msgs.Insert(hl7Msg); isProcessed = false; } else { MessageHL7 messageHl7Object = new MessageHL7(hl7Msg.MsgText); //this creates an entire heirarchy of objects. try { MessageParser.Process(messageHl7Object, IsVerboseLogging); //also saves the message to the db messageControlId = messageHl7Object.ControlId; ackEvent = messageHl7Object.AckEvent; } catch (Exception ex) { EventLog.WriteEntry("OpenDentHL7", "Error in OnDataRecieved when processing message:\r\n" + ex.Message + "\r\n" + ex.StackTrace, EventLogEntryType.Error); isProcessed = false; } } MessageHL7 hl7Ack = MessageConstructor.GenerateACK(messageControlId, isProcessed, ackEvent); if (hl7Ack == null) { EventLog.WriteEntry("OpenDentHL7", "No ACK defined for the enabled HL7 definition or no HL7 definition enabled.", EventLogEntryType.Information); return; } byte[] ackByteOutgoing = Encoding.ASCII.GetBytes(MLLP_START_CHAR + hl7Ack.ToString() + MLLP_END_CHAR + MLLP_ENDMSG_CHAR); if (IsVerboseLogging) { EventLog.WriteEntry("OpenDentHL7", "Beginning to send ACK.\r\n" + MLLP_START_CHAR + hl7Ack.ToString() + MLLP_END_CHAR + MLLP_ENDMSG_CHAR, EventLogEntryType.Information); } socketIncomingWorker.Send(ackByteOutgoing); //this is a locking call //eCW uses the same worker socket to send the next message. Without this call to BeginReceive, they would attempt to send again //and the send would fail since we were no longer listening in this thread. eCW would timeout after 30 seconds of waiting for their //acknowledgement, then they would close their end and create a new socket for the next message. With this call, we can accept message //after message without waiting for a new connection. dataBufferIncoming = new byte[8]; //clear the buffer socketIncomingWorker.BeginReceive(dataBufferIncoming, 0, dataBufferIncoming.Length, SocketFlags.None, new AsyncCallback(OnDataReceived), null); }
///<summary>Converts a DataTable to a list of objects.</summary> internal static List<HL7Msg> TableToList(DataTable table) { List<HL7Msg> retVal=new List<HL7Msg>(); HL7Msg hL7Msg; for(int i=0;i<table.Rows.Count;i++) { hL7Msg=new HL7Msg(); hL7Msg.HL7MsgNum= PIn.Long (table.Rows[i]["HL7MsgNum"].ToString()); hL7Msg.HL7Status= (HL7MessageStatus)PIn.Int(table.Rows[i]["HL7Status"].ToString()); hL7Msg.MsgText = PIn.String(table.Rows[i]["MsgText"].ToString()); hL7Msg.AptNum = PIn.Long (table.Rows[i]["AptNum"].ToString()); retVal.Add(hL7Msg); } return retVal; }
///<summary>Sets given appt.AptStatus to broken. ///Provide procCode that should be charted, can be null but will not chart a broken procedure. ///Also considers various broken procedure based prefs. ///Makes its own securitylog entries.</summary> public static void BreakApptHelper(Appointment appt, Patient pat, ProcedureCode procCode) { //suppressHistory is true due to below logic creating a log with a specific HistAppointmentAction instead of the generic changed. DateTime datePrevious = appt.DateTStamp; bool suppressHistory = false; if (procCode != null) { suppressHistory = (procCode.ProcCode.In("D9986", "D9987")); } Appointments.SetAptStatus(appt, ApptStatus.Broken, suppressHistory); //Appointments S-Class handles Signalods if (appt.AptStatus != ApptStatus.Complete) //seperate log entry for completed appointments. { SecurityLogs.MakeLogEntry(Permissions.AppointmentEdit, pat.PatNum, appt.ProcDescript + ", " + appt.AptDateTime.ToString() + ", Broken from the Appts module.", appt.AptNum, datePrevious); } else { SecurityLogs.MakeLogEntry(Permissions.AppointmentCompleteEdit, pat.PatNum, appt.ProcDescript + ", " + appt.AptDateTime.ToString() + ", Broken from the Appts module.", appt.AptNum, datePrevious); } #region HL7 //If there is an existing HL7 def enabled, send a SIU message if there is an outbound SIU message defined if (HL7Defs.IsExistingHL7Enabled()) { //S15 - Appt Cancellation event MessageHL7 messageHL7 = MessageConstructor.GenerateSIU(pat, Patients.GetPat(pat.Guarantor), EventTypeHL7.S15, appt); //Will be null if there is no outbound SIU message defined, so do nothing if (messageHL7 != null) { HL7Msg hl7Msg = new HL7Msg(); hl7Msg.AptNum = appt.AptNum; hl7Msg.HL7Status = HL7MessageStatus.OutPending; //it will be marked outSent by the HL7 service. hl7Msg.MsgText = messageHL7.ToString(); hl7Msg.PatNum = pat.PatNum; HL7Msgs.Insert(hl7Msg); #if DEBUG MessageBox.Show("Appointments", messageHL7.ToString()); #endif } } #endregion #region Charting the proc if (procCode != null) { switch (procCode.ProcCode) { case "D9986": //Missed HistAppointments.CreateHistoryEntry(appt.AptNum, HistAppointmentAction.Missed); break; case "D9987": //Cancelled HistAppointments.CreateHistoryEntry(appt.AptNum, HistAppointmentAction.Cancelled); break; } Procedure procedureCur = new Procedure(); procedureCur.PatNum = pat.PatNum; procedureCur.ProvNum = (procCode.ProvNumDefault > 0 ? procCode.ProvNumDefault : appt.ProvNum); procedureCur.CodeNum = procCode.CodeNum; procedureCur.ProcDate = DateTime.Today; procedureCur.DateEntryC = DateTime.Now; procedureCur.ProcStatus = ProcStat.C; procedureCur.ClinicNum = appt.ClinicNum; procedureCur.UserNum = Security.CurUser.UserNum; procedureCur.Note = Lans.g("AppointmentEdit", "Appt BROKEN for") + " " + appt.ProcDescript + " " + appt.AptDateTime.ToString(); procedureCur.PlaceService = (PlaceOfService)PrefC.GetInt(PrefName.DefaultProcedurePlaceService); //Default proc place of service for the Practice is used. List <InsSub> listInsSubs = InsSubs.RefreshForFam(Patients.GetFamily(pat.PatNum)); List <InsPlan> listInsPlans = InsPlans.RefreshForSubList(listInsSubs); List <PatPlan> listPatPlans = PatPlans.Refresh(pat.PatNum); InsPlan insPlanPrimary = null; InsSub insSubPrimary = null; if (listPatPlans.Count > 0) { insSubPrimary = InsSubs.GetSub(listPatPlans[0].InsSubNum, listInsSubs); insPlanPrimary = InsPlans.GetPlan(insSubPrimary.PlanNum, listInsPlans); } double procFee; long feeSch; if (insPlanPrimary == null || procCode.NoBillIns) { feeSch = FeeScheds.GetFeeSched(0, pat.FeeSched, procedureCur.ProvNum); } else //Only take into account the patient's insurance fee schedule if the D9986 procedure is not marked as NoBillIns { feeSch = FeeScheds.GetFeeSched(insPlanPrimary.FeeSched, pat.FeeSched, procedureCur.ProvNum); } procFee = Fees.GetAmount0(procedureCur.CodeNum, feeSch, procedureCur.ClinicNum, procedureCur.ProvNum); if (insPlanPrimary != null && insPlanPrimary.PlanType == "p" && !insPlanPrimary.IsMedical) //PPO { double provFee = Fees.GetAmount0(procedureCur.CodeNum, Providers.GetProv(procedureCur.ProvNum).FeeSched, procedureCur.ClinicNum, procedureCur.ProvNum); procedureCur.ProcFee = Math.Max(provFee, procFee); } else { procedureCur.ProcFee = procFee; } if (!PrefC.GetBool(PrefName.EasyHidePublicHealth)) { procedureCur.SiteNum = pat.SiteNum; } Procedures.Insert(procedureCur); //Now make a claimproc if the patient has insurance. We do this now for consistency because a claimproc could get created in the future. List <Benefit> listBenefits = Benefits.Refresh(listPatPlans, listInsSubs); List <ClaimProc> listClaimProcsForProc = ClaimProcs.RefreshForProc(procedureCur.ProcNum); Procedures.ComputeEstimates(procedureCur, pat.PatNum, listClaimProcsForProc, false, listInsPlans, listPatPlans, listBenefits, pat.Age, listInsSubs); FormProcBroken FormPB = new FormProcBroken(procedureCur); FormPB.IsNew = true; FormPB.ShowDialog(); } #endregion #region BrokenApptAdjustment if (PrefC.GetBool(PrefName.BrokenApptAdjustment)) { Adjustment AdjustmentCur = new Adjustment(); AdjustmentCur.DateEntry = DateTime.Today; AdjustmentCur.AdjDate = DateTime.Today; AdjustmentCur.ProcDate = DateTime.Today; AdjustmentCur.ProvNum = appt.ProvNum; AdjustmentCur.PatNum = pat.PatNum; AdjustmentCur.AdjType = PrefC.GetLong(PrefName.BrokenAppointmentAdjustmentType); AdjustmentCur.ClinicNum = appt.ClinicNum; FormAdjust FormA = new FormAdjust(pat, AdjustmentCur); FormA.IsNew = true; FormA.ShowDialog(); } #endregion #region BrokenApptCommLog if (PrefC.GetBool(PrefName.BrokenApptCommLog)) { Commlog CommlogCur = new Commlog(); CommlogCur.PatNum = pat.PatNum; CommlogCur.CommDateTime = DateTime.Now; CommlogCur.CommType = Commlogs.GetTypeAuto(CommItemTypeAuto.APPT); CommlogCur.Note = Lan.g("Appointment", "Appt BROKEN for") + " " + appt.ProcDescript + " " + appt.AptDateTime.ToString(); CommlogCur.Mode_ = CommItemMode.None; CommlogCur.UserNum = Security.CurUser.UserNum; FormCommItem FormCI = new FormCommItem(); FormCI.ShowDialog(new CommItemModel() { CommlogCur = CommlogCur }, new CommItemController(FormCI) { IsNew = true }); } #endregion AutomationL.Trigger(AutomationTrigger.BreakAppointment, null, pat.PatNum); Recalls.SynchScheduledApptFull(appt.PatNum); }
///<summary>Updates one HL7Msg 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.</summary> internal static void Update(HL7Msg hL7Msg,HL7Msg oldHL7Msg) { string command=""; if(hL7Msg.HL7Status != oldHL7Msg.HL7Status) { if(command!=""){ command+=",";} command+="HL7Status = "+POut.Int ((int)hL7Msg.HL7Status)+""; } if(hL7Msg.MsgText != oldHL7Msg.MsgText) { if(command!=""){ command+=",";} command+="MsgText = "+DbHelper.ParamChar+"paramMsgText"; } if(hL7Msg.AptNum != oldHL7Msg.AptNum) { if(command!=""){ command+=",";} command+="AptNum = "+POut.Long(hL7Msg.AptNum)+""; } if(command==""){ return; } if(hL7Msg.MsgText==null) { hL7Msg.MsgText=""; } OdSqlParameter paramMsgText=new OdSqlParameter("paramMsgText",OdDbType.Text,hL7Msg.MsgText); command="UPDATE hl7msg SET "+command +" WHERE HL7MsgNum = "+POut.Long(hL7Msg.HL7MsgNum); Db.NonQ(command,paramMsgText); }
///<summary>Updates one HL7Msg 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(HL7Msg hL7Msg,HL7Msg oldHL7Msg){ string command=""; if(hL7Msg.HL7Status != oldHL7Msg.HL7Status) { if(command!=""){ command+=",";} command+="HL7Status = "+POut.Int ((int)hL7Msg.HL7Status)+""; } if(hL7Msg.MsgText != oldHL7Msg.MsgText) { if(command!=""){ command+=",";} command+="MsgText = "+DbHelper.ParamChar+"paramMsgText"; } if(hL7Msg.AptNum != oldHL7Msg.AptNum) { if(command!=""){ command+=",";} command+="AptNum = "+POut.Long(hL7Msg.AptNum)+""; } //DateTStamp can only be set by MySQL if(hL7Msg.PatNum != oldHL7Msg.PatNum) { if(command!=""){ command+=",";} command+="PatNum = "+POut.Long(hL7Msg.PatNum)+""; } if(hL7Msg.Note != oldHL7Msg.Note) { if(command!=""){ command+=",";} command+="Note = '"+POut.String(hL7Msg.Note)+"'"; } if(command==""){ return false; } if(hL7Msg.MsgText==null) { hL7Msg.MsgText=""; } OdSqlParameter paramMsgText=new OdSqlParameter("paramMsgText",OdDbType.Text,hL7Msg.MsgText); command="UPDATE hl7msg SET "+command +" WHERE HL7MsgNum = "+POut.Long(hL7Msg.HL7MsgNum); Db.NonQ(command,paramMsgText); return true; }
///<summary>Creates a single recall appointment. If it's from a double click, then it will end up on that spot in the Appts module. If not, it will end up on the pinboard with StringDateJumpTo as due date to jump to. ListAptNumsSelected will contain the AptNum of the new appointment.</summary> public void MakeRecallAppointment() { List <InsSub> listInsSubs = InsSubs.RefreshForFam(_famCur); List <InsPlan> listInsPlans = InsPlans.RefreshForSubList(listInsSubs); Appointment apt = null; DateTime dateTimeApt = DateTime.MinValue; if (this.IsInitialDoubleClick) { dateTimeApt = DateTimeClicked; } try{ apt = AppointmentL.CreateRecallApt(_patCur, listInsPlans, -1, listInsSubs, dateTimeApt); } catch (Exception ex) { MessageBox.Show(ex.Message); return; } DateTime datePrevious = apt.DateTStamp; ListAptNumsSelected.Add(apt.AptNum); if (IsInitialDoubleClick) { Appointment oldApt = apt.Copy(); if (_patCur.AskToArriveEarly > 0) { apt.DateTimeAskedToArrive = apt.AptDateTime.AddMinutes(-_patCur.AskToArriveEarly); MessageBox.Show(Lan.g(this, "Ask patient to arrive") + " " + _patCur.AskToArriveEarly + " " + Lan.g(this, "minutes early at") + " " + apt.DateTimeAskedToArrive.ToShortTimeString() + "."); } apt.AptStatus = ApptStatus.Scheduled; apt.ClinicNum = _patCur.ClinicNum; apt.Op = OpNumClicked; apt = Appointments.AssignFieldsForOperatory(apt); //Use apt.ClinicNum because it was just set based on Op.ClinicNum in AssignFieldsForOperatory(). if (!AppointmentL.IsSpecialtyMismatchAllowed(_patCur.PatNum, apt.ClinicNum)) { return; } Appointments.Update(apt, oldApt); _otherResult = OtherResult.CreateNew; SecurityLogs.MakeLogEntry(Permissions.AppointmentCreate, apt.PatNum, apt.AptDateTime.ToString(), apt.AptNum, datePrevious); //If there is an existing HL7 def enabled, send a SIU message if there is an outbound SIU message defined if (HL7Defs.IsExistingHL7Enabled()) { //S12 - New Appt Booking event MessageHL7 messageHL7 = MessageConstructor.GenerateSIU(_patCur, _famCur.GetPatient(_patCur.Guarantor), EventTypeHL7.S12, apt); //Will be null if there is no outbound SIU message defined, so do nothing if (messageHL7 != null) { HL7Msg hl7Msg = new HL7Msg(); hl7Msg.AptNum = apt.AptNum; hl7Msg.HL7Status = HL7MessageStatus.OutPending; //it will be marked outSent by the HL7 service. hl7Msg.MsgText = messageHL7.ToString(); hl7Msg.PatNum = _patCur.PatNum; HL7Msgs.Insert(hl7Msg); #if DEBUG MessageBox.Show(this, messageHL7.ToString()); #endif } } DialogResult = DialogResult.OK; return; } //not initialClick _otherResult = OtherResult.PinboardAndSearch; Recall recall = Recalls.GetRecallProphyOrPerio(_patCur.PatNum); //shouldn't return null. if (recall.DateDue < DateTime.Today) { StringDateJumpTo = DateTime.Today.ToShortDateString(); //they are overdue } else { StringDateJumpTo = recall.DateDue.ToShortDateString(); } //no securitylog entry needed here. That will happen when it's dragged off pinboard. DialogResult = DialogResult.OK; }
//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> hl7Existing=HL7Msgs.GetOneExisting(HL7MsgCur); if(hl7Existing.Count>0) {//This message is already in the db HL7MsgCur.HL7MsgNum=hl7Existing[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."); } HL7DefMessage hl7defmsg=null; for(int i=0;i<def.hl7DefMessages.Count;i++) { if(def.hl7DefMessages[i].MessageType==msg.MsgType) {//&& def.hl7DefMessages[i].EventType==msg.EventType) { //Ignoring event type for now, we will treat all ADT's and SIU's the same hl7defmsg=def.hl7DefMessages[i]; } } if(hl7defmsg==null) {//No message definition matches this message's MessageType and EventType 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; DateTime birthdate=DateTime.MinValue; string patLName=null; string patFName=null; bool isExistingPID=false;//Needed to add note to hl7msg table if there isn't a PID segment in the message. //Get patient in question, incoming messages must have a PID segment so use that to find the pat in question for(int s=0;s<hl7defmsg.hl7DefSegments.Count;s++) { if(hl7defmsg.hl7DefSegments[s].SegmentName!=SegmentNameHL7.PID) { continue; } int pidOrder=hl7defmsg.hl7DefSegments[s].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 ) { break; } isExistingPID=true; for(int f=0;f<hl7defmsg.hl7DefSegments[s].hl7DefFields.Count;f++) {//Go through fields of PID segment and get patnum, chartnum, patient name, and/or birthdate to locate patient if(hl7defmsg.hl7DefSegments[s].hl7DefFields[f].FieldName=="pat.ChartNumber") { int chartNumOrdinal=hl7defmsg.hl7DefSegments[s].hl7DefFields[f].OrdinalPos; chartNum=msg.Segments[pidOrder].Fields[chartNumOrdinal].ToString(); } else if(hl7defmsg.hl7DefSegments[s].hl7DefFields[f].FieldName=="pat.PatNum") { int patNumOrdinal=hl7defmsg.hl7DefSegments[s].hl7DefFields[f].OrdinalPos; patNum=PIn.Long(msg.Segments[pidOrder].Fields[patNumOrdinal].ToString()); } else if(hl7defmsg.hl7DefSegments[s].hl7DefFields[f].FieldName=="pat.birthdateTime") { int patBdayOrdinal=hl7defmsg.hl7DefSegments[s].hl7DefFields[f].OrdinalPos; birthdate=FieldParser.DateTimeParse(msg.Segments[pidOrder].Fields[patBdayOrdinal].ToString()); } else if(hl7defmsg.hl7DefSegments[s].hl7DefFields[f].FieldName=="pat.nameLFM") { int patNameOrdinal=hl7defmsg.hl7DefSegments[s].hl7DefFields[f].OrdinalPos; patLName=msg.Segments[pidOrder].GetFieldComponent(patNameOrdinal,0); patFName=msg.Segments[pidOrder].GetFieldComponent(patNameOrdinal,1); } } } if(!isExistingPID) { 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."); } //We now have patnum, chartnum, patname, and/or birthdate so locate pat Patient pat=null; Patient patOld=null; if(patNum!=0) { pat=Patients.GetPat(patNum); } if(def.InternalType!="eCWStandalone" && pat==null) { IsNewPat=true; } //In eCWstandalone integration, if we couldn't locate patient by patNum or patNum was 0 then pat will still be null so try to locate by chartNum if chartNum is not null if(def.InternalType=="eCWStandalone" && chartNum!=null) { pat=Patients.GetPatByChartNumber(chartNum); } //In eCWstandalone integration, if pat is still null we need to try to locate patient by name and birthdate if(def.InternalType=="eCWStandalone" && pat==null) { long patNumByName=Patients.GetPatNumByNameAndBirthday(patLName,patFName,birthdate); //If patNumByName is 0 we couldn't locate by patNum, chartNum or name and birthdate so this message must be for a new patient if(patNumByName==0) { IsNewPat=true; } else { pat=Patients.GetPat(patNumByName); patOld=pat.Copy(); pat.ChartNumber=chartNum;//from now on, we will be able to find pat by chartNumber Patients.Update(pat,patOld); } } if(IsNewPat) { pat=new Patient(); if(chartNum!=null) { pat.ChartNumber=chartNum; } if(patNum!=0) { pat.PatNum=patNum; pat.Guarantor=patNum; } pat.PriProv=PrefC.GetLong(PrefName.PracticeDefaultProv); pat.BillingType=PrefC.GetLong(PrefName.PracticeDefaultBillType); } else { patOld=pat.Copy(); } //Update hl7msg table with correct PatNum for this message HL7MsgCur.PatNum=pat.PatNum; HL7Msgs.Update(HL7MsgCur); //If this is a message that contains an SCH segment, loop through to find the AptNum. Pass it to the other segments that will need it. long aptNum=0; for(int s=0;s<hl7defmsg.hl7DefSegments.Count;s++) { if(hl7defmsg.hl7DefSegments[s].SegmentName!=SegmentNameHL7.SCH) { continue; } //we found the SCH segment int schOrder=hl7defmsg.hl7DefSegments[s].ItemOrder; for(int f=0;f<hl7defmsg.hl7DefSegments[s].hl7DefFields.Count;f++) {//Go through fields of SCH segment and get AptNum if(hl7defmsg.hl7DefSegments[s].hl7DefFields[f].FieldName=="apt.AptNum") { int aptNumOrdinal=hl7defmsg.hl7DefSegments[s].hl7DefFields[f].OrdinalPos; aptNum=PIn.Long(msg.Segments[schOrder].Fields[aptNumOrdinal].ToString()); } } } //We now have a patient object , either loaded from the db or new, and aptNum 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(isVerboseLogging) { EventLog.WriteEntry("OpenDentHL7","Inserted patient: "+pat.FName+" "+pat.LName,EventLogEntryType.Information); } if(pat.PatNum==0) { pat.PatNum=Patients.Insert(pat,false); } else { pat.PatNum=Patients.Insert(pat,true); } patOld=pat.Copy(); } for(int i=0;i<hl7defmsg.hl7DefSegments.Count;i++) { try { SegmentHL7 seg=msg.GetSegment(hl7defmsg.hl7DefSegments[i].SegmentName,!hl7defmsg.hl7DefSegments[i].IsOptional); if(seg!=null) {//null if segment was not found but is optional ProcessSeg(pat,aptNum,hl7defmsg.hl7DefSegments[i],seg,msg); } } catch(ApplicationException ex) {//Required segment was missing, or other error. HL7MsgCur.Note="Could not process this HL7 message. "+ex; HL7Msgs.Update(HL7MsgCur); throw new Exception("Could not process HL7 message. "+ex); } } //We have processed the message so now update or insert 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); return; } if(IsNewPat) { if(pat.Guarantor==0) { pat.Guarantor=pat.PatNum; Patients.Update(pat,patOld); } else { Patients.Update(pat,patOld); } } else { if(isVerboseLogging) { EventLog.WriteEntry("OpenDentHL7","Updated patient: "+pat.FName+" "+pat.LName,EventLogEntryType.Information); } Patients.Update(pat,patOld); } HL7MsgCur.HL7Status=HL7MessageStatus.InProcessed; HL7Msgs.Update(HL7MsgCur); }
///<summary>Inserts one HL7Msg into the database. Returns the new priKey. Doesn't use the cache.</summary> public static long InsertNoCache(HL7Msg hL7Msg) { return(InsertNoCache(hL7Msg, false)); }
/// <summary> /// </summary> private Patient CreatePatient(String LastName, String FirstName, DateTime birthDate, WebSheets.SheetAndSheetField sAnds) { Patient newPat = new Patient(); newPat.LName = LastName; newPat.FName = FirstName; newPat.Birthdate = birthDate; newPat.ClinicNum = sAnds.web_sheet.ClinicNum; if (PrefC.GetBool(PrefName.EasyNoClinics)) { //Set the patients primary provider to the practice default provider. newPat.PriProv = Providers.GetDefaultProvider().ProvNum; } else //Using clinics. //Set the patients primary provider to the clinic default provider. { newPat.PriProv = Providers.GetDefaultProvider(Clinics.ClinicNum).ProvNum; } Type t = newPat.GetType(); FieldInfo[] fi = t.GetFields(); foreach (FieldInfo field in fi) { // find match for fields in Patients in the web_sheetfieldlist var WebSheetFieldList = sAnds.web_sheetfieldlist.Where(sf => sf.FieldName.ToLower() == field.Name.ToLower()); if (WebSheetFieldList.Count() > 0) { // this loop is used to fill a field that may generate mutiple values for a single field in the patient. //for example the field gender has 2 eqivalent sheet fields in the web_sheetfieldlist for (int i = 0; i < WebSheetFieldList.Count(); i++) { WebSheets.webforms_sheetfield sf = WebSheetFieldList.ElementAt(i); String SheetWebFieldValue = sf.FieldValue; String RadioButtonValue = sf.RadioButtonValue; FillPatientFields(newPat, field, SheetWebFieldValue, RadioButtonValue); } } } try{ Patients.Insert(newPat, false); SecurityLogs.MakeLogEntry(Permissions.PatientCreate, newPat.PatNum, "Created from Web Forms."); //set Guarantor field the same as PatNum Patient patOld = newPat.Copy(); newPat.Guarantor = newPat.PatNum; Patients.Update(newPat, patOld); //If there is an existing HL7 def enabled, send an ADT message if there is an outbound ADT message defined if (HL7Defs.IsExistingHL7Enabled()) { MessageHL7 messageHL7 = MessageConstructor.GenerateADT(newPat, newPat, EventTypeHL7.A04); //patient is guarantor //Will be null if there is no outbound ADT message defined, so do nothing if (messageHL7 != null) { HL7Msg hl7Msg = new HL7Msg(); hl7Msg.AptNum = 0; hl7Msg.HL7Status = HL7MessageStatus.OutPending; //it will be marked outSent by the HL7 service. hl7Msg.MsgText = messageHL7.ToString(); hl7Msg.PatNum = newPat.PatNum; HL7Msgs.Insert(hl7Msg); #if DEBUG MessageBox.Show(this, messageHL7.ToString()); #endif } } } catch (Exception e) { gridMain.EndUpdate(); MessageBox.Show(e.Message); } return(newPat); }
/// <summary> private static Patient CreatePatient(String LastName, String FirstName, DateTime birthDate, WebForms_Sheet webFormSheet, Sheet sheet, string cultureName) { bool isWebForm = webFormSheet != null; Patient newPat = new Patient(); newPat.LName = LastName; newPat.FName = FirstName; newPat.Birthdate = birthDate; if (isWebForm) { newPat.ClinicNum = webFormSheet.ClinicNum; } else { newPat.ClinicNum = sheet.ClinicNum; } if (!PrefC.HasClinicsEnabled) { //Set the patients primary provider to the practice default provider. newPat.PriProv = Providers.GetDefaultProvider().ProvNum; } else //Using clinics. //Set the patients primary provider to the clinic default provider. { newPat.PriProv = Providers.GetDefaultProvider(Clinics.ClinicNum).ProvNum; } Type t = newPat.GetType(); FieldInfo[] fi = t.GetFields(); foreach (FieldInfo field in fi) { // find match for fields in Patients in the SheetFields if (isWebForm) { List <WebForms_SheetField> listWebFormsSheetFields = webFormSheet.SheetFields.FindAll(x => x.FieldName.ToLower() == field.Name.ToLower()); if (listWebFormsSheetFields.Count() > 0) { // this loop is used to fill a field that may generate mutiple values for a single field in the patient. //for example the field gender has 2 eqivalent sheet fields in the SheetFields foreach (WebForms_SheetField webFormsSheetField in listWebFormsSheetFields) { FillPatientFields(newPat, field, webFormsSheetField.FieldValue, webFormsSheetField.RadioButtonValue, cultureName, isWebForm, false); } } } else { List <SheetField> listSheetFields = sheet.SheetFields.FindAll(x => x.FieldName.ToLower() == field.Name.ToLower()); if (listSheetFields.Count() > 0) { // this loop is used to fill a field that may generate mutiple values for a single field in the patient. //for example the field gender has 2 eqivalent sheet fields in the SheetFields foreach (SheetField sheetField in listSheetFields) { FillPatientFields(newPat, field, sheetField.FieldValue, sheetField.RadioButtonValue, "", isWebForm, sheet.IsCemtTransfer); } } } } try { Patients.Insert(newPat, false); SecurityLogs.MakeLogEntry(Permissions.PatientCreate, newPat.PatNum, isWebForm ? "Created from Web Forms." : "Created from CEMT transfer."); //set Guarantor field the same as PatNum Patient patOld = newPat.Copy(); newPat.Guarantor = newPat.PatNum; Patients.Update(newPat, patOld); //If there is an existing HL7 def enabled, send an ADT message if there is an outbound ADT message defined if (HL7Defs.IsExistingHL7Enabled()) { MessageHL7 messageHL7 = MessageConstructor.GenerateADT(newPat, newPat, EventTypeHL7.A04); //patient is guarantor //Will be null if there is no outbound ADT message defined, so do nothing if (messageHL7 != null) { HL7Msg hl7Msg = new HL7Msg(); hl7Msg.AptNum = 0; hl7Msg.HL7Status = HL7MessageStatus.OutPending; //it will be marked outSent by the HL7 service. hl7Msg.MsgText = messageHL7.ToString(); hl7Msg.PatNum = newPat.PatNum; HL7Msgs.Insert(hl7Msg); #if DEBUG MessageBox.Show("FormWebForms", messageHL7.ToString()); #endif } } } catch (Exception e) { MessageBox.Show(e.Message); } return(newPat); }
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); } }
//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> hl7Existing = HL7Msgs.GetOneExisting(HL7MsgCur); if (hl7Existing.Count > 0) //This message is already in the db { HL7MsgCur.HL7MsgNum = hl7Existing[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."); } HL7DefMessage hl7defmsg = null; for (int i = 0; i < def.hl7DefMessages.Count; i++) { if (def.hl7DefMessages[i].MessageType == msg.MsgType) //&& def.hl7DefMessages[i].EventType==msg.EventType) { //Ignoring event type for now, we will treat all ADT's and SIU's the same { hl7defmsg = def.hl7DefMessages[i]; } } if (hl7defmsg == null) //No message definition matches this message's MessageType and EventType { 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; DateTime birthdate = DateTime.MinValue; string patLName = null; string patFName = null; bool isExistingPID = false; //Needed to add note to hl7msg table if there isn't a PID segment in the message. //Get patient in question, incoming messages must have a PID segment so use that to find the pat in question for (int s = 0; s < hl7defmsg.hl7DefSegments.Count; s++) { if (hl7defmsg.hl7DefSegments[s].SegmentName != SegmentNameHL7.PID) { continue; } int pidOrder = hl7defmsg.hl7DefSegments[s].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 ) { break; } isExistingPID = true; for (int f = 0; f < hl7defmsg.hl7DefSegments[s].hl7DefFields.Count; f++) //Go through fields of PID segment and get patnum, chartnum, patient name, and/or birthdate to locate patient { if (hl7defmsg.hl7DefSegments[s].hl7DefFields[f].FieldName == "pat.ChartNumber") { int chartNumOrdinal = hl7defmsg.hl7DefSegments[s].hl7DefFields[f].OrdinalPos; chartNum = msg.Segments[pidOrder].Fields[chartNumOrdinal].ToString(); } else if (hl7defmsg.hl7DefSegments[s].hl7DefFields[f].FieldName == "pat.PatNum") { int patNumOrdinal = hl7defmsg.hl7DefSegments[s].hl7DefFields[f].OrdinalPos; patNum = PIn.Long(msg.Segments[pidOrder].Fields[patNumOrdinal].ToString()); } else if (hl7defmsg.hl7DefSegments[s].hl7DefFields[f].FieldName == "pat.birthdateTime") { int patBdayOrdinal = hl7defmsg.hl7DefSegments[s].hl7DefFields[f].OrdinalPos; birthdate = FieldParser.DateTimeParse(msg.Segments[pidOrder].Fields[patBdayOrdinal].ToString()); } else if (hl7defmsg.hl7DefSegments[s].hl7DefFields[f].FieldName == "pat.nameLFM") { int patNameOrdinal = hl7defmsg.hl7DefSegments[s].hl7DefFields[f].OrdinalPos; patLName = msg.Segments[pidOrder].GetFieldComponent(patNameOrdinal, 0); patFName = msg.Segments[pidOrder].GetFieldComponent(patNameOrdinal, 1); } } } if (!isExistingPID) { 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."); } //We now have patnum, chartnum, patname, and/or birthdate so locate pat Patient pat = null; Patient patOld = null; if (patNum != 0) { pat = Patients.GetPat(patNum); } if (def.InternalType != "eCWStandalone" && pat == null) { IsNewPat = true; } //In eCWstandalone integration, if we couldn't locate patient by patNum or patNum was 0 then pat will still be null so try to locate by chartNum if chartNum is not null if (def.InternalType == "eCWStandalone" && chartNum != null) { pat = Patients.GetPatByChartNumber(chartNum); } //In eCWstandalone integration, if pat is still null we need to try to locate patient by name and birthdate if (def.InternalType == "eCWStandalone" && pat == null) { long patNumByName = Patients.GetPatNumByNameAndBirthday(patLName, patFName, birthdate); //If patNumByName is 0 we couldn't locate by patNum, chartNum or name and birthdate so this message must be for a new patient if (patNumByName == 0) { IsNewPat = true; } else { pat = Patients.GetPat(patNumByName); patOld = pat.Copy(); pat.ChartNumber = chartNum; //from now on, we will be able to find pat by chartNumber Patients.Update(pat, patOld); } } if (IsNewPat) { pat = new Patient(); if (chartNum != null) { pat.ChartNumber = chartNum; } if (patNum != 0) { pat.PatNum = patNum; pat.Guarantor = patNum; } pat.PriProv = PrefC.GetLong(PrefName.PracticeDefaultProv); pat.BillingType = PrefC.GetLong(PrefName.PracticeDefaultBillType); } else { patOld = pat.Copy(); } //Update hl7msg table with correct PatNum for this message HL7MsgCur.PatNum = pat.PatNum; HL7Msgs.Update(HL7MsgCur); //If this is a message that contains an SCH segment, loop through to find the AptNum. Pass it to the other segments that will need it. long aptNum = 0; for (int s = 0; s < hl7defmsg.hl7DefSegments.Count; s++) { if (hl7defmsg.hl7DefSegments[s].SegmentName != SegmentNameHL7.SCH) { continue; } //we found the SCH segment int schOrder = hl7defmsg.hl7DefSegments[s].ItemOrder; for (int f = 0; f < hl7defmsg.hl7DefSegments[s].hl7DefFields.Count; f++) //Go through fields of SCH segment and get AptNum { if (hl7defmsg.hl7DefSegments[s].hl7DefFields[f].FieldName == "apt.AptNum") { int aptNumOrdinal = hl7defmsg.hl7DefSegments[s].hl7DefFields[f].OrdinalPos; aptNum = PIn.Long(msg.Segments[schOrder].Fields[aptNumOrdinal].ToString()); } } } //We now have a patient object , either loaded from the db or new, and aptNum 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 (isVerboseLogging) { EventLog.WriteEntry("OpenDentHL7", "Inserted patient: " + pat.FName + " " + pat.LName, EventLogEntryType.Information); } if (pat.PatNum == 0) { pat.PatNum = Patients.Insert(pat, false); } else { pat.PatNum = Patients.Insert(pat, true); } patOld = pat.Copy(); } for (int i = 0; i < hl7defmsg.hl7DefSegments.Count; i++) { try { SegmentHL7 seg = msg.GetSegment(hl7defmsg.hl7DefSegments[i].SegmentName, !hl7defmsg.hl7DefSegments[i].IsOptional); if (seg != null) //null if segment was not found but is optional { ProcessSeg(pat, aptNum, hl7defmsg.hl7DefSegments[i], seg, msg); } } catch (ApplicationException ex) { //Required segment was missing, or other error. HL7MsgCur.Note = "Could not process this HL7 message. " + ex; HL7Msgs.Update(HL7MsgCur); throw new Exception("Could not process HL7 message. " + ex); } } //We have processed the message so now update or insert 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); return; } if (IsNewPat) { if (pat.Guarantor == 0) { pat.Guarantor = pat.PatNum; Patients.Update(pat, patOld); } else { Patients.Update(pat, patOld); } } else { if (isVerboseLogging) { EventLog.WriteEntry("OpenDentHL7", "Updated patient: " + pat.FName + " " + pat.LName, EventLogEntryType.Information); } Patients.Update(pat, patOld); } HL7MsgCur.HL7Status = HL7MessageStatus.InProcessed; HL7Msgs.Update(HL7MsgCur); }
///<summary>Updates one HL7Msg in the database.</summary> public static void Update(HL7Msg hL7Msg){ string command="UPDATE hl7msg SET " +"HL7Status = "+POut.Int ((int)hL7Msg.HL7Status)+", " +"MsgText = "+DbHelper.ParamChar+"paramMsgText, " +"AptNum = "+POut.Long (hL7Msg.AptNum)+", " //DateTStamp can only be set by MySQL +"PatNum = "+POut.Long (hL7Msg.PatNum)+", " +"Note = '"+POut.String(hL7Msg.Note)+"' " +"WHERE HL7MsgNum = "+POut.Long(hL7Msg.HL7MsgNum); if(hL7Msg.MsgText==null) { hL7Msg.MsgText=""; } OdSqlParameter paramMsgText=new OdSqlParameter("paramMsgText",OdDbType.Text,hL7Msg.MsgText); Db.NonQ(command,paramMsgText); }
///<summary>Updates one HL7Msg in the database.</summary> internal static void Update(HL7Msg hL7Msg) { string command="UPDATE hl7msg SET " +"HL7Status= "+POut.Int ((int)hL7Msg.HL7Status)+", " +"MsgText = "+DbHelper.ParamChar+"paramMsgText, " +"AptNum = "+POut.Long (hL7Msg.AptNum)+" " +"WHERE HL7MsgNum = "+POut.Long(hL7Msg.HL7MsgNum); if(hL7Msg.MsgText==null) { hL7Msg.MsgText=""; } OdSqlParameter paramMsgText=new OdSqlParameter("paramMsgText",OdDbType.Text,hL7Msg.MsgText); Db.NonQ(command,paramMsgText); }
///<summary>Inserts one HL7Msg into the database. Returns the new priKey.</summary> public static long Insert(HL7Msg hL7Msg) { return(Insert(hL7Msg, false)); }
///<summary>Sets given appt.AptStatus to broken. ///Provide procCode that should be charted, can be null but will not chart a broken procedure. ///Also considers various broken procedure based prefs. ///Makes its own securitylog entries.</summary> public static void BreakApptHelper(Appointment appt, Patient pat, ProcedureCode procCode) { //suppressHistory is true due to below logic creating a log with a specific HistAppointmentAction instead of the generic changed. DateTime datePrevious = appt.DateTStamp; bool suppressHistory = false; if (procCode != null) { suppressHistory = (procCode.ProcCode.In("D9986", "D9987")); } Appointments.SetAptStatus(appt, ApptStatus.Broken, suppressHistory); //Appointments S-Class handles Signalods if (appt.AptStatus != ApptStatus.Complete) //seperate log entry for completed appointments. { SecurityLogs.MakeLogEntry(Permissions.AppointmentEdit, pat.PatNum, appt.ProcDescript + ", " + appt.AptDateTime.ToString() + ", Broken from the Appts module.", appt.AptNum, datePrevious); } else { SecurityLogs.MakeLogEntry(Permissions.AppointmentCompleteEdit, pat.PatNum, appt.ProcDescript + ", " + appt.AptDateTime.ToString() + ", Broken from the Appts module.", appt.AptNum, datePrevious); } #region HL7 //If there is an existing HL7 def enabled, send a SIU message if there is an outbound SIU message defined if (HL7Defs.IsExistingHL7Enabled()) { //S15 - Appt Cancellation event MessageHL7 messageHL7 = MessageConstructor.GenerateSIU(pat, Patients.GetPat(pat.Guarantor), EventTypeHL7.S15, appt); //Will be null if there is no outbound SIU message defined, so do nothing if (messageHL7 != null) { HL7Msg hl7Msg = new HL7Msg(); hl7Msg.AptNum = appt.AptNum; hl7Msg.HL7Status = HL7MessageStatus.OutPending; //it will be marked outSent by the HL7 service. hl7Msg.MsgText = messageHL7.ToString(); hl7Msg.PatNum = pat.PatNum; HL7Msgs.Insert(hl7Msg); #if DEBUG MessageBox.Show("Appointments", messageHL7.ToString()); #endif } } #endregion List <Procedure> listProcedures = new List <Procedure>(); //splits should only exist on procs if they are using tp pre-payments List <PaySplit> listSplitsForApptProcs = new List <PaySplit>(); bool isNonRefundable = false; double brokenProcAmount = 0; Procedure brokenProcedure = new Procedure(); bool wasBrokenProcDeleted = false; if (PrefC.GetYN(PrefName.PrePayAllowedForTpProcs)) { listProcedures = Procedures.GetProcsForSingle(appt.AptNum, false); if (listProcedures.Count > 0) { listSplitsForApptProcs = PaySplits.GetPaySplitsFromProcs(listProcedures.Select(x => x.ProcNum).ToList()); } } #region Charting the proc if (procCode != null) { switch (procCode.ProcCode) { case "D9986": //Missed HistAppointments.CreateHistoryEntry(appt.AptNum, HistAppointmentAction.Missed); break; case "D9987": //Cancelled HistAppointments.CreateHistoryEntry(appt.AptNum, HistAppointmentAction.Cancelled); break; } brokenProcedure.PatNum = pat.PatNum; brokenProcedure.ProvNum = (procCode.ProvNumDefault > 0 ? procCode.ProvNumDefault : appt.ProvNum); brokenProcedure.CodeNum = procCode.CodeNum; brokenProcedure.ProcDate = DateTime.Today; brokenProcedure.DateEntryC = DateTime.Now; brokenProcedure.ProcStatus = ProcStat.C; brokenProcedure.ClinicNum = appt.ClinicNum; brokenProcedure.UserNum = Security.CurUser.UserNum; brokenProcedure.Note = Lans.g("AppointmentEdit", "Appt BROKEN for") + " " + appt.ProcDescript + " " + appt.AptDateTime.ToString(); brokenProcedure.PlaceService = (PlaceOfService)PrefC.GetInt(PrefName.DefaultProcedurePlaceService); //Default proc place of service for the Practice is used. List <InsSub> listInsSubs = InsSubs.RefreshForFam(Patients.GetFamily(pat.PatNum)); List <InsPlan> listInsPlans = InsPlans.RefreshForSubList(listInsSubs); List <PatPlan> listPatPlans = PatPlans.Refresh(pat.PatNum); InsPlan insPlanPrimary = null; InsSub insSubPrimary = null; if (listPatPlans.Count > 0) { insSubPrimary = InsSubs.GetSub(listPatPlans[0].InsSubNum, listInsSubs); insPlanPrimary = InsPlans.GetPlan(insSubPrimary.PlanNum, listInsPlans); } double procFee; long feeSch; if (insPlanPrimary == null || procCode.NoBillIns) { feeSch = FeeScheds.GetFeeSched(0, pat.FeeSched, brokenProcedure.ProvNum); } else //Only take into account the patient's insurance fee schedule if the D9986 procedure is not marked as NoBillIns { feeSch = FeeScheds.GetFeeSched(insPlanPrimary.FeeSched, pat.FeeSched, brokenProcedure.ProvNum); } procFee = Fees.GetAmount0(brokenProcedure.CodeNum, feeSch, brokenProcedure.ClinicNum, brokenProcedure.ProvNum); if (insPlanPrimary != null && insPlanPrimary.PlanType == "p" && !insPlanPrimary.IsMedical) //PPO { double provFee = Fees.GetAmount0(brokenProcedure.CodeNum, Providers.GetProv(brokenProcedure.ProvNum).FeeSched, brokenProcedure.ClinicNum, brokenProcedure.ProvNum); brokenProcedure.ProcFee = Math.Max(provFee, procFee); } else if (listSplitsForApptProcs.Count > 0 && PrefC.GetBool(PrefName.TpPrePayIsNonRefundable) && procCode.ProcCode == "D9986") { //if there are pre-payments, non-refundable pre-payments is turned on, and the broken appointment is a missed code then auto-fill //the window with the sum of the procs for the appointment. Transfer money below after broken procedure is confirmed by the user. brokenProcedure.ProcFee = listSplitsForApptProcs.Sum(x => x.SplitAmt); isNonRefundable = true; } else { brokenProcedure.ProcFee = procFee; } if (!PrefC.GetBool(PrefName.EasyHidePublicHealth)) { brokenProcedure.SiteNum = pat.SiteNum; } Procedures.Insert(brokenProcedure); //Now make a claimproc if the patient has insurance. We do this now for consistency because a claimproc could get created in the future. List <Benefit> listBenefits = Benefits.Refresh(listPatPlans, listInsSubs); List <ClaimProc> listClaimProcsForProc = ClaimProcs.RefreshForProc(brokenProcedure.ProcNum); Procedures.ComputeEstimates(brokenProcedure, pat.PatNum, listClaimProcsForProc, false, listInsPlans, listPatPlans, listBenefits, pat.Age, listInsSubs); FormProcBroken FormPB = new FormProcBroken(brokenProcedure, isNonRefundable); FormPB.IsNew = true; FormPB.ShowDialog(); brokenProcAmount = FormPB.AmountTotal; wasBrokenProcDeleted = FormPB.IsProcDeleted; } #endregion #region BrokenApptAdjustment if (PrefC.GetBool(PrefName.BrokenApptAdjustment)) { Adjustment AdjustmentCur = new Adjustment(); AdjustmentCur.DateEntry = DateTime.Today; AdjustmentCur.AdjDate = DateTime.Today; AdjustmentCur.ProcDate = DateTime.Today; AdjustmentCur.ProvNum = appt.ProvNum; AdjustmentCur.PatNum = pat.PatNum; AdjustmentCur.AdjType = PrefC.GetLong(PrefName.BrokenAppointmentAdjustmentType); AdjustmentCur.ClinicNum = appt.ClinicNum; FormAdjust FormA = new FormAdjust(pat, AdjustmentCur); FormA.IsNew = true; FormA.ShowDialog(); } #endregion #region BrokenApptCommLog if (PrefC.GetBool(PrefName.BrokenApptCommLog)) { Commlog commlogCur = new Commlog(); commlogCur.PatNum = pat.PatNum; commlogCur.CommDateTime = DateTime.Now; commlogCur.CommType = Commlogs.GetTypeAuto(CommItemTypeAuto.APPT); commlogCur.Note = Lan.g("Appointment", "Appt BROKEN for") + " " + appt.ProcDescript + " " + appt.AptDateTime.ToString(); commlogCur.Mode_ = CommItemMode.None; commlogCur.UserNum = Security.CurUser.UserNum; commlogCur.IsNew = true; FormCommItem FormCI = new FormCommItem(commlogCur); FormCI.ShowDialog(); } #endregion #region Transfer money from TP Procedures if necessary //Note this MUST come after FormProcBroken since clicking cancel in that window will delete the procedure. if (isNonRefundable && !wasBrokenProcDeleted && listSplitsForApptProcs.Count > 0) { //transfer what the user specified in the broken appointment window. //transfer up to the amount specified by the user foreach (Procedure proc in listProcedures) { if (brokenProcAmount == 0) { break; } List <PaySplit> listSplitsForAppointmentProcedure = listSplitsForApptProcs.FindAll(x => x.ProcNum == proc.ProcNum); foreach (PaySplit split in listSplitsForAppointmentProcedure) { if (brokenProcAmount == 0) { break; } double amt = Math.Min(brokenProcAmount, split.SplitAmt); Payments.CreateTransferForTpProcs(proc, new List <PaySplit> { split }, brokenProcedure, amt); double amtPaidOnApt = listSplitsForApptProcs.Sum(x => x.SplitAmt); if (amtPaidOnApt > amt) { //If the original prepayment amount is greater than the amt being specified for the appointment break, transfer //the difference to an Unallocated Unearned Paysplit on the account. double remainingAmt = amtPaidOnApt - amt; //We have to create a new transfer payment here to correlate to the split. Payment txfrPayment = new Payment(); txfrPayment.PayAmt = 0; txfrPayment.PayDate = DateTime.Today; txfrPayment.ClinicNum = split.ClinicNum; txfrPayment.PayNote = "Automatic transfer from treatment planned procedure prepayment."; txfrPayment.PatNum = split.PatNum; //ultimately where the payment ends up. txfrPayment.PayType = 0; Payments.Insert(txfrPayment); PaymentEdit.IncomeTransferData transferData = PaymentEdit.IncomeTransferData.CreateTransfer(split, txfrPayment.PayNum, true, remainingAmt); PaySplit offset = transferData.ListSplitsCur.FirstOrDefault(x => x.FSplitNum != 0); long offsetSplitNum = PaySplits.Insert(offset); //Get the FSplitNum from the offset PaySplit allocation = transferData.ListSplitsCur.FirstOrDefault(x => x.FSplitNum == 0); allocation.FSplitNum = offsetSplitNum; PaySplits.Insert(allocation); //Insert so the split is now up to date SecurityLogs.MakeLogEntry(Permissions.PaymentCreate, txfrPayment.PatNum, "Automatic transfer of funds for treatment plan procedure pre-payments."); } brokenProcAmount -= amt; } } } //if broken appointment procedure was deleted (user cancelled out of the window) just keep money on the original procedure. #endregion AppointmentEvent.Fire(ODEventType.AppointmentEdited, appt); AutomationL.Trigger(AutomationTrigger.BreakAppointment, null, pat.PatNum); Recalls.SynchScheduledApptFull(appt.PatNum); }