예제 #1
0
        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));
        }
예제 #2
0
        ///<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;
        }
예제 #3
0
        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);
        }
예제 #4
0
        ///<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);
        }
예제 #5
0
        ///<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);
        }
예제 #6
0
        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);
        }
예제 #7
0
        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));
        }
예제 #8
0
        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);
        }
예제 #9
0
        ///<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.
        }