public static string GetPropVal(ProgramName programName, string desc) { //No need to check RemotingRole; no call to db. long programNum = Programs.GetProgramNum(programName); return(GetPropVal(programNum, desc)); }
///<summary>The main logic that sends Podium invitations. Set isService true only when the calling method is the Open Dental Service.</summary> public static void ThreadPodiumSendInvitations(bool isService) { long programNum = Programs.GetProgramNum(ProgramName.Podium); //Consider blocking re-entrance if this hasn't finished. //Only send invitations if the program link is enabled, the computer name is set to this computer, and eConnector is not set to send invitations if (!Programs.IsEnabled(ProgramName.Podium) || !ODEnvironment.IdIsThisComputer(ProgramProperties.GetPropVal(programNum, PropertyDescs.ComputerNameOrIP)) || ProgramProperties.GetPropVal(programNum, PropertyDescs.UseService) != POut.Bool(isService)) { return; } //Keep a consistant "Now" timestamp throughout this method. DateTime nowDT = MiscData.GetNowDateTime(); if (Podium.DateTimeLastRan == DateTime.MinValue) //First time running the thread. { Podium.DateTimeLastRan = nowDT.AddMilliseconds(-PodiumThreadIntervalMS); } ReviewInvitationTrigger newPatTrigger = PIn.Enum <ReviewInvitationTrigger>(ProgramProperties.GetPropVal(programNum, PropertyDescs.NewPatientTriggerType)); ReviewInvitationTrigger existingPatTrigger = PIn.Enum <ReviewInvitationTrigger>(ProgramProperties.GetPropVal(programNum, PropertyDescs.ExistingPatientTriggerType)); List <Appointment> listNewPatAppts = GetAppointmentsToSendReview(newPatTrigger, programNum, true); foreach (Appointment apptCur in listNewPatAppts) { Podium.SendData(Patients.GetPat(apptCur.PatNum), apptCur.ClinicNum); } List <Appointment> listExistingPatAppts = GetAppointmentsToSendReview(existingPatTrigger, programNum, false); foreach (Appointment apptCur in listExistingPatAppts) { Podium.SendData(Patients.GetPat(apptCur.PatNum), apptCur.ClinicNum); } Podium.DateTimeLastRan = nowDT; }
public static DataTable GetMissingPaymentsTable(DateTime dateStart, DateTime dateEnd) { if (RemotingClient.RemotingRole == RemotingRole.ClientWeb) { return(Meth.GetTable(MethodBase.GetCurrentMethod(), dateStart, dateEnd)); } string command = "SELECT xchargetransaction.* " + "FROM xchargetransaction " + "WHERE " + DbHelper.BetweenDates("TransactionDateTime", dateStart, dateEnd) + " " + "AND xchargetransaction.ResultCode=0"; //Valid entries to count have result code 0 List <XChargeTransaction> listTrans = Crud.XChargeTransactionCrud.SelectMany(command); command = "SELECT payment.* " + "FROM payment " //only payments with the same PaymentType as the X-Charge PaymentType for the clinic + "INNER JOIN (" + "SELECT ClinicNum,PropertyValue PaymentType FROM programproperty " + "WHERE ProgramNum=" + POut.Long(Programs.GetProgramNum(ProgramName.Xcharge)) + " AND PropertyDesc='PaymentType'" + ") paytypes ON paytypes.ClinicNum=payment.ClinicNum AND paytypes.PaymentType=payment.PayType " + "WHERE DateEntry BETWEEN " + POut.Date(dateStart) + " AND " + POut.Date(dateEnd); List <Payment> listPays = Crud.PaymentCrud.SelectMany(command); for (int i = listTrans.Count - 1; i >= 0; i--) //Looping backwards in order to remove items { XChargeTransaction tran = listTrans[i]; Payment pay = listPays.Where(x => x.PatNum == tran.PatNum) .Where(x => x.DateEntry.Date == tran.TransactionDateTime.Date) .Where(x => x.PayAmt.Equals(tran.Amount)) .FirstOrDefault(); if (pay == null) //The XCharge transaction does not have a corresponding payment. { continue; } listTrans.RemoveAt(i); listPays.Remove(pay); //So that the same payment does not get counted for more than one XCharge transaction. } DataTable table = Crud.XChargeTransactionCrud.ListToTable(listTrans); List <string> listColumnsToKeep = new List <string> { "TransactionDateTime", "TransType", "ClerkID", "ItemNum", "PatNum", "CreditCardNum", "Expiration", "Result", "Amount" }; //Remove columns we don't want. for (int i = table.Columns.Count - 1; i >= 0; i--) { if (table.Columns[i].ColumnName.In(listColumnsToKeep)) { continue; } table.Columns.RemoveAt(i); } //Reorder the column in the order we want them. for (int i = 0; i < listColumnsToKeep.Count; i++) { table.Columns[listColumnsToKeep[i]].SetOrdinal(i); } return(table); }
///<summary>Increments the PreviousFileNumber program property to the next available int and returns that new file number.</summary> public static int GetUniqueFileNum() { if (RemotingClient.RemotingRole == RemotingRole.ClientWeb) { return(Meth.GetInt(MethodBase.GetCurrentMethod())); } long progNum = Programs.GetProgramNum(ProgramName.TrojanExpressCollect); int fileNum = PIn.Int(ProgramProperties.GetValFromDb(progNum, "PreviousFileNumber"), false) + 1; while (ProgramProperties.SetProperty(progNum, "PreviousFileNumber", fileNum.ToString()) < 1) { fileNum++; } return(fileNum); }
///<summary>Tries each of the phone numbers provided in the list one at a time until it succeeds.</summary> public static bool SendData(Patient pat, long clinicNum) { string locationId = ProgramProperties.GetPropValForClinicOrDefault(Programs.GetProgramNum(ProgramName.Podium), PropertyDescs.LocationID, clinicNum); if (string.IsNullOrEmpty(locationId)) { return(false); } List <string> listPhoneNumbers = new List <string>() { pat.WirelessPhone, pat.HmPhone }; int statusCode = -100; //Set default to a failure, negative because http status codes are 1xx-5xx string apiUrl = "https://api.podium.com/api/v2/switchboard_invitations"; string apiToken = ProgramProperties.GetPropVal(Programs.GetProgramNum(ProgramName.Podium), PropertyDescs.APIToken); if (pat.TxtMsgOk == YN.No || (pat.TxtMsgOk == YN.Unknown && PrefC.GetBool(PrefName.TextMsgOkStatusTreatAsNo))) //Don't text //Try to use email { statusCode = MakeWebCall(apiToken, apiUrl, locationId, "", pat); MakeCommlog(pat, "", statusCode); return(statusCode.Between(200, 299)); } //Try all phone numbers for this patient since they allow texting for (int i = 0; i < listPhoneNumbers.Count; i++) { string phoneNumber = new string(listPhoneNumbers[i].Where(x => char.IsDigit(x)).ToArray()); if (phoneNumber == "") { continue; } statusCode = MakeWebCall(apiToken, apiUrl, locationId, phoneNumber, pat); if (statusCode.Between(200, 299)) { MakeCommlog(pat, phoneNumber, statusCode); return(true); } else { //If the status code was an error for the current number, we will attempt to use other numbers the patient may have entered. //We will only make an error commlog and return false if all numbers tried returned an error. } } MakeCommlog(pat, "", statusCode); //explicitly failed or did not succeed. return(false); }
public static string GetPropVal(ProgramName progName, string propertyDesc) { //No need to check RemotingRole; no call to db. long programNum = Programs.GetProgramNum(progName); for (int i = 0; i < ProgramPropertyC.Listt.Count; i++) { if (ProgramPropertyC.Listt[i].ProgramNum != programNum) { continue; } if (ProgramPropertyC.Listt[i].PropertyDesc != propertyDesc) { continue; } return(ProgramPropertyC.Listt[i].PropertyValue); } throw new ApplicationException("Property not found: " + propertyDesc); }
public static DataTable GetMissingXTransTable(DateTime dateStart, DateTime dateEnd) { if (RemotingClient.RemotingRole == RemotingRole.ClientWeb) { return(Meth.GetTable(MethodBase.GetCurrentMethod(), dateStart, dateEnd)); } string command = "SELECT payment.PatNum,LName,FName,payment.DateEntry,payment.PayDate,payment.PayNote,payment.PayAmt " + "FROM patient " + "INNER JOIN payment ON payment.PatNum=patient.PatNum " //only payments with the same PaymentType as the X-Charge PaymentType for the clinic + "INNER JOIN (" + "SELECT ClinicNum,PropertyValue AS PaymentType FROM programproperty " + "WHERE ProgramNum=" + POut.Long(Programs.GetProgramNum(ProgramName.Xcharge)) + " AND PropertyDesc='PaymentType'" + ") paytypes ON paytypes.ClinicNum=payment.ClinicNum AND paytypes.PaymentType=payment.PayType " + "LEFT JOIN xchargetransaction ON xchargetransaction.PatNum=payment.PatNum " + "AND " + DbHelper.DtimeToDate("TransactionDateTime") + "=payment.DateEntry " + "AND (CASE WHEN xchargetransaction.ResultCode=5 THEN 0 ELSE xchargetransaction.Amount END)=payment.PayAmt " + "AND xchargetransaction.ResultCode IN(0,5,10) " + "WHERE payment.DateEntry BETWEEN " + POut.Date(dateStart) + " AND " + POut.Date(dateEnd) + " " + "AND TransactionDateTime IS NULL " + "ORDER BY payment.PayDate ASC,LName,FName"; return(Db.GetTable(command)); }
private static void MakeCommlog(Patient pat, string phoneNumber, int statusCode) { string commText = ""; //Status code meanings: // -100: Patient had no phone number // -200: Patient can't text and had no email // 2XX: Successfully sent message // 422: Message has already been sent for patient // Anything else: Failure of some sort. switch (statusCode / 100) //Get general http status codes e.g. -100=-1, 203=2 { case -1: //Failure, no phone number commText = Lans.g("Podium", "Podium review invitation request failed because there was no phone number. Error code:") + " " + statusCode; break; case -2: //Failure, no email commText = Lans.g("Podium", "Podium review invitation request failed because the patient doesn't accept texts " + "and there was no email address. Error code:") + " " + statusCode; break; case 2: //Success https://httpstatusdogs.com/200-ok commText = Lans.g("Podium", "Podium review invitation request successfully sent."); break; case 4: //Client side communication failure https://httpstatusdogs.com/400-bad-request if (statusCode == 422) //422 is Unprocessable Entity, which is sent in this case when a phone number has received an invite already. { commText = Lans.g("Podium", "The request failed because an identical request was previously sent."); } else { commText = Lans.g("Podium", "The request failed to reach Podium with error code:") + " " + statusCode; } break; case 5: //Server side internal failure. https://httpstatusdogs.com/500-internal-server-error commText = Lans.g("Podium", "The request was rejected by the Podium server with error code:") + " " + statusCode; break; default: //General Failure commText = Lans.g("Podium", "The request failed to send with error code:") + " " + statusCode; break; } if (!string.IsNullOrEmpty(commText)) { commText += "\r\n"; } commText += Lans.g("Podium", "The information sent in the request was") + ": \r\n" + Lans.g("Podium", "First name") + ": \"" + pat.FName + "\", " + Lans.g("Podium", "Last name") + ": \"" + pat.LName + "\", " + Lans.g("Podium", "Email") + ": \"" + pat.Email + "\""; if (phoneNumber != "") //If "successful". { commText += ", " + Lans.g("Podium", "Phone number") + ": \"" + phoneNumber + "\""; } else { string wirelessPhone = new string(pat.WirelessPhone.Where(x => char.IsDigit(x)).ToArray()); string homePhone = new string(pat.HmPhone.Where(x => char.IsDigit(x)).ToArray()); List <string> phonesTried = new List <string> { wirelessPhone, homePhone }.FindAll(x => x != ""); string phoneNumbersTried = ", " + Lans.g("Podium", "No valid phone number found."); if (pat.TxtMsgOk == YN.No || (pat.TxtMsgOk == YN.Unknown && PrefC.GetBool(PrefName.TextMsgOkStatusTreatAsNo))) //Used email { phoneNumbersTried = ""; } else if (phonesTried.Count > 0) { phoneNumbersTried = ", " + Lans.g("Podium", "Phone numbers tried") + ": " + string.Join(", ", phonesTried); } commText += phoneNumbersTried; } long programNum = Programs.GetProgramNum(ProgramName.Podium); Commlog commlogCur = new Commlog(); commlogCur.CommDateTime = DateTime.Now; commlogCur.DateTimeEnd = DateTime.Now; commlogCur.PatNum = pat.PatNum; commlogCur.UserNum = 0; //run from server, no valid CurUser commlogCur.CommSource = CommItemSource.ProgramLink; commlogCur.ProgramNum = programNum; commlogCur.CommType = Commlogs.GetTypeAuto(CommItemTypeAuto.MISC); commlogCur.Note = commText; commlogCur.Mode_ = CommItemMode.Text; commlogCur.SentOrReceived = CommSentOrReceived.Sent; Commlogs.Insert(commlogCur); }
///<summary>Tries each of the phone numbers provided in the list one at a time until it succeeds.</summary> public static bool SendData(Patient pat, long clinicNum) { List <string> listPhoneNumbers = new List <string>() { pat.WirelessPhone, pat.HmPhone }; string firstName = pat.FName; string lastName = pat.LName; string emailIn = pat.Email; string isTestString = "false"; string locationId = ProgramProperties.GetPropValForClinicOrDefault(Programs.GetProgramNum(ProgramName.Podium), PropertyDescs.LocationID, clinicNum); int statusCode = -100; //Set default to a failure, negative because http status codes are 1xx-5xx #if DEBUG isTestString = "true"; #endif for (int i = 0; i < listPhoneNumbers.Count; i++) { string phoneNumber = new string(listPhoneNumbers[i].Where(x => char.IsDigit(x)).ToArray()); if (phoneNumber == "") { continue; } string apiUrl = "https://podium.co/api/v2/review_invitations"; string apiToken = ProgramProperties.GetPropVal(Programs.GetProgramNum(ProgramName.Podium), PropertyDescs.APIToken); //I might be able to use _programNum here if static is per class like I think it is if (string.IsNullOrEmpty(locationId)) { return(false); } try { using (WebClientEx client = new WebClientEx()) { client.Headers[HttpRequestHeader.Accept] = "application/json"; client.Headers[HttpRequestHeader.ContentType] = "application/json"; client.Headers[HttpRequestHeader.Authorization] = "Token token=\"" + apiToken + "\""; client.Encoding = UnicodeEncoding.UTF8; string bodyJson = string.Format(@" {{ ""locationId"": ""{0}"", ""lastName"": ""{3}"", ""firstName"": ""{2}"", ""email"": ""{4}"", ""phoneNumber"": ""{1}"", ""integrationName"": ""opendental"", ""test"": {5} }}" , locationId, phoneNumber, firstName, lastName, emailIn, isTestString); //Post with Authorization headers and a body comprised of a JSON serialized anonymous type. client.UploadString(apiUrl, "POST", bodyJson); statusCode = (int)(client.StatusCode); if (statusCode.Between(200, 299)) { MakeCommlog(pat, phoneNumber, statusCode); return(true); } } } catch (WebException we) { if (we.Response.GetType() == typeof(HttpWebResponse)) { statusCode = (int)((HttpWebResponse)we.Response).StatusCode; } } catch (Exception) { //Do nothing because a verbose commlog will be made below if all phone numbers fail. } } MakeCommlog(pat, "", statusCode); //explicitly failed or did not succeed. return(false); //Sample Request: //Accept: 'application/json's //Content-Type: 'application/json' //Authorization: 'Token token="my_dummy_token"' //Body: //{ // "location_id": "54321", // "phone_number": "1234567890", // "customer": { // "first_name": "Johnny", // "last_name": "Appleseed", // "email": "*****@*****.**" // }, // "test": true //} //NOTE: There will never be a value after "customer": although it was initially interpreted that there would be a "new" flag there. }