///<summary>Creates and inserts a payment similar to the load logic for the income transfer manager.</summary> private Payment CreatePaymentTransferHelper(Patient pat) { Payment payCur = new Payment(); payCur.ClinicNum = 0; if (PrefC.HasClinicsEnabled) //if clinics aren't enabled default to 0 { payCur.ClinicNum = Clinics.ClinicNum; if ((PayClinicSetting)PrefC.GetInt(PrefName.PaymentClinicSetting) == PayClinicSetting.PatientDefaultClinic || (Clinics.ClinicNum == 0 && (PayClinicSetting)PrefC.GetInt(PrefName.PaymentClinicSetting) == PayClinicSetting.SelectedExceptHQ)) { payCur.ClinicNum = pat.ClinicNum; } } payCur.DateEntry = DateTime.Now; payCur.PatNum = pat.PatNum; payCur.PayDate = datePicker.Value; payCur.PaymentSource = CreditCardSource.None; payCur.ProcessStatus = ProcessStat.OfficeProcessed; payCur.PayType = 0; //Income transfer (will always be income transfer). payCur.PayAmt = 0; //Income transfer payment. payCur.PayNum = Payments.Insert(payCur); //Insert and get the payment PayNum which is used with linking the splits. SecurityLogs.MakeLogEntry(Permissions.PaymentCreate, pat.PatNum, $"{Patients.GetLim(pat.PatNum).GetNameLF()}, created by Family Balancer tool."); return(payCur); }
/// <summary>Only used to void or refund transactions from PayConnectPortal. Creates new cloned payment and paysplits for the refund or void. /// Returns true if the transaction was successful, otherwise false.</summary public static bool VoidOrRefundPayConnectPortalTransaction(PayConnectResponseWeb pcResponseWeb, Payment payment, PayConnectService.transType transType, string refNum, decimal amount) { if (!transType.In(PayConnectService.transType.RETURN, PayConnectService.transType.VOID)) { return(false); } List <PaySplit> listPaySplits = PaySplits.GetForPayment(payment.PayNum); PayConnectService.creditCardRequest _payConnectRequest = new PayConnectService.creditCardRequest(); PayConnectResponse response = null; string receiptStr = ""; _payConnectRequest.TransType = transType; _payConnectRequest.RefNumber = refNum; _payConnectRequest.Amount = amount; PayConnectService.transResponse transResponse = PayConnect.ProcessCreditCard(_payConnectRequest, payment.ClinicNum, x => MsgBox.Show(x)); response = new PayConnectResponse(transResponse, _payConnectRequest); receiptStr = PayConnect.BuildReceiptString(_payConnectRequest, transResponse, null, payment.ClinicNum); if (response == null || response.StatusCode != "0") //error in transaction { return(false); } //Record a new payment for the voided transaction Payment clonePayment = payment.Clone(); clonePayment.PayAmt *= -1; //The negated amount of the original payment clonePayment.PayDate = DateTime.Now; clonePayment.Receipt = receiptStr; clonePayment.PayNote = Lan.g("PayConnectL", "Transaction Type") + ": " + Enum.GetName(typeof(PayConnectService.transType), transType) + Environment.NewLine + Lan.g("PayConnectL", "Status") + ": " + response.Description + Environment.NewLine + Lan.g("PayConnectL", "Amount") + ": " + clonePayment.PayAmt + Environment.NewLine + Lan.g("PayConnectL", "Auth Code") + ": " + response.AuthCode + Environment.NewLine + Lan.g("PayConnectL", "Ref Number") + ": " + response.RefNumber; clonePayment.PaymentSource = pcResponseWeb.CCSource; clonePayment.ProcessStatus = ProcessStat.OfficeProcessed; clonePayment.PayNum = Payments.Insert(clonePayment); List <PaySplit> listClonedPaySplits = new List <PaySplit>(); foreach (PaySplit paySplit in listPaySplits) { PaySplit copy = paySplit.Copy(); copy.SplitAmt *= -1; copy.PayNum = clonePayment.PayNum; copy.DatePay = clonePayment.PayDate; listClonedPaySplits.Add(copy); } PaySplits.InsertMany(listClonedPaySplits); PayConnectResponseWeb newPCResponseWeb = new PayConnectResponseWeb() { PatNum = payment.PatNum, PayNum = clonePayment.PayNum, CCSource = pcResponseWeb.CCSource, Amount = clonePayment.PayAmt, PayNote = Lan.g("PayConnectL", clonePayment.PayNote + Environment.NewLine + "From within Open Dental Proper."), ProcessingStatus = PayConnectWebStatus.Completed, DateTimeEntry = DateTime.Now, DateTimeCompleted = DateTime.Now, IsTokenSaved = false, RefNumber = transResponse.RefNumber, TransType = transType, PaymentToken = pcResponseWeb.PaymentToken, }; PayConnectResponseWebs.Insert(newPCResponseWeb); SecurityLogs.MakeLogEntry(Permissions.PaymentCreate, clonePayment.PatNum, Patients.GetLim(clonePayment.PatNum).GetNameLF() + ", " + clonePayment.PayAmt.ToString("c")); return(true); }
///<summary>Sets given appt.AptStatus to broken. ///Provide procCode that should be charted, can be null but will not chart a broken procedure. ///Also considers various broken procedure based prefs. ///Makes its own securitylog entries.</summary> public static void BreakApptHelper(Appointment appt, Patient pat, ProcedureCode procCode) { //suppressHistory is true due to below logic creating a log with a specific HistAppointmentAction instead of the generic changed. DateTime datePrevious = appt.DateTStamp; bool suppressHistory = false; if (procCode != null) { suppressHistory = (procCode.ProcCode.In("D9986", "D9987")); } Appointments.SetAptStatus(appt, ApptStatus.Broken, suppressHistory); //Appointments S-Class handles Signalods if (appt.AptStatus != ApptStatus.Complete) //seperate log entry for completed appointments. { SecurityLogs.MakeLogEntry(Permissions.AppointmentEdit, pat.PatNum, appt.ProcDescript + ", " + appt.AptDateTime.ToString() + ", Broken from the Appts module.", appt.AptNum, datePrevious); } else { SecurityLogs.MakeLogEntry(Permissions.AppointmentCompleteEdit, pat.PatNum, appt.ProcDescript + ", " + appt.AptDateTime.ToString() + ", Broken from the Appts module.", appt.AptNum, datePrevious); } #region HL7 //If there is an existing HL7 def enabled, send a SIU message if there is an outbound SIU message defined if (HL7Defs.IsExistingHL7Enabled()) { //S15 - Appt Cancellation event MessageHL7 messageHL7 = MessageConstructor.GenerateSIU(pat, Patients.GetPat(pat.Guarantor), EventTypeHL7.S15, appt); //Will be null if there is no outbound SIU message defined, so do nothing if (messageHL7 != null) { HL7Msg hl7Msg = new HL7Msg(); hl7Msg.AptNum = appt.AptNum; hl7Msg.HL7Status = HL7MessageStatus.OutPending; //it will be marked outSent by the HL7 service. hl7Msg.MsgText = messageHL7.ToString(); hl7Msg.PatNum = pat.PatNum; HL7Msgs.Insert(hl7Msg); #if DEBUG MessageBox.Show("Appointments", messageHL7.ToString()); #endif } } #endregion List <Procedure> listProcedures = new List <Procedure>(); //splits should only exist on procs if they are using tp pre-payments List <PaySplit> listSplitsForApptProcs = new List <PaySplit>(); bool isNonRefundable = false; double brokenProcAmount = 0; Procedure brokenProcedure = new Procedure(); bool wasBrokenProcDeleted = false; if (PrefC.GetYN(PrefName.PrePayAllowedForTpProcs)) { listProcedures = Procedures.GetProcsForSingle(appt.AptNum, false); if (listProcedures.Count > 0) { listSplitsForApptProcs = PaySplits.GetPaySplitsFromProcs(listProcedures.Select(x => x.ProcNum).ToList()); } } #region Charting the proc if (procCode != null) { switch (procCode.ProcCode) { case "D9986": //Missed HistAppointments.CreateHistoryEntry(appt.AptNum, HistAppointmentAction.Missed); break; case "D9987": //Cancelled HistAppointments.CreateHistoryEntry(appt.AptNum, HistAppointmentAction.Cancelled); break; } brokenProcedure.PatNum = pat.PatNum; brokenProcedure.ProvNum = (procCode.ProvNumDefault > 0 ? procCode.ProvNumDefault : appt.ProvNum); brokenProcedure.CodeNum = procCode.CodeNum; brokenProcedure.ProcDate = DateTime.Today; brokenProcedure.DateEntryC = DateTime.Now; brokenProcedure.ProcStatus = ProcStat.C; brokenProcedure.ClinicNum = appt.ClinicNum; brokenProcedure.UserNum = Security.CurUser.UserNum; brokenProcedure.Note = Lans.g("AppointmentEdit", "Appt BROKEN for") + " " + appt.ProcDescript + " " + appt.AptDateTime.ToString(); brokenProcedure.PlaceService = (PlaceOfService)PrefC.GetInt(PrefName.DefaultProcedurePlaceService); //Default proc place of service for the Practice is used. List <InsSub> listInsSubs = InsSubs.RefreshForFam(Patients.GetFamily(pat.PatNum)); List <InsPlan> listInsPlans = InsPlans.RefreshForSubList(listInsSubs); List <PatPlan> listPatPlans = PatPlans.Refresh(pat.PatNum); InsPlan insPlanPrimary = null; InsSub insSubPrimary = null; if (listPatPlans.Count > 0) { insSubPrimary = InsSubs.GetSub(listPatPlans[0].InsSubNum, listInsSubs); insPlanPrimary = InsPlans.GetPlan(insSubPrimary.PlanNum, listInsPlans); } double procFee; long feeSch; if (insPlanPrimary == null || procCode.NoBillIns) { feeSch = FeeScheds.GetFeeSched(0, pat.FeeSched, brokenProcedure.ProvNum); } else //Only take into account the patient's insurance fee schedule if the D9986 procedure is not marked as NoBillIns { feeSch = FeeScheds.GetFeeSched(insPlanPrimary.FeeSched, pat.FeeSched, brokenProcedure.ProvNum); } procFee = Fees.GetAmount0(brokenProcedure.CodeNum, feeSch, brokenProcedure.ClinicNum, brokenProcedure.ProvNum); if (insPlanPrimary != null && insPlanPrimary.PlanType == "p" && !insPlanPrimary.IsMedical) //PPO { double provFee = Fees.GetAmount0(brokenProcedure.CodeNum, Providers.GetProv(brokenProcedure.ProvNum).FeeSched, brokenProcedure.ClinicNum, brokenProcedure.ProvNum); brokenProcedure.ProcFee = Math.Max(provFee, procFee); } else if (listSplitsForApptProcs.Count > 0 && PrefC.GetBool(PrefName.TpPrePayIsNonRefundable) && procCode.ProcCode == "D9986") { //if there are pre-payments, non-refundable pre-payments is turned on, and the broken appointment is a missed code then auto-fill //the window with the sum of the procs for the appointment. Transfer money below after broken procedure is confirmed by the user. brokenProcedure.ProcFee = listSplitsForApptProcs.Sum(x => x.SplitAmt); isNonRefundable = true; } else { brokenProcedure.ProcFee = procFee; } if (!PrefC.GetBool(PrefName.EasyHidePublicHealth)) { brokenProcedure.SiteNum = pat.SiteNum; } Procedures.Insert(brokenProcedure); //Now make a claimproc if the patient has insurance. We do this now for consistency because a claimproc could get created in the future. List <Benefit> listBenefits = Benefits.Refresh(listPatPlans, listInsSubs); List <ClaimProc> listClaimProcsForProc = ClaimProcs.RefreshForProc(brokenProcedure.ProcNum); Procedures.ComputeEstimates(brokenProcedure, pat.PatNum, listClaimProcsForProc, false, listInsPlans, listPatPlans, listBenefits, pat.Age, listInsSubs); FormProcBroken FormPB = new FormProcBroken(brokenProcedure, isNonRefundable); FormPB.IsNew = true; FormPB.ShowDialog(); brokenProcAmount = FormPB.AmountTotal; wasBrokenProcDeleted = FormPB.IsProcDeleted; } #endregion #region BrokenApptAdjustment if (PrefC.GetBool(PrefName.BrokenApptAdjustment)) { Adjustment AdjustmentCur = new Adjustment(); AdjustmentCur.DateEntry = DateTime.Today; AdjustmentCur.AdjDate = DateTime.Today; AdjustmentCur.ProcDate = DateTime.Today; AdjustmentCur.ProvNum = appt.ProvNum; AdjustmentCur.PatNum = pat.PatNum; AdjustmentCur.AdjType = PrefC.GetLong(PrefName.BrokenAppointmentAdjustmentType); AdjustmentCur.ClinicNum = appt.ClinicNum; FormAdjust FormA = new FormAdjust(pat, AdjustmentCur); FormA.IsNew = true; FormA.ShowDialog(); } #endregion #region BrokenApptCommLog if (PrefC.GetBool(PrefName.BrokenApptCommLog)) { Commlog commlogCur = new Commlog(); commlogCur.PatNum = pat.PatNum; commlogCur.CommDateTime = DateTime.Now; commlogCur.CommType = Commlogs.GetTypeAuto(CommItemTypeAuto.APPT); commlogCur.Note = Lan.g("Appointment", "Appt BROKEN for") + " " + appt.ProcDescript + " " + appt.AptDateTime.ToString(); commlogCur.Mode_ = CommItemMode.None; commlogCur.UserNum = Security.CurUser.UserNum; commlogCur.IsNew = true; FormCommItem FormCI = new FormCommItem(commlogCur); FormCI.ShowDialog(); } #endregion #region Transfer money from TP Procedures if necessary //Note this MUST come after FormProcBroken since clicking cancel in that window will delete the procedure. if (isNonRefundable && !wasBrokenProcDeleted && listSplitsForApptProcs.Count > 0) { //transfer what the user specified in the broken appointment window. //transfer up to the amount specified by the user foreach (Procedure proc in listProcedures) { if (brokenProcAmount == 0) { break; } List <PaySplit> listSplitsForAppointmentProcedure = listSplitsForApptProcs.FindAll(x => x.ProcNum == proc.ProcNum); foreach (PaySplit split in listSplitsForAppointmentProcedure) { if (brokenProcAmount == 0) { break; } double amt = Math.Min(brokenProcAmount, split.SplitAmt); Payments.CreateTransferForTpProcs(proc, new List <PaySplit> { split }, brokenProcedure, amt); double amtPaidOnApt = listSplitsForApptProcs.Sum(x => x.SplitAmt); if (amtPaidOnApt > amt) { //If the original prepayment amount is greater than the amt being specified for the appointment break, transfer //the difference to an Unallocated Unearned Paysplit on the account. double remainingAmt = amtPaidOnApt - amt; //We have to create a new transfer payment here to correlate to the split. Payment txfrPayment = new Payment(); txfrPayment.PayAmt = 0; txfrPayment.PayDate = DateTime.Today; txfrPayment.ClinicNum = split.ClinicNum; txfrPayment.PayNote = "Automatic transfer from treatment planned procedure prepayment."; txfrPayment.PatNum = split.PatNum; //ultimately where the payment ends up. txfrPayment.PayType = 0; Payments.Insert(txfrPayment); PaymentEdit.IncomeTransferData transferData = PaymentEdit.IncomeTransferData.CreateTransfer(split, txfrPayment.PayNum, true, remainingAmt); PaySplit offset = transferData.ListSplitsCur.FirstOrDefault(x => x.FSplitNum != 0); long offsetSplitNum = PaySplits.Insert(offset); //Get the FSplitNum from the offset PaySplit allocation = transferData.ListSplitsCur.FirstOrDefault(x => x.FSplitNum == 0); allocation.FSplitNum = offsetSplitNum; PaySplits.Insert(allocation); //Insert so the split is now up to date SecurityLogs.MakeLogEntry(Permissions.PaymentCreate, txfrPayment.PatNum, "Automatic transfer of funds for treatment plan procedure pre-payments."); } brokenProcAmount -= amt; } } } //if broken appointment procedure was deleted (user cancelled out of the window) just keep money on the original procedure. #endregion AppointmentEvent.Fire(ODEventType.AppointmentEdited, appt); AutomationL.Trigger(AutomationTrigger.BreakAppointment, null, pat.PatNum); Recalls.SynchScheduledApptFull(appt.PatNum); }
private void butSend_Click(object sender, EventArgs e) { //Assuming the use of XCharge. If adding another vendor (PayConnect for example) //make sure to move XCharge validation in FillGrid() to here. if (prog == null) //Gets filled in FillGrid() { return; } if (gridMain.SelectedIndices.Length < 1) { MsgBox.Show(this, "Must select at least one recurring charge."); return; } if (!PaymentsWithinLockDate()) { return; } string recurringResultFile = "Recurring charge results for " + DateTime.Now.ToShortDateString() + " ran at " + DateTime.Now.ToShortTimeString() + "\r\n\r\n"; int failed = 0; int success = 0; string user = ProgramProperties.GetPropVal(prog.ProgramNum, "Username"); string password = ProgramProperties.GetPropVal(prog.ProgramNum, "Password"); #region Card Charge Loop for (int i = 0; i < gridMain.SelectedIndices.Length; i++) { #region X-Charge if (table.Rows[gridMain.SelectedIndices[i]]["XChargeToken"].ToString() != "" && CreditCards.IsDuplicateXChargeToken(table.Rows[gridMain.SelectedIndices[i]]["XChargeToken"].ToString())) { MessageBox.Show(Lan.g(this, "A duplicate token was found, the card cannot be charged for customer: ") + table.Rows[i]["PatName"].ToString()); continue; } insertPayment = false; ProcessStartInfo info = new ProcessStartInfo(xPath); long patNum = PIn.Long(table.Rows[gridMain.SelectedIndices[i]]["PatNum"].ToString()); string resultfile = Path.Combine(Path.GetDirectoryName(xPath), "XResult.txt"); File.Delete(resultfile); //delete the old result file. info.Arguments = ""; double amt = PIn.Double(table.Rows[gridMain.SelectedIndices[i]]["ChargeAmt"].ToString()); DateTime exp = PIn.Date(table.Rows[gridMain.SelectedIndices[i]]["CCExpiration"].ToString()); string address = PIn.String(table.Rows[gridMain.SelectedIndices[i]]["Address"].ToString()); string addressPat = PIn.String(table.Rows[gridMain.SelectedIndices[i]]["AddressPat"].ToString()); string zip = PIn.String(table.Rows[gridMain.SelectedIndices[i]]["Zip"].ToString()); string zipPat = PIn.String(table.Rows[gridMain.SelectedIndices[i]]["ZipPat"].ToString()); info.Arguments += "/AMOUNT:" + amt.ToString("F2") + " /LOCKAMOUNT "; info.Arguments += "/TRANSACTIONTYPE:PURCHASE /LOCKTRANTYPE "; if (table.Rows[gridMain.SelectedIndices[i]]["XChargeToken"].ToString() != "") { info.Arguments += "/XCACCOUNTID:" + table.Rows[gridMain.SelectedIndices[i]]["XChargeToken"].ToString() + " "; info.Arguments += "/RECURRING "; } else { info.Arguments += "/ACCOUNT:" + table.Rows[gridMain.SelectedIndices[i]]["CCNumberMasked"].ToString() + " "; } if (exp.Year > 1880) { info.Arguments += "/EXP:" + exp.ToString("MMyy") + " "; } if (address != "") { info.Arguments += "\"/ADDRESS:" + address + "\" "; } else if (addressPat != "") { info.Arguments += "\"/ADDRESS:" + addressPat + "\" "; } if (zip != "") { info.Arguments += "\"/ZIP:" + zip + "\" "; } else if (zipPat != "") { info.Arguments += "\"/ZIP:" + zipPat + "\" "; } info.Arguments += "/RECEIPT:Pat" + patNum + " "; //aka invoice# info.Arguments += "\"/CLERK:" + Security.CurUser.UserName + " R\" /LOCKCLERK "; info.Arguments += "/RESULTFILE:\"" + resultfile + "\" "; info.Arguments += "/USERID:" + user + " "; info.Arguments += "/PASSWORD:"******" "; info.Arguments += "/HIDEMAINWINDOW "; info.Arguments += "/AUTOPROCESS "; info.Arguments += "/SMALLWINDOW "; info.Arguments += "/AUTOCLOSE "; info.Arguments += "/NORESULTDIALOG "; Cursor = Cursors.WaitCursor; Process process = new Process(); process.StartInfo = info; process.EnableRaisingEvents = true; process.Start(); while (!process.HasExited) { Application.DoEvents(); } Thread.Sleep(200); //Wait 2/10 second to give time for file to be created. Cursor = Cursors.Default; string line = ""; string resultText = ""; recurringResultFile += "PatNum: " + patNum + " Name: " + table.Rows[i]["PatName"].ToString() + "\r\n"; using (TextReader reader = new StreamReader(resultfile)) { line = reader.ReadLine(); while (line != null) { if (resultText != "") { resultText += "\r\n"; } resultText += line; if (line.StartsWith("RESULT=")) { if (line == "RESULT=SUCCESS") { success++; labelCharged.Text = Lan.g(this, "Charged=") + success; insertPayment = true; } else { failed++; labelFailed.Text = Lan.g(this, "Failed=") + failed; } } line = reader.ReadLine(); } recurringResultFile += resultText + "\r\n\r\n"; } #endregion if (insertPayment) { Patient patCur = Patients.GetPat(patNum); Payment paymentCur = new Payment(); paymentCur.DateEntry = nowDateTime.Date; paymentCur.PayDate = GetPayDate(PIn.Date(table.Rows[gridMain.SelectedIndices[i]]["LatestPayment"].ToString()), PIn.Date(table.Rows[gridMain.SelectedIndices[i]]["DateStart"].ToString())); paymentCur.PatNum = patCur.PatNum; paymentCur.ClinicNum = PIn.Long(table.Rows[gridMain.SelectedIndices[i]]["ClinicNum"].ToString()); paymentCur.PayType = PIn.Int(ProgramProperties.GetPropVal(prog.ProgramNum, "PaymentType")); paymentCur.PayAmt = amt; paymentCur.PayNote = resultText; paymentCur.IsRecurringCC = true; Payments.Insert(paymentCur); long provNum = PIn.Long(table.Rows[gridMain.SelectedIndices[i]]["ProvNum"].ToString()); //for payment plans only if (provNum == 0) //Regular payments need to apply to the provider that the family owes the most money to. { DataTable dt = Patients.GetPaymentStartingBalances(patCur.Guarantor, paymentCur.PayNum); double highestAmt = 0; for (int j = 0; j < dt.Rows.Count; j++) { double afterIns = PIn.Double(dt.Rows[j]["AfterIns"].ToString()); if (highestAmt >= afterIns) { continue; } highestAmt = afterIns; provNum = PIn.Long(dt.Rows[j]["ProvNum"].ToString()); } } PaySplit split = new PaySplit(); split.PatNum = paymentCur.PatNum; split.ClinicNum = paymentCur.ClinicNum; split.PayNum = paymentCur.PayNum; split.ProcDate = paymentCur.PayDate; split.DatePay = paymentCur.PayDate; split.ProvNum = provNum; split.SplitAmt = paymentCur.PayAmt; split.PayPlanNum = PIn.Long(table.Rows[gridMain.SelectedIndices[i]]["PayPlanNum"].ToString()); PaySplits.Insert(split); if (PrefC.GetBool(PrefName.AgingCalculatedMonthlyInsteadOfDaily)) { Ledgers.ComputeAging(patCur.Guarantor, PrefC.GetDate(PrefName.DateLastAging), false); } else { Ledgers.ComputeAging(patCur.Guarantor, DateTimeOD.Today, false); if (PrefC.GetDate(PrefName.DateLastAging) != DateTime.Today) { Prefs.UpdateString(PrefName.DateLastAging, POut.Date(DateTime.Today, false)); //Since this is always called from UI, the above line works fine to keep the prefs cache current. } } } } #endregion try { File.WriteAllText(Path.Combine(Path.GetDirectoryName(xPath), "RecurringChargeResult.txt"), recurringResultFile); } catch { } //Do nothing cause this is just for internal use. FillGrid(); labelCharged.Text = Lan.g(this, "Charged=") + success; labelFailed.Text = Lan.g(this, "Failed=") + failed; MsgBox.Show(this, "Done charging cards.\r\nIf there are any patients remaining in list, print the list and handle each one manually."); }