using System; using System.Collections.Generic; using System.Linq; using System.Text; using Sdaleo; using Sdaleo.Systems.SQLServer; using Pluto.Api; using System.Data; using Sdaleo.Systems; using Yaulw.Win32; using CrossProduct.Core; using System.Reflection; using Yaulw.Tools; namespace PlutoServer.MSL.Connectors { public static class LytecConnector { internal static string MAKE_SURE_APOSTROPHY_IS_SQL_READY(string str) { if (!String.IsNullOrEmpty(str)) { str = str.Replace("'", "''"); return str; } return String.Empty; } internal static bool IS_VALID_FOUR_DIGIT_PIN(string fourdigitpin) { if (!String.IsNullOrEmpty(fourdigitpin)) { fourdigitpin = fourdigitpin.Trim(); if (fourdigitpin.Length == 4) { foreach (char c in fourdigitpin) { if (!Char.IsDigit(c)) return false; } return true; } } return false; } #region System / Setup Specific /// /// Retrieve the entire Practice List for the given Shared DB Credential * Credential must point to Shared DB * /// /// /// public static DBRetVal GetPracticeList(SQLServerCredential credential) { if (credential != null) { DB db = DB.Create(credential); DBRetVal retVal = db.FillDataTable("SELECT [Practice Name],[Database Name],[UserAPIKey] FROM [Practice List]"); if (!retVal.IsValid) MSLSpecific.Logger.Error("Lytec. Failed to retrieve Practice List for Server:{0} Instance:{1}", credential.Server, credential.Instance); return retVal; } return new DBRetVal(); } /// /// Retrieve the userapi key for a given practice, given Shared DB Credential * Credential must point to Shared DB * /// /// /// public static string GetUserApiKeyForPractice(SQLServerCredential credential, string DataSetOrDataBaseName, out bool bErrorOccured) { bErrorOccured = true; if (credential != null && !String.IsNullOrEmpty(DataSetOrDataBaseName)) { DB db = DB.Create(credential); DBRetVal retVal = db.ExecuteScalar(String.Format("SELECT [UserAPIKey] FROM [Practice List] WHERE [Database Name] LIKE '{0}'", MAKE_SURE_APOSTROPHY_IS_SQL_READY(DataSetOrDataBaseName))); bErrorOccured = retVal.ErrorOccured; if (retVal.IsValid) return retVal.GetScalarRetVal(); } return String.Empty; } /// /// Use this to Set the User API Key * Credential must point to Shared DB * /// /// /// /// /// public static bool SetUserApiKeyForPractice(SQLServerCredential credential, string DataSetOrDataBaseName, string UserApiKey) { if (credential != null && !String.IsNullOrEmpty(DataSetOrDataBaseName)) { DB db = DB.Create(credential); DBRetVal retVal = db.ExecuteNonQuery(String.Format("UPDATE [Practice List] SET [UserApiKey]='{0}' WHERE [Database Name] LIKE '{1}'", UserApiKey, MAKE_SURE_APOSTROPHY_IS_SQL_READY(DataSetOrDataBaseName))); if (!retVal.IsValid) MSLSpecific.Logger.Error("Lytec. Failed to set ApiKey for Server:{0} Instance:{1} Database:{2}", credential.Server, credential.Instance, DataSetOrDataBaseName); return retVal.IsValid; } return false; } /// /// we must ensure uniqueness amongst all our userapikeys (Lytec and somehow Medisoft get into an /// invalid state) where they duplicate them * Credential must point to Shared DB * /// /// /// /// /// public static void CleanUpDuplicateUserApiKeyExists(SQLServerCredential credential) { DBRetVal retVal = GetPracticeList(credential); if (retVal.IsValid) { Stack userApiStack = new Stack(); foreach (DataRow row in retVal.GetDataTableRetVal().Rows) { string UserApiKey = DataRet.Retrieve(row["UserAPIKey"]); if (!String.IsNullOrEmpty(UserApiKey)) { if (!userApiStack.Contains(UserApiKey)) { userApiStack.Push(UserApiKey); } else { // we found a duplicate!!! (Clear the UserApiKey for that Database - IMP!) string DBName = DataRet.Retrieve(row["Database Name"]); SetUserApiKeyForPractice(credential, DBName, String.Empty); } } } } } #endregion #region User Authentication /// /// User Authentication /// /// /// /// /// public static UserInfo1 AuthenticateUserLogin(string SystemApiKey, string User, string Password) { if (!String.IsNullOrEmpty(User) && !String.IsNullOrEmpty(Password)) { SQLServerCredential credential = DBCache.GetSQLServerConnection(SystemApiKey); DB db = DB.Create(credential); DBRetVal result = db.FillDataTable(String.Format("SELECT [Code],[Name],[User Permissions],[Password] FROM [NDCLytec User] Where [Code]='{0}'", MAKE_SURE_APOSTROPHY_IS_SQL_READY(User))); //if ((result != null) && (result.IsValid)) /* Rameshes fix that should not be needed and didn't make sense */ if (result.IsValid) { DataRow row = result.GetDataTableFirstRow(); string dbName = DataRet.Retrieve(row["Name"]); //string dbPwd = DataRet.Retrieve(row["Password"]); // Decrypt the Password from Lytec (also first char is always garbage, a Lytec thing) byte[] pwdField = DataEncryption.DecryptBinary((byte[])row["Password"]); string dbPwd = SimpleStringBinaryConverter.GetString(pwdField).Substring(1); // Authenticate! Password must match (case-insensitive) Lytec doesn't care about case if (!String.IsNullOrEmpty(dbName) && (String.Compare(Password.Trim(), dbPwd, true) == 0)) { UserInfo1 info = new UserInfo1(); info.AuthToken = SystemApiKey; info.Id = DataRet.Retrieve(row["Code"]); info.UserName = info.Id; // Seperate First n' Last name, if possible info.Name = dbName; int nFirst = dbName.IndexOf(' '); if (nFirst != -1) { info.FirstName = dbName.Substring(0, nFirst); info.LastName = dbName.Substring(nFirst + 1); } else { info.FirstName = dbName; info.LastName = ""; } // Authenticated, return the object info.IsProvider = true; return info; } } } return null; } /// /// Pin Authentication /// /// /// /// /// public static UserInfo1 AuthenticateUserPIN(string SystemApiKey, string User, string pin) { if (!String.IsNullOrEmpty(User) && !String.IsNullOrEmpty(pin)) { SQLServerCredential credential = DBCache.GetSQLServerConnection(SystemApiKey); DB db = DB.Create(credential); DBRetVal result = db.FillDataTable(String.Format("SELECT [Code],[Name],[MobilePin] FROM [NDCLytec User] Where [Code]='{0}'", MAKE_SURE_APOSTROPHY_IS_SQL_READY(User))); if (result.IsValid) { DataRow row = result.GetDataTableFirstRow(); string dbName = DataRet.Retrieve(row["Name"]); string dbPin = DataRet.Retrieve(row["MobilePin"]); // Authenticate! Password must match (case-sensitive) if (!String.IsNullOrEmpty(dbName) && (String.Compare(pin.Trim(), dbPin, false) == 0)) { UserInfo1 info = new UserInfo1(); info.AuthToken = SystemApiKey; info.Id = DataRet.Retrieve(row["Code"]); info.UserName = info.Id; // Seperate First n' Last name, if possible info.Name = dbName; int nFirst = dbName.IndexOf(' '); if (nFirst != -1) { info.FirstName = dbName.Substring(0, nFirst); info.LastName = dbName.Substring(nFirst + 1); } else { info.FirstName = dbName; info.LastName = ""; } // Authenticated, return the object info.IsProvider = true; return info; } } } return null; } /// /// Create Pin /// /// /// /// /// public static bool CreatePin(string SystemApiKey, string User, string pin) { if (!String.IsNullOrEmpty(User) && IS_VALID_FOUR_DIGIT_PIN(pin)) { SQLServerCredential credential = DBCache.GetSQLServerConnection(SystemApiKey); DB db = DB.Create(credential); string sql = String.Format("UPDATE [NDCLytec User] SET [MobilePin]='{0}' WHERE [Code]='{1}'", MAKE_SURE_APOSTROPHY_IS_SQL_READY(pin), MAKE_SURE_APOSTROPHY_IS_SQL_READY(User)); DBRetVal result = db.ExecuteNonQuery(sql); return result.IsValid; } return false; } /// /// Pin is Set /// /// /// /// public static bool PinIsSet(string SystemApiKey, string User) { if (!String.IsNullOrEmpty(User)) { SQLServerCredential credential = DBCache.GetSQLServerConnection(SystemApiKey); DB db = DB.Create(credential); string sql = String.Format("SELECT [MobilePin] FROM [NDCLytec User] Where [Code]='{0}'", MAKE_SURE_APOSTROPHY_IS_SQL_READY(User)); DBRetVal result = db.ExecuteScalar(sql); if (result.IsValid) { string currentPin = DataRet.Retrieve(result.GetScalarRetVal()); return IS_VALID_FOUR_DIGIT_PIN(currentPin); } } return false; } #endregion #region Public Simple Info Getters /// /// Get Providers /// /// /// public static ProviderInfo1[] GetProviderList(string SystemApiKey) { SQLServerCredential credential = DBCache.GetSQLServerConnection(SystemApiKey); DB db = DB.Create(credential); DBRetVal result = db.FillDataTable("SELECT [Code],[First Name],[Middle Name],[Last Name],[Phone],[ID] FROM [Provider]"); List providers = new List(); if (result.IsValid) { foreach (DataRow row in result.GetDataTableRetVal().Rows) { ProviderInfo1 info = new ProviderInfo1(); info.UserId = DataRet.Retrieve(row["Code"]); info.FirstName = DataRet.Retrieve(row["First Name"]); info.MiddleName = DataRet.Retrieve(row["Middle Name"]); info.LastName = DataRet.Retrieve(row["Last Name"]); info.Id = DataRet.Retrieve(row["ID"], -1); string phone = DataRet.Retrieve(row["Phone"]); if (!String.IsNullOrEmpty(phone) && phone != "( ) -") { info.Phone = phone; } providers.Add(info); } return providers.ToArray(); } return null; } /// /// Get Diagnosis /// /// /// /// public static TerminologyInfo1[] GetDiagnosisList(string SystemApiKey, string searchStr) { SQLServerCredential credential = DBCache.GetSQLServerConnection(SystemApiKey); DB db = DB.Create(credential); string sql = "SELECT [Code],[Description] FROM [Diagnosis] WHERE [Inactive] = 0"; if (!String.IsNullOrEmpty(searchStr)) sql += String.Format(" AND ([Code] like '%{0}%' OR [Description] like '%{0}%')", MAKE_SURE_APOSTROPHY_IS_SQL_READY(searchStr)); DBRetVal result = db.FillDataTable(sql); List diagnoses = new List(); if (result.IsValid) { foreach (DataRow row in result.GetDataTableRetVal().Rows) { TerminologyInfo1 info = new TerminologyInfo1(); info.Code = DataRet.Retrieve(row["Code"]); info.Description = DataRet.Retrieve(row["Description"]); diagnoses.Add(info); } return diagnoses.ToArray(); } return null; } /// /// GetProcedures /// /// /// /// /// public static TerminologyInfo1[] GetProceduresList(string SystemApiKey, string chart, string searchStr) { SQLServerCredential credential = DBCache.GetSQLServerConnection(SystemApiKey); DB db = DB.Create(credential); string sql = "SELECT [Code],[Description] FROM [Transaction Code] WHERE [Inactive] = 0"; if (!String.IsNullOrEmpty(searchStr)) sql += String.Format(" AND ([Code] like '%{0}%' OR [Description] like '%{0}%')", MAKE_SURE_APOSTROPHY_IS_SQL_READY(searchStr)); // To Do: Implement Fee Schedule for lytec (when time comes...) // In Order to get the modifiers for chart DBRetVal result = db.FillDataTable(sql); List procedures = new List(); if (result.IsValid) { foreach (DataRow row in result.GetDataTableRetVal().Rows) { TerminologyInfo1 info = new TerminologyInfo1(); info.Code = DataRet.Retrieve(row["Code"]); info.Description = DataRet.Retrieve(row["Description"]); procedures.Add(info); } return procedures.ToArray(); } return null; } /// /// Get Facilities /// /// /// public static FacilityInfo1[] GetFacilitiesList(string SystemApiKey) { SQLServerCredential credential = DBCache.GetSQLServerConnection(SystemApiKey); DB db = DB.Create(credential); DBRetVal result = db.FillDataTable("SELECT [Code],[Name],[Address 1],[Address 2],[City],[State],[Zip Code],[Zip Plus 4] FROM [Address] WHERE [Inactive] = 0 AND [Type] = 2"); List facilities = new List(); if (result.IsValid) { foreach (DataRow row in result.GetDataTableRetVal().Rows) { FacilityInfo1 info = new FacilityInfo1(); info.Code = DataRet.Retrieve(row["Code"]); info.Name = DataRet.Retrieve(row["Name"]); // Address Info info.Address = new AddressInfo1(); info.Address.Address1 = DataRet.Retrieve(row["Address 1"]); info.Address.Address2 = DataRet.Retrieve(row["Address 2"]); info.Address.City = DataRet.Retrieve(row["City"]); info.Address.State = DataRet.Retrieve(row["State"]); // Zip Code Plus 4 string zipCode = DataRet.Retrieve(row["Zip Code"]); string zipCode4 = DataRet.Retrieve(row["Zip Plus 4"]); if (!String.IsNullOrEmpty(zipCode) && !String.IsNullOrEmpty(zipCode4)) info.Address.Zip = String.Format("{0}-{1}", zipCode, zipCode4); else info.Address.Zip = zipCode; facilities.Add(info); } return facilities.ToArray(); } return null; } /// /// Get Resources (NOT CALLED) /// /// /// public static ResourceInfo1[] GetResourcesList(string SystemApiKey) { SQLServerCredential credential = DBCache.GetSQLServerConnection(SystemApiKey); DB db = DB.Create(credential); DBRetVal result = db.FillDataTable("SELECT [Resource Key],[Name] FROM [Resource]"); List resources = new List(); if (result.IsValid) { foreach (DataRow row in result.GetDataTableRetVal().Rows) { ResourceInfo1 info = new ResourceInfo1(); info.ID = DataRet.Retrieve(row["Resource Key"], -1); info.Name = DataRet.Retrieve(row["Name"]); resources.Add(info); } return resources.ToArray(); } return null; } /// /// Get Patients /// /// /// /// /// /// public static PatientInfo1[] GetPatientList(string SystemApiKey, string lastName, string firstName, string dateOfBirth) { SQLServerCredential credential = DBCache.GetSQLServerConnection(SystemApiKey); DB db = DB.Create(credential); string sql = "SELECT [Chart Number],[First Name],[Middle Name],[Last Name],[Birth Date],[Sex],[Address 1],[Address 2],[City],[State],[Zip Code],[Zip Plus 4],[Home Phone],[Work Phone],[Work Extension],[Mobile Phone],[Social Security] FROM [Patient]"; // Allow them to search by patient name or by date of birth, // not both that is just too confusing bool bWhereIsSet = false; if (!String.IsNullOrEmpty(dateOfBirth)) { DateTime dt; if (DateTime.TryParse(dateOfBirth, out dt)) { sql = sql + String.Format(" WHERE CONVERT(VARCHAR(25), [Birth Date], 126) LIKE '{0:D4}-{1:D2}-{2:D2}%' ", dt.Year, dt.Month, dt.Day); bWhereIsSet = true; } } else { if (!String.IsNullOrEmpty(lastName)) { sql = sql + String.Format(" WHERE [Last Name] like '%{0}%' {1}", MAKE_SURE_APOSTROPHY_IS_SQL_READY(lastName), !String.IsNullOrEmpty(firstName) ? "OR" : ""); bWhereIsSet = true; } if (!String.IsNullOrEmpty(firstName) && !String.IsNullOrEmpty(lastName)) { sql = sql + String.Format(" {1} [First Name] like '%{0}%' ", MAKE_SURE_APOSTROPHY_IS_SQL_READY(firstName), String.IsNullOrEmpty(lastName) ? "WHERE" : ""); } } // Only query for Active Patients if (bWhereIsSet) sql = sql + " AND [Inactive] = 0 "; else sql = sql + " WHERE [Inactive] = 0 "; sql = sql + " ORDER BY [Last Name],[First Name],[Middle Name] "; DBRetVal result = db.FillDataTable(sql); List patients = new List(); if (result.IsValid) { foreach (DataRow row in result.GetDataTableRetVal().Rows) { PatientInfo1 info = FillInPatientInformation(row, false, true); patients.Add(info); } return patients.ToArray(); } return null; } #endregion #region Private Static SQL Query Helper /// /// Fills in the Patient Info from the Patient Table Columns /// /// /// If Set to true, will fill in as much patient information as possible, otherwise will on fill basic patient name / chart information /// if set to true will fill try to fill in phone information /// private static PatientInfo1 FillInPatientInformation(DataRow row, bool bFillAsMuchPatientInfoAsPossible, bool bFillInPhoneInfo) { PatientInfo1 info = new PatientInfo1(); // Basic Patient Info (Always fill that information) info.ChartId = DataRet.Retrieve(row["Chart Number"]); info.Id = DataRet.Retrieve(row["Chart Number"]); info.FirstName = DataRet.Retrieve(row["First Name"]); info.MiddleName = DataRet.Retrieve(row["Middle Name"]); info.LastName = DataRet.Retrieve(row["Last Name"]); info.Weight = ""; // we don't track weight // Date of Birth & Age Calculation info.DOB = DataRet.Retrieve(row["Birth Date"]); if (info.DOB > DateTime.MinValue) { DateTime now = DateTime.Today; int age = now.Year - info.DOB.Year; if (info.DOB > now.AddYears(-age)) age--; info.Age = age.ToString(); } // Gender int nSex = DataRet.Retrieve(row["Sex"], 2); if (nSex == 0) info.Gender = "Male"; else if (nSex == 1) info.Gender = "Female"; else info.Gender = "Unknown"; // If we want more details on the Patient, this will be set to false if (bFillAsMuchPatientInfoAsPossible) { // Social Security info.SocialSecurity = DataRet.Retrieve(row["Social Security"]); // Primary Address AddressInfo1 address = new AddressInfo1(); address.Address1 = DataRet.Retrieve(row["Address 1"]); address.Address2 = DataRet.Retrieve(row["Address 2"]); address.City = DataRet.Retrieve(row["City"]); address.State = DataRet.Retrieve(row["State"]); // Zip Code Plus 4 string zipCode = DataRet.Retrieve(row["Zip Code"]); string zipCode4 = DataRet.Retrieve(row["Zip Plus 4"]); if (!String.IsNullOrEmpty(zipCode) && !String.IsNullOrEmpty(zipCode4)) address.Zip = String.Format("{0}-{1}", zipCode, zipCode4); else address.Zip = zipCode; info.PrimaryAddress = address; } // Phone Number may already be filled in for us if (bFillInPhoneInfo) { // Primary Phone string Phone = DataRet.Retrieve(row["Mobile Phone"]); if (!String.IsNullOrEmpty(Phone) && Phone != "( ) -") { info.PrimaryPhone = Phone; } else { Phone = DataRet.Retrieve(row["Home Phone"]); if (!String.IsNullOrEmpty(Phone) && Phone != "( ) -") { info.PrimaryPhone = Phone; } else { Phone = DataRet.Retrieve(row["Work Phone"]); if (!String.IsNullOrEmpty(Phone) && Phone != "( ) -") { info.PrimaryPhone = Phone; string WorkExtension = DataRet.Retrieve(row["Work Extension"]); if (!String.IsNullOrEmpty(WorkExtension)) info.PrimaryPhone = String.Concat(info.PrimaryPhone, ",", WorkExtension); } } } } return info; } /// /// Basically the same query as Appointment Query except with no Appointment information because all we have /// is a Chart /// /// Patient Chart /// private static string BuildPatientQuery(string PatientChart) { if (!String.IsNullOrEmpty(PatientChart)) { StringBuilder sb = new StringBuilder(); sb.Append("SELECT p.[Chart Number] as [Chart Number],p.[Provider Code] as [Provider]"); sb.Append(",p.[First Name] as [First Name],p.[Middle Name] as [Middle Name],p.[Last Name] as [Last Name],p.[Birth Date],p.[Sex] as [Sex],p.[Address 1] as [Address 1],p.[Address 2] as [Address 2],p.[City] as [City]"); sb.Append(",p.[State] as [State],p.[Zip Code] as [Zip Code],p.[Zip Plus 4] as [Zip Plus 4],p.[Home Phone] as [Home Phone],p.[Work Phone] as [Work Phone],p.[Work Extension] as [Work Extension],p.[Mobile Phone] as [Mobile Phone]"); sb.Append(",p.[Copay] as [Patient Copay], p.[Primary Code], p.[Ref Physician Code], p.[Social Security]"); sb.Append(",i.[Name] as [Primary Insurance Name]"); sb.Append(",d.[Name] as [Ref Physician Name],d.[Phone] as [Ref Physician Phone],d.[Main Phone] as [Ref Physician Main Phone], d.[Mobile Phone] as [Ref Physician Mobile Phone]"); sb.Append(" FROM [Patient] p "); sb.Append(" LEFT JOIN [Insurance] i ON p.[Primary Code] = i.[Code]"); sb.Append(" LEFT JOIN [Address] d ON p.[Ref Physician Code] = d.[Code]"); sb.Append(" WHERE "); sb.Append("[Chart Number] = '"); sb.Append(MAKE_SURE_APOSTROPHY_IS_SQL_READY(PatientChart)); sb.Append("'"); return sb.ToString(); } return String.Empty; } /// /// Build the Appointment / Patient SQL Query as needed, either specify a Date Range or give a specific ID /// /// /// /// private static string BuildAppointmentQuery(int nApptID, DateTime startDT, DateTime endDT, string[] providers, int[] resources) { StringBuilder sb = new StringBuilder(); sb.Append("SELECT a.[Unique ID],a.[Resource],a.[Appt Date],a.[Status], a.[Appt Time],a.[Slot],a.[Length],a.[Appointment],a.[Chart] as [Chart Number],a.[Provider],a.[Procedure Code],a.[Appointment Note],a.[Text Color],a.[Background Color],a.[Reason]"); sb.Append(",a.[Home Phone] as [Appt Home Phone],a.[Work Phone] as [Appt Work Phone],a.[Work Extension] as [Appt Work Extension]"); sb.Append(",p.[First Name],p.[Middle Name],p.[Last Name],p.[Birth Date],p.[Sex] as [Sex],p.[Address 1],p.[Address 2],p.[City]"); sb.Append(",p.[State],p.[Zip Code],p.[Zip Plus 4],p.[Home Phone],p.[Work Phone],p.[Work Extension],p.[Mobile Phone]"); sb.Append(",p.[Copay] as [Patient Copay], p.[Primary Code], p.[Ref Physician Code], p.[Social Security]"); sb.Append(",r.[Description] as [Reason Description]"); sb.Append(",i.[Name] as [Primary Insurance Name]"); sb.Append(",d.[Name] as [Ref Physician Name],d.[Phone] as [Ref Physician Phone],d.[Main Phone] as [Ref Physician Main Phone], d.[Mobile Phone] as [Ref Physician Mobile Phone]"); sb.Append(" FROM [Appointment] a LEFT JOIN"); sb.Append(" [Patient] p ON p.[Chart Number] = a.[Chart]"); sb.Append(" LEFT JOIN [Appointment Reason] r ON a.[Reason] = r.[Code]"); sb.Append(" LEFT JOIN [Insurance] i ON p.[Primary Code] = i.[Code]"); sb.Append(" LEFT JOIN [Address] d ON p.[Ref Physician Code] = d.[Code]"); sb.Append(" WHERE "); // if ID is specified, we are looking for a single appointment, // otherwise we are looking for a range if (nApptID >= 0) { sb.Append(String.Format(" [Unique ID] = {0} ", nApptID)); } else { // [Appt Date] between '2010-10-06 00:00:00' and '2010-10-06 00:00:00' AND const string DATE_FORMAT = "{0:D4}-{1:D2}-{2:D2} 00:00:00"; string StartDate = String.Format(DATE_FORMAT, startDT.Year, startDT.Month, startDT.Day); string EndDate = String.Format(DATE_FORMAT, endDT.Year, endDT.Month, endDT.Day); sb.Append(String.Format("[Appt Date] between '{0}' AND '{1}' AND ", StartDate, EndDate)); // [Appt Time] between '1753-01-01 08:00:00' AND '1753-01-01 12:00:00' const string TIME_FORMAT = "1753-01-01 {0:D2}:{1:D2}:{2:D2}"; string StartTime = String.Format(TIME_FORMAT, startDT.Hour, startDT.Minute, startDT.Second); string EndTime = String.Format(TIME_FORMAT, endDT.Hour, endDT.Minute, endDT.Second); sb.Append(String.Format("[Appt Time] between '{0}' AND '{1}' ", StartTime, EndTime)); // Do we have providers? // AND [Provider] in ('LAB',) if (providers != null && providers.Length > 0) { sb.Append("AND [Provider] in ('',"); // Allow '' blank providers for (int i = 0; i < providers.Length; ++i) { sb.Append("'"); sb.Append(MAKE_SURE_APOSTROPHY_IS_SQL_READY(providers[i])); sb.Append("'"); if (i != providers.Length - 1) sb.Append(","); } sb.Append(") "); } // Do we have resources? // AND [Resource] in (2, ) if (resources != null && resources.Length > 0) { sb.Append("AND [Resource] in ("); for (int i = 0; i < resources.Length; ++i) { sb.Append(resources[i]); if (i != resources.Length - 1) sb.Append(","); } sb.Append(") "); } // Filter out all Cancelled Appointments sb.Append(" AND a.[Status] <> 7 "); // Don't forget the order by sb.Append("Order By [Appt Date], [Appt Time], [Slot]"); } return sb.ToString(); } #endregion #region Public Appointment Methods /// /// Retrieve all appointments for the given time period for the given providers or resources /// /// /// /// /// /// public static AppointmentInfo1[] GetAppointments(string SystemApiKey, ProviderInfo1[] providerInfo, ResourceInfo1[] resourceInfo, DateTime startDate, DateTime endDate) { SQLServerCredential credential = DBCache.GetSQLServerConnection(SystemApiKey); DB db = DB.Create(credential); string sql = ""; List providers = new List(); List resources = new List(); if (providerInfo != null) { foreach (ProviderInfo1 info1 in providerInfo) { if (!String.IsNullOrEmpty(info1.UserId)) providers.Add(info1.UserId); } } if (resourceInfo != null) { foreach (ResourceInfo1 info1 in resourceInfo) { if (info1.ID >= 0) resources.Add(info1.ID); } } // Build the Appointment QUERY (don't pass in an Appt ID and it will use a range) sql = BuildAppointmentQuery(-1, startDate, endDate, providers.ToArray(), resources.ToArray()); DBRetVal result = db.FillDataTable(sql); List appointments = new List(); if (result.IsValid) { foreach (DataRow row in result.GetDataTableRetVal().Rows) { AppointmentInfo1 info = new AppointmentInfo1(); info.Id = DataRet.Retrieve(row["Unique ID"]); // Retrieving Description for the Appointment Reason Code via join info.Reason = DataRet.Retrieve(row["Reason Description"]); //Retrieving Provider code for the appointment info.ProviderCode = DataRet.Retrieve(row["Provider"]); // Let's figgure out the Appointment DTs // StartDateTime = DateTime.Parse(string.Format("{0} {1}:{2}:00 AM", today.ToShortDateString(), 2, 15)), // EndDateTime = DateTime.Parse(string.Format("{0} {1}:{2}:00 PM", today.ToShortDateString(), 2, 30)), DateTime dtDate = DataRet.Retrieve(row["Appt Date"]); DateTime dtTime = DataRet.Retrieve(row["Appt Time"]); int nDuration = DataRet.Retrieve(row["Length"]); info.StartDateTime = DateTime.Parse(string.Format("{0} {1:D2}:{2:D2}:{3:D2}", dtDate.ToShortDateString(), dtTime.Hour, dtTime.Minute, dtTime.Second)); if (nDuration == 0) { // Leave it as is (no duration was set) info.EndDateTime = info.StartDateTime; } else { dtTime = dtTime.AddMinutes(nDuration); info.EndDateTime = DateTime.Parse(string.Format("{0} {1:D2}:{2:D2}:{3:D2}", dtDate.ToShortDateString(), dtTime.Hour, dtTime.Minute, dtTime.Second)); } // Appointment Primary Phone bool bAppointmentPhoneFound = false; string Phone = DataRet.Retrieve(row["Appt Home Phone"]); if (!String.IsNullOrEmpty(Phone) && Phone != "( ) -") { bAppointmentPhoneFound = true; } else { Phone = DataRet.Retrieve(row["Appt Work Phone"]); if (!String.IsNullOrEmpty(Phone) && Phone != "( ) -") { bAppointmentPhoneFound = true; string WorkExtension = DataRet.Retrieve(row["Appt Work Extension"]); if (!String.IsNullOrEmpty(WorkExtension)) Phone = String.Concat(Phone, ",", WorkExtension); } } // Attach Chart Information (Only the most basic information, try to attach Phone Number if none found in appointment) bool bHasChart = (DataRet.Retrieve(row["Chart Number"]) != ""); if (bHasChart) info.Patient = FillInPatientInformation(row, false, !bAppointmentPhoneFound); else info.AppointmentDescription = DataRet.Retrieve(row["Appointment"]); // Attach Appointment Phone always, if found if (bAppointmentPhoneFound) info.AppointmentPhone = Phone; //[Background Color] //#define DEFAULT_GRID_TEXT_COLOR (RGB(0,0,0)) //#define DEFAULT_GRID_BKGND_COLOR (RGB(255,255,255)) int ColorRef = DataRet.Retrieve(row["Background Color"], 0); if (ColorRef == 0) info.AppointmentColor = new AppointmentColor1() { Red = 255, Green = 255, Blue = 255 }; else info.AppointmentColor = new AppointmentColor1() { Red = Functions.GetRValue(ColorRef), Green = Functions.GetGValue(ColorRef), Blue = Functions.GetBValue(ColorRef) }; // Determine the Appointment Status /* * Lytec Status 0 – Pending 1 – Confirmed 2 – Waiting 3 – Being Seen 4 – Completed 5 – Late 6 – Missed 7 – Cancelled 8 – Rescheduled 9 – Recall * -------------------------- * Lytec To Mobile Mapping 0 – Unconfirmed_Pending 1 – Confirmed 2 – CheckedIn_Waiting 3 – Missed 4 – Cancelled 5 – Being Seen 6 – CheckedOut_Completed 7 - Rescheduled * */ string status = DataRet.Retrieve(row["Status"]); switch (status) { case "0": info.AppointmentStatus = AppointmentStatusEnum.Unconfirmed_Pending; break; case "1": info.AppointmentStatus = AppointmentStatusEnum.Confirmed; break; case "2": info.AppointmentStatus = AppointmentStatusEnum.CheckedIn_Waiting; break; case "3": info.AppointmentStatus = AppointmentStatusEnum.BeingSeen; break; case "4": info.AppointmentStatus = AppointmentStatusEnum.CheckedOut_Completed; break; case "6": info.AppointmentStatus = AppointmentStatusEnum.Missed; break; case "7": info.AppointmentStatus = AppointmentStatusEnum.Cancelled; break; case "8": info.AppointmentStatus = AppointmentStatusEnum.Rescheduled; break; default: break; } // Finally, add the appointment appointments.Add(info); } return appointments.ToArray(); } return null; } /// /// Appointment Detail query. Fills in useful information for the patient regarding the Appointment /// /// /// /// public static AppointmentDetail1 GetAppointmentDetail(string SystemApiKey, int ApptID, bool bCalculateBalance) { SQLServerCredential credential = DBCache.GetSQLServerConnection(SystemApiKey); DB db = DB.Create(credential); string sql = BuildAppointmentQuery(ApptID, DateTime.MinValue, DateTime.MinValue, null, null); DBRetVal result = db.FillDataTable(sql); if (result.IsValid) { DataRow row = result.GetDataTableFirstRow(); AppointmentDetail1 detail = new AppointmentDetail1(); // Appointment Stuff detail.ApptNote = DataRet.Retrieve(row["Appointment Note"]); detail.ApptReason = DataRet.Retrieve(row["Reason Description"]); // Chart Info bool bHasChart = (DataRet.Retrieve(row["Chart Number"]) != ""); if (bHasChart) detail.PatientDetail = GetPatientDetail(SystemApiKey, row, String.Empty, bCalculateBalance); // Return the Appointment Detail Item return detail; } return null; } /// /// Similar to GetAppointmentDetail, except all we have here is only the chart, so no appointment information /// /// /// if this is set to not null (a valid DBRetVal), it will use the DBRetVal and not re-query /// /// public static PatientDetail1 GetPatientDetail(string SystemApiKey, DataRow UseThisRowInsteadOfQuery, string PatientChart, bool bCalculateBalance) { SQLServerCredential credential = DBCache.GetSQLServerConnection(SystemApiKey); DB db = DB.Create(credential); DataRow row = null; // Query only if the row has not been passed in if (UseThisRowInsteadOfQuery == null) { string sql = BuildPatientQuery(PatientChart); DBRetVal result = db.FillDataTable(sql); if (result.IsValid) row = result.GetDataTableFirstRow(); } else { row = UseThisRowInsteadOfQuery; } // Fill in the information if (row != null) { PatientDetail1 detail = new PatientDetail1(); // Chart Info detail.Patient = FillInPatientInformation(row, true, true); // Patient Billing Information detail.PatientBilling = new PatientBillingInfo1(); detail.PatientBilling.CopayAmount = DataRet.Retrieve(row["Patient Copay"], 0); detail.PatientBilling.PrimaryInsCarrier = DataRet.Retrieve(row["Primary Insurance Name"]); // Refering Provider comes from Patient Table also detail.PatientBilling.RefProvider = DataRet.Retrieve(row["Ref Physician Name"]); // Fill in Refering Provider Phone string Phone = DataRet.Retrieve(row["Ref Physician Phone"]); if (!String.IsNullOrEmpty(Phone) && Phone != "( ) -") { detail.PatientBilling.RefProviderPhone = Phone; } else { Phone = DataRet.Retrieve(row["Ref Physician Main Phone"]); if (!String.IsNullOrEmpty(Phone) && Phone != "( ) -") { detail.PatientBilling.RefProviderPhone = Phone; } else { Phone = DataRet.Retrieve(row["Ref Physician Mobile Phone"]); if (!String.IsNullOrEmpty(Phone) && Phone != "( ) -") { detail.PatientBilling.RefProviderPhone = Phone; } } } // Calculate Account Balance (May take awhile) if (bCalculateBalance) { detail.PatientBilling.AccountBalance = CalculatePatientBalance(detail.Patient.ChartId, SystemApiKey); } // Return the Appointment Detail Item return detail; } return null; } /// /// Used to calculate the Patient Balance (stripped out of Lytec) /// /// /// private static decimal CalculatePatientBalance(string Chart, string SystemApiKey) { if (!String.IsNullOrEmpty(Chart)) { // Copied from Lytec Source Code (CalculateBalancesThread.cpp's GetPatientInsuranceBalance()) StringBuilder sb = new StringBuilder(); sb.Append("SELECT\r\n"); sb.Append(" SUM ([Billing Totals].[Pat Portion]) AS [Pat Portion]\r\n"); sb.Append(" , SUM ([Billing Totals].[Prim Ins Portion]) AS [Prim Ins Portion]\r\n"); sb.Append(" , SUM ([Billing Totals].[Sec Ins Portion]) AS [Sec Ins Portion]\r\n"); sb.Append(" , SUM ([Billing Totals].[Tert Ins Portion]) AS [Tert Ins Portion]\r\n"); sb.Append("FROM\r\n"); sb.Append(" (\r\n"); sb.Append(" SELECT\r\n"); sb.Append(" [dbo].[view_DetailPortionsSplit].[Billing]\r\n"); sb.Append(" , MIN ([dbo].[view_DetailPortionsSplit].[Chart]) AS CHART\r\n"); sb.Append(" , SUM ([dbo].[view_DetailPortionsSplit].[Current Patient Portion]) + MIN (CASE WHEN [dbo].[view_DetailPortionsSplit].[Which Insurance Is Responsible] >= 0 THEN [dbo].[view_HeaderCopay].[Adjusted CoPay] ELSE 0.0 END) AS [Pat Portion]\r\n"); sb.Append(" , SUM (CASE WHEN [dbo].[view_DetailPortionsSplit].[Which Insurance Is Responsible] = 0 THEN [dbo].[view_DetailPortionsSplit].[Insurance Portion] ELSE 0.0 END) - MIN (CASE WHEN [dbo].[view_DetailPortionsSplit].[Which Insurance Is Responsible] = 0 THEN [dbo].[view_HeaderCopay].[Adjusted CoPay] ELSE 0.0 END) AS [Prim Ins Portion]\r\n"); sb.Append(" , SUM (CASE WHEN [dbo].[view_DetailPortionsSplit].[Which Insurance Is Responsible] = 1 THEN [dbo].[view_DetailPortionsSplit].[Insurance Portion] ELSE 0.0 END) - MIN (CASE WHEN [dbo].[view_DetailPortionsSplit].[Which Insurance Is Responsible] = 1 THEN [dbo].[view_HeaderCopay].[Adjusted CoPay] ELSE 0.0 END) AS [Sec Ins Portion]\r\n"); sb.Append(" , SUM (CASE WHEN [dbo].[view_DetailPortionsSplit].[Which Insurance Is Responsible] = 2 THEN [dbo].[view_DetailPortionsSplit].[Insurance Portion] ELSE 0.0 END) - MIN (CASE WHEN [dbo].[view_DetailPortionsSplit].[Which Insurance Is Responsible] = 2 THEN [dbo].[view_HeaderCopay].[Adjusted CoPay] ELSE 0.0 END) AS [Tert Ins Portion]\r\n"); sb.Append(" FROM\r\n"); sb.Append(" [dbo].[view_DetailPortionsSplit] INNER JOIN [dbo].[view_HeaderCopay]\r\n"); sb.Append(" ON ([dbo].[view_DetailPortionsSplit].[Billing] = [dbo].[view_HeaderCopay].[Billing])\r\n"); sb.Append(" WHERE\r\n"); sb.Append(String.Format(" [dbo].[view_DetailPortionsSplit].[Chart] = '{0}' \r\n", MAKE_SURE_APOSTROPHY_IS_SQL_READY(Chart))); sb.Append(" GROUP BY\r\n"); sb.Append(" [dbo].[view_DetailPortionsSplit].[Billing]\r\n"); sb.Append(" ) AS [Billing Totals]\r\n"); string sql = sb.ToString(); SQLServerCredential credential = DBCache.GetSQLServerConnection(SystemApiKey); DB db = DB.Create(credential); DBRetVal result = db.FillDataTable(sql); if (result.IsValid) { DataRow row = result.GetDataTableFirstRow(); decimal PatientBalance = DataRet.Retrieve(row[0], 0); decimal PrimaryInsBalance = DataRet.Retrieve(row[1], 0); decimal SecondaryInsBalance = DataRet.Retrieve(row[2], 0); decimal TertiaryInsBalance = DataRet.Retrieve(row[3], 0); // We only return Patient Balance for now return PatientBalance; } } return 0; } #endregion #region Post Billing /// /// Post Billing to Pending Transactions /// /// /// /// /// public static bool PostBilling(string SystemApiKey, string User, BillingPost1 billingInfo) { // Make sure this is a valid BillingPost we are getting passed in if ((billingInfo == null) || (billingInfo.BillingDetails == null) || (billingInfo.BillingDetails.Length == 0)) return false; // Generate new Unique ID string UniqueID = Guid.NewGuid().ToString(); UniqueID = UniqueID.Replace("-", "").Substring(0, 32); // Create encryption code. string sSource = billingInfo.ChartID + billingInfo.PatientLastName + UniqueID; string s64Trimmed = CrossProduct.Core.Utils.MD5EncryptMsg(sSource, 20); bool bSuccessfulPost = true; // Setup DB SQLServerCredential credential = DBCache.GetSQLServerConnection(SystemApiKey); DB db = DB.Create(credential); for (int i = 0; i < billingInfo.BillingDetails.Length && bSuccessfulPost; ++i) { BillingDetail1 detail = billingInfo.BillingDetails[i]; // billing notes have their own procedure code bool bIsNote = !String.IsNullOrEmpty(detail.BillingNote); StringBuilder ValueList = new StringBuilder(); ValueList.Append("NEWID(), "); // ID ValueList.Append("0, "); // Post, tinyint ValueList.Append("0, "); // Transaction status, int ValueList.Append("'" + MAKE_SURE_APOSTROPHY_IS_SQL_READY(billingInfo.ChartID) + "', "); // varchar15 ValueList.Append("0, "); // Billing, int ValueList.Append("'" + MAKE_SURE_APOSTROPHY_IS_SQL_READY(billingInfo.ProviderID) + "', "); // varchar 15 // Make sure valid Date From if (billingInfo.DateFrom != null && billingInfo.DateFrom != DateTime.MinValue) ValueList.Append("'" + CrossProduct.Core.Utils.SqlDTString(billingInfo.DateFrom) + "', "); // datetime else ValueList.Append("'" + CrossProduct.Core.Utils.SqlDTString(DateTime.Now) + "', "); // datetime // If it is a Note type (BillingNote is set) don't attach anything but MOBIL0000 as Procedure Code if (bIsNote) { ValueList.Append("'MOBIL0000', "); // varchar 15 ValueList.Append("'', "); // varchar 3 ValueList.Append("'', "); // varchar 3 ValueList.Append("'', "); // varchar 3 ValueList.Append("'', "); // varchar 3 ValueList.Append("'" + MAKE_SURE_APOSTROPHY_IS_SQL_READY(detail.BillingNote) + "', "); // Lytec Billing Note! } else { ValueList.Append("'" + MAKE_SURE_APOSTROPHY_IS_SQL_READY(detail.ProcedureCode) + "', "); // varchar 15 ValueList.Append("'" + MAKE_SURE_APOSTROPHY_IS_SQL_READY(detail.ProcedureModifier1) + "', "); // varchar 3 ValueList.Append("'" + MAKE_SURE_APOSTROPHY_IS_SQL_READY(detail.ProcedureModifier2) + "', "); // varchar 3 ValueList.Append("'" + MAKE_SURE_APOSTROPHY_IS_SQL_READY(detail.ProcedureModifier3) + "', "); // varchar 3 ValueList.Append("'" + MAKE_SURE_APOSTROPHY_IS_SQL_READY(detail.ProcedureModifier4) + "', "); // varchar 3 ValueList.Append("'', "); // No Note } ValueList.Append(detail.Units + ", "); // real ValueList.Append((i + 1).ToString() + ", "); // Procedure Order, int ValueList.Append("'" + MAKE_SURE_APOSTROPHY_IS_SQL_READY(billingInfo.PatientLastName) + "', "); // patient last name ValueList.Append("'" + MAKE_SURE_APOSTROPHY_IS_SQL_READY(billingInfo.PatientFirstName) + "', "); // patient first name // Don't insert an invalid birthday (Lytec allows nulls) if (billingInfo.BirthDate != null && billingInfo.BirthDate != DateTime.MinValue) ValueList.Append("'" + CrossProduct.Core.Utils.SqlDTString(billingInfo.BirthDate) + "', "); // datetime else ValueList.Append(" NULL, "); // datetime ValueList.Append("'" + MAKE_SURE_APOSTROPHY_IS_SQL_READY(billingInfo.PatientSocialSecurity) + "', ");// varchar 11 ValueList.Append("0, "); // Exception // int ValueList.Append("'" + MAKE_SURE_APOSTROPHY_IS_SQL_READY(billingInfo.Facility) + "', "); // Facility ValueList.Append("0, "); // Created By Application. // int ValueList.Append("'" + UniqueID + "', "); // varchar 32 ValueList.Append("'', "); // Error msg. varchar 80 ValueList.Append("'" + CrossProduct.Core.Utils.SqlDTString(DateTime.Now) + "', "); // Created Date, datetime ValueList.Append("NULL, "); // mod date, datetime ValueList.Append("0,"); // Viewed, tinyint ValueList.Append("'" + MAKE_SURE_APOSTROPHY_IS_SQL_READY(s64Trimmed) + "',"); // Encryption Code. /// ******* /// Attach each Diagnosis Code (we can do up to 12) for each procedure /// Always do this last, deals with trailing Comma (,) /// ******* for (int j = 0; j < 12; ++j) { try { ValueList.Append("'"); // We only add Diagnosis codes, if this is not a billing note if(!bIsNote) ValueList.Append(MAKE_SURE_APOSTROPHY_IS_SQL_READY(detail.DiagnosisCodes[j])); // Diagnosis 1 - 12, varchar 15 if (j < 11) ValueList.Append("', "); else ValueList.Append("'"); } catch (IndexOutOfRangeException) { if (j < 11) ValueList.Append("', "); else ValueList.Append("'"); } catch (NullReferenceException) { if (j < 11) ValueList.Append("', "); else ValueList.Append("'"); } } // Build our sql statement. StringBuilder sb = new StringBuilder("INSERT INTO [Pending Transaction] "); sb.Append(" ([Unique ID], Post, [Transaction Status], [Chart Number], [Billing], [Provider], [Date From], [Transaction Code], [Modifier 1], [Modifier 2], [Modifier 3], [Modifier 4], [Detail Note], [Units], [Procedure Order], [Last Name], [First Name], [Birth Date], [Social Security], [Exception], [Facility], [Created By Application], [HL7 Message ID], [Error Message], [Created Date], [Modified Date], [Viewed], [Checksum], [Diagnosis 1], [Diagnosis 2], [Diagnosis 3], [Diagnosis 4], [Diagnosis 5], [Diagnosis 6], [Diagnosis 7], [Diagnosis 8], [Diagnosis 9], [Diagnosis 10], [Diagnosis 11], [Diagnosis 12] )"); sb.Append(" VALUES (" + ValueList.ToString() + ")"); string StrSQL = sb.ToString(); // Post the Billing DBRetVal result = db.ExecuteNonQuery(StrSQL); bSuccessfulPost = result.IsValid; } return bSuccessfulPost; } #endregion } }