///<summary>Throws exception if anything about the provider information is not valid. ///All intended exceptions are Exceptions and are already translated.</summary> public static void ValidateProv(Provider prov, Clinic clinic = null) { if (prov == null) { throw new ODException(Lans.g("Erx", "Provider not found")); } ProviderClinic provClinic = ProviderClinics.GetOneOrDefault(prov.ProvNum, (clinic == null ? 0 : clinic.ClinicNum)); if (prov.IsErxEnabled == ErxEnabledStatus.Disabled) { throw new ODException(Lans.g("Erx", "Erx is disabled for provider") + ": " + prov.Abbr + ". " + Lans.g("Erx", "To enable, edit provider in Lists | Providers and acknowledge Electronic Prescription fees.")); } if (prov.IsHidden) { throw new ODException(Lans.g("Erx", "Provider") + ": " + prov.Abbr + " " + Lans.g("Erx", "is hidden") + ". " + Lans.g("Erx", "Unhide the provider to use Erx features.")); } if (prov.IsNotPerson) { throw new ODException(Lans.g("Erx", "Provider must be a person") + ": " + prov.Abbr); } string fname = prov.FName.Trim(); if (fname == "") { throw new ODException(Lans.g("Erx", "Provider first name missing") + ": " + prov.Abbr); } if (Regex.Replace(fname, "[^A-Za-z'\\- ]*", "") != fname) { throw new ODException(Lans.g("Erx", "Provider first name can only contain letters, dashes, apostrophes, or spaces.") + ": " + prov.Abbr); } string lname = prov.LName.Trim(); if (lname == "") { throw new ODException(Lans.g("Erx", "Provider last name missing") + ": " + prov.Abbr); } if (Regex.Replace(lname, "[^A-Za-z'\\- ]*", "") != lname) //Will catch situations such as "Dale Jr. III" and "Ross DMD". { throw new ODException(Lans.g("Erx", "Provider last name can only contain letters, dashes, apostrophes, or spaces. Use the suffix box for I, II, III, Jr, or Sr") + ": " + prov.Abbr); } //prov.Suffix is not validated here. In ErxXml.cs, the suffix is converted to the appropriate suffix enumeration value, or defaults to DDS if the suffix does not make sense. string deaNum = prov.DEANum; if (provClinic != null) { deaNum = provClinic.DEANum; } if (deaNum.ToLower() != "none" && !Regex.IsMatch(deaNum, "^[A-Za-z]{2}[0-9]{7}$")) { throw new ODException(Lans.g("Erx", "Provider DEA Number must be 2 letters followed by 7 digits. If no DEA Number, enter NONE.") + ": " + prov.Abbr); } string npi = Regex.Replace(prov.NationalProvID, "[^0-9]*", ""); //NPI with all non-numeric characters removed. if (npi.Length != 10) { throw new ODException(Lans.g("Erx", "Provider NPI must be exactly 10 digits") + ": " + prov.Abbr); } if (provClinic == null || provClinic.StateLicense == "") { throw new ODException(Lans.g("Erx", "Provider state license missing") + ": " + prov.Abbr); } if (provClinic == null || !USlocales.IsValidAbbr(provClinic.StateWhereLicensed)) { throw new ODException(Lans.g("Erx", "Provider state where licensed invalid") + ": " + prov.Abbr); } }
///<summary>Only called from Chart for now. No validation is performed here. Validate before calling. There are many validtion checks, including the NPI must be exactly 10 digits.</summary> public static string BuildNewCropClickThroughXml(Provider prov, Employee emp, Patient pat) { string deaNumDefault = ProviderClinics.GetDEANum(prov.ProvNum); NCScript ncScript = new NCScript(); ncScript.Credentials = new CredentialsType(); ncScript.Credentials.partnerName = NewCrop.NewCropPartnerName; ncScript.Credentials.name = NewCrop.NewCropAccountName; ncScript.Credentials.password = NewCrop.NewCropAccountPasssword; ncScript.Credentials.productName = NewCrop.NewCropProductName; ncScript.Credentials.productVersion = NewCrop.NewCropProductVersion; ncScript.UserRole = new UserRoleType(); bool isMidlevel = false; if (emp == null) //Provider { if (prov.IsSecondary) //Mid-level provider { isMidlevel = true; //Secondary (HYG) providers go accross to NewCrop as midlevel providers for now to satisfy the Ohio prescriber requirements. //HYG providers are not normally able to click through to NewCrop because they do not have an NPI number and an NPI is required. //In the future, instead of using the IsSecondary flag as as workaround, we should instead create a new field on the provider table //or perhaps the userod table to allow the user to select the type of provider. ncScript.UserRole.user = UserType.MidlevelPrescriber; ncScript.UserRole.role = RoleType.midlevelPrescriber; } else //Fully licensed provider { ncScript.UserRole.user = UserType.LicensedPrescriber; ncScript.UserRole.role = RoleType.doctor; } } else //Employee { ncScript.UserRole.user = UserType.Staff; ncScript.UserRole.role = RoleType.nurse; } ncScript.Destination = new DestinationType(); ncScript.Destination.requestedPage = RequestedPageType.compose; //This is the tab that the user will want 90% of the time. string practiceTitle = Tidy(PrefC.GetString(PrefName.PracticeTitle), 50); //May be blank. string practicePhone = PrefC.GetString(PrefName.PracticePhone); //Validated to be 10 digits within the chart. string practiceFax = PrefC.GetString(PrefName.PracticeFax); //Validated to be 10 digits within the chart. string practiceAddress = PrefC.GetString(PrefName.PracticeAddress); //Validated to exist in chart. string practiceAddress2 = PrefC.GetString(PrefName.PracticeAddress2); //May be blank. string practiceCity = PrefC.GetString(PrefName.PracticeCity); //Validated to exist in chart. string practiceState = PrefC.GetString(PrefName.PracticeST).ToUpper(); //Validated to be a US state code in chart. string practiceZip = Regex.Replace(PrefC.GetString(PrefName.PracticeZip), "[^0-9]*", ""); //Zip with all non-numeric characters removed. Validated to be 9 digits in chart. string practiceZip4 = practiceZip.Substring(5); //Last 4 digits of zip. practiceZip = practiceZip.Substring(0, 5); //First 5 digits of zip. string country = "US"; //Always United States for now. //if(CultureInfo.CurrentCulture.Name.Length>=2) { // country=CultureInfo.CurrentCulture.Name.Substring(CultureInfo.CurrentCulture.Name.Length-2); //} ncScript.Account = new AccountTypeRx(); //Each LicensedPrescriberID must be unique within an account. Since we send ProvNum for LicensedPrescriberID, each OD database must have a unique AccountID. ncScript.Account.ID = PrefC.GetString(PrefName.NewCropAccountId); //Customer account number then a dash then a random alpha-numeric string of 3 characters, followed by 2 digits. ncScript.Account.accountName = practiceTitle; //May be blank. ncScript.Account.siteID = "1"; //Always send 1. For each AccountID/SiteID pair, a separate database will be created in NewCrop. ncScript.Account.AccountAddress = new AddressType(); ncScript.Account.AccountAddress.address1 = practiceAddress; //Validated to exist in chart. ncScript.Account.AccountAddress.address2 = practiceAddress2; //May be blank. ncScript.Account.AccountAddress.city = practiceCity; //Validated to exist in chart. ncScript.Account.AccountAddress.state = practiceState; //Validated to be a US state code in chart. ncScript.Account.AccountAddress.zip = practiceZip; //Validated to be 9 digits in chart. First 5 digits go in this field. ncScript.Account.AccountAddress.zip4 = practiceZip4; //Validated to be 9 digits in chart. Last 4 digits go in this field. ncScript.Account.AccountAddress.country = country; //Validated above. ncScript.Account.accountPrimaryPhoneNumber = practicePhone; //Validated to be 10 digits within the chart. ncScript.Account.accountPrimaryFaxNumber = practiceFax; //Validated to be 10 digits within the chart. ncScript.Location = new LocationType(); ProviderClinic provClinic = null; if (PrefC.GetBool(PrefName.EasyNoClinics) || (!PrefC.GetBool(PrefName.ElectronicRxClinicUseSelected) && pat.ClinicNum == 0) || (PrefC.GetBool(PrefName.ElectronicRxClinicUseSelected) && Clinics.ClinicNum == 0 && pat.ClinicNum == 0)) { //No clinic. ncScript.Location.ID = "0"; //Always 0, since clinicnums must be >= 1, will never overlap with a clinic if the office turns clinics on after first use. ncScript.Location.locationName = practiceTitle; //May be blank. ncScript.Location.LocationAddress = new AddressType(); ncScript.Location.LocationAddress.address1 = practiceAddress; //Validated to exist in chart. ncScript.Location.LocationAddress.address2 = practiceAddress2; //May be blank. ncScript.Location.LocationAddress.city = practiceCity; //Validated to exist in chart. ncScript.Location.LocationAddress.state = practiceState; //Validated to be a US state code in chart. ncScript.Location.LocationAddress.zip = practiceZip; //Validated to be 9 digits in chart. First 5 digits go in this field. ncScript.Location.LocationAddress.zip4 = practiceZip4; //Validated to be 9 digits in chart. Last 4 digits go in this field. ncScript.Location.LocationAddress.country = country; //Validated above. ncScript.Location.primaryPhoneNumber = practicePhone; //Validated to be 10 digits within the chart. ncScript.Location.primaryFaxNumber = practiceFax; //Validated to be 10 digits within the chart. ncScript.Location.pharmacyContactNumber = practicePhone; //Validated to be 10 digits within the chart. } else //Using clinics. { Clinic clinic = null; if (PrefC.GetBool(PrefName.ElectronicRxClinicUseSelected) && Clinics.ClinicNum != 0) { clinic = Clinics.GetClinic(Clinics.ClinicNum); } else { clinic = Clinics.GetClinic(pat.ClinicNum); } provClinic = ProviderClinics.GetOne(prov.ProvNum, clinic.ClinicNum); ncScript.Location.ID = clinic.ClinicNum.ToString(); //A positive integer. ncScript.Location.locationName = clinic.Description; //May be blank. ncScript.Location.LocationAddress = new AddressType(); ncScript.Location.LocationAddress.address1 = clinic.Address; //Validated to exist in chart. ncScript.Location.LocationAddress.address2 = clinic.Address2; //May be blank. ncScript.Location.LocationAddress.city = clinic.City; //Validated to exist in chart. ncScript.Location.LocationAddress.state = clinic.State.ToUpper(); //Validated to be a US state code in chart. string clinicZip = Regex.Replace(clinic.Zip, "[^0-9]*", ""); //Zip with all non-numeric characters removed. Validated to be 9 digits in chart. string clinicZip4 = clinicZip.Substring(5); //Last 4 digits of zip. clinicZip = clinicZip.Substring(0, 5); //First 5 digits of zip. ncScript.Location.LocationAddress.zip = clinicZip; //Validated to be 9 digits in chart. First 5 digits go in this field. ncScript.Location.LocationAddress.zip4 = clinicZip4; //Validated to be 9 digits in chart. Last 4 digits go in this field. ncScript.Location.LocationAddress.country = country; //Validated above. ncScript.Location.primaryPhoneNumber = clinic.Phone; //Validated to be 10 digits within the chart. ncScript.Location.primaryFaxNumber = clinic.Fax; //Validated to be 10 digits within the chart. ncScript.Location.pharmacyContactNumber = clinic.Phone; //Validated to be 10 digits within the chart. } //Each unique provider ID sent to NewCrop will cause a billing charge. //Some customer databases have provider duplicates, because they have one provider record per clinic with matching NPIs. //We send NPI as the ID to prevent extra NewCrop charges. //Conversation with NewCrop: //Question: If one of our customers clicks through to NewCrop with 2 different LicensedPrescriber.ID values, // but with the same provider name and NPI, will Open Dental be billed twice or just one time for the NPI used? //Answer: "They would be billed twice. The IDs you send us should always be maintained and unique. // Users are always identified by LicensedPrescriber ID, since their name or credentials could potentially change." if (isMidlevel) { ncScript.MidlevelPrescriber = new MidlevelPrescriberType(); ncScript.MidlevelPrescriber.ID = prov.NationalProvID; //UPIN is obsolete ncScript.MidlevelPrescriber.LicensedPrescriberName = NewCrop.GetPersonNameForProvider(prov); if (deaNumDefault.ToLower() == "none") { ncScript.MidlevelPrescriber.dea = "NONE"; } else { ncScript.MidlevelPrescriber.dea = deaNumDefault; } if (provClinic != null) { ncScript.MidlevelPrescriber.locationDea = provClinic.DEANum; } ncScript.MidlevelPrescriber.licenseState = prov.StateWhereLicensed.ToUpper(); //Validated to be a US state code in the chart. ncScript.MidlevelPrescriber.licenseNumber = prov.StateLicense; //Validated to exist in chart. ncScript.MidlevelPrescriber.npi = prov.NationalProvID; //Validated to be 10 digits in chart. } else //Licensed presriber { ncScript.LicensedPrescriber = new LicensedPrescriberType(); ncScript.LicensedPrescriber.ID = prov.NationalProvID; //UPIN is obsolete ncScript.LicensedPrescriber.LicensedPrescriberName = NewCrop.GetPersonNameForProvider(prov); if (deaNumDefault.ToLower() == "none") { ncScript.LicensedPrescriber.dea = "NONE"; } else { ncScript.LicensedPrescriber.dea = deaNumDefault; } if (provClinic != null) { ncScript.LicensedPrescriber.locationDea = provClinic.DEANum; } ncScript.LicensedPrescriber.licenseState = prov.StateWhereLicensed.ToUpper(); //Validated to be a US state code in the chart. ncScript.LicensedPrescriber.licenseNumber = prov.StateLicense; //Validated to exist in chart. ncScript.LicensedPrescriber.npi = prov.NationalProvID; //Validated to be 10 digits in chart. //ncScript.LicensedPrescriber.freeformCredentials=;//This is where DDS and DMD should go, but we don't support this yet. Probably not necessary anyway. } if (emp != null) { ncScript.Staff = new StaffType(); ncScript.Staff.ID = "emp" + emp.EmployeeNum.ToString(); //A positive integer. Returned in the ExternalUserID field when retreiving prescriptions from NewCrop. Also, provider ID is returned in the same field if a provider created the prescription, so that we can create a distintion between employee IDs and provider IDs. ncScript.Staff.StaffName = new PersonNameType(); ncScript.Staff.StaffName.first = emp.FName; //First name or last name will not be blank. Validated in Chart. ncScript.Staff.StaffName.last = emp.LName; //First name or last name will not be blank. Validated in Chart. ncScript.Staff.StaffName.middle = emp.MiddleI; //May be blank. } ncScript.Patient = new PatientType(); ncScript.Patient.ID = pat.PatNum.ToString(); //A positive integer. ncScript.Patient.PatientName = new PersonNameType(); ncScript.Patient.PatientName.last = pat.LName; //Validated to exist in Patient Edit window. ncScript.Patient.PatientName.first = pat.FName; //May be blank. ncScript.Patient.PatientName.middle = pat.MiddleI; //May be blank. ncScript.Patient.medicalRecordNumber = pat.PatNum.ToString(); //A positive integer. //NewCrop specifically requested that we do not send SSN. //ncScript.Patient.socialSecurityNumber=Regex.Replace(pat.SSN,"[^0-9]*","");//Removes all non-numerical characters. ncScript.Patient.PatientAddress = new AddressOptionalType(); ncScript.Patient.PatientAddress.address1 = pat.Address; //May be blank. ncScript.Patient.PatientAddress.address2 = pat.Address2; //May be blank. ncScript.Patient.PatientAddress.city = pat.City; //May be blank. ncScript.Patient.PatientAddress.state = pat.State.ToUpper(); //May be blank. Validated in chart to be blank or to be a valid US state code. //For some reason, NewCrop will fail to load if a 9 digit zip code is sent. //One customer had all 9 digit zip codes entered for their patients, so we added code here to only send the first 5 digits of the zip. //Patient zip is validated in Chart to be blank, or #####, or #####-####, or #########. if (pat.Zip == "") { ncScript.Patient.PatientAddress.zip = ""; //Blank is allowed. } else //5 or 9 digit zip. Formats are #####, or #####-####, or #########. { ncScript.Patient.PatientAddress.zip = pat.Zip.Substring(0, 5); //First 5 digts only. } ncScript.Patient.PatientAddress.country = country; //Validated above. ncScript.Patient.PatientContact = new ContactType(); //ncScript.Patient.PatientContact.backOfficeFax=;//We do not have a field to pull this information from. //ncScript.Patient.PatientContact.backOfficeTelephone=;//We do not have a field to pull this information from. ncScript.Patient.PatientContact.cellularTelephone = pat.WirelessPhone; //May be blank. Does not need to be 10 digits. ncScript.Patient.PatientContact.email = pat.Email; //May be blank, or may also contain multiple email addresses separated by commas. //ncScript.Patient.PatientContact.fax=;//We do not have a field to pull this information from. ncScript.Patient.PatientContact.homeTelephone = pat.HmPhone; //May be blank. Does not need to be 10 digits. //ncScript.Patient.PatientContact.pagerTelephone=;//We do not have a field to pull this information from. ncScript.Patient.PatientContact.workTelephone = pat.WkPhone; //May be blank. Does not need to be 10 digits. ncScript.Patient.PatientCharacteristics = new PatientCharacteristicsType(); ncScript.Patient.PatientCharacteristics.dob = pat.Birthdate.ToString("yyyyMMdd"); //DOB must be in CCYYMMDD format. if (pat.Gender == PatientGender.Male) { ncScript.Patient.PatientCharacteristics.gender = GenderType.M; } else if (pat.Gender == PatientGender.Female) { ncScript.Patient.PatientCharacteristics.gender = GenderType.F; } else { ncScript.Patient.PatientCharacteristics.gender = GenderType.U; } ncScript.Patient.PatientCharacteristics.genderSpecified = true; //NewCrop programmer's comments regarding other fields we are not currently using (these fields are sent back when fetching prescriptions in the Chart): //ExternalPrescriptionId = your unique identifier for the prescription, only to be used if you are generating the prescription on your own UI. // This is referenced by NewCrop, and cannot be populated with any other value. //EncounterIdentifier = unique ID for the patient visit (e.g. John Doe, 11/11/2013). // This is used by NewCrop for reporting events against a visit, but otherwise does not impact the session. //EpisodeIdentifier = unique ID for the patient’s issue (e.g. John Doe’s broken leg) which may include multiple visits. // Currently not used by NewCrop except for being echoed back; it is possible this functionality would be expanded in the future based on its intent as noted. //ExternalSource = a codified field noting the origin of the prescription. This may not be used. //Serialize MemoryStream memoryStream = new MemoryStream(); XmlSerializer xmlSerializer = new XmlSerializer(typeof(NCScript)); xmlSerializer.Serialize(memoryStream, ncScript); byte[] memoryStreamInBytes = memoryStream.ToArray(); return(Encoding.UTF8.GetString(memoryStreamInBytes, 0, memoryStreamInBytes.Length)); }
///<summary>Returns true if we got a URL from PDMP. ///If true, the response will be the URL. ///If false, the response will be the error.</summary> public static bool TrySendData(Program programCur, Patient pat, out string response) { string result = ""; StringBuilder sbErrors = new StringBuilder(); bool isSuccess = false; ODProgress.ShowAction(() => { if (!programCur.Enabled) { sbErrors.AppendLine(programCur.ProgName + Lans.g("PDMP", " must be enabled in Program Links.")); result = sbErrors.ToString(); return; } if (pat == null) { sbErrors.AppendLine(Lans.g("PDMP", "Please select a patient.")); result = sbErrors.ToString(); return; } Provider prov = Providers.GetProv(pat.PriProv); if (prov == null) { sbErrors.AppendLine(Lans.g("PDMP", "Patient does not have a primary provider.")); result = sbErrors.ToString(); return; } string strDeaNum = ProviderClinics.GetDEANum(prov.ProvNum, Clinics.ClinicNum); //If no result found, retries using clinicNum=0. if (string.IsNullOrWhiteSpace(strDeaNum)) { sbErrors.AppendLine(Lans.g("PDMP", "Patient's provider does not have a DEA number.")); } string stateWhereLicensed = ProviderClinics.GetStateWhereLicensed(pat.PriProv, Clinics.ClinicNum); if (string.IsNullOrWhiteSpace(stateWhereLicensed)) { sbErrors.AppendLine(Lans.g("PDMP", "Patient's provider is not licensed for any state.")); } string facilityIdPropDesc = ""; string userNamePropDesc = ""; string passwordPropDesc = ""; try { facilityIdPropDesc = GetFacilityIdPropDesc(stateWhereLicensed); userNamePropDesc = GetClientUserNamePropDesc(stateWhereLicensed); passwordPropDesc = GetClientPasswordPropDesc(stateWhereLicensed); } catch (NotImplementedException niex) { sbErrors.AppendLine(niex.Message); } //Validation failed. We gave the user as much information to fix as possible. if (!string.IsNullOrWhiteSpace(sbErrors.ToString())) { result = sbErrors.ToString(); return; } //Validation passed and we can now call the PDMP API. string facilityId = ProgramProperties.GetPropVal(programCur.ProgramNum, facilityIdPropDesc); string clientUsername = ProgramProperties.GetPropVal(programCur.ProgramNum, userNamePropDesc); string clientPassword = ProgramProperties.GetPropVal(programCur.ProgramNum, passwordPropDesc); try { //Each state may use a different API for its PDMP services. Implement appropriate classes to handle each state we support. switch (stateWhereLicensed) { case "IL": PDMPLogicoy pdmp = new PDMPLogicoy(clientUsername, clientPassword, pat, prov, stateWhereLicensed, strDeaNum, facilityId); result = pdmp.GetURL(); isSuccess = true; break; default: result = Lans.g("PDMP", "PDMP program link has not been implemented for state: ") + stateWhereLicensed; return; } } catch (Exception ex) { result = ex.Message; return; } }, startingMessage: Lans.g("PDMP", "Fetching data...")); response = result; return(isSuccess); }