///<summary>Hides FeeScheds that are not hidden and not in use by anything. Returns the number of fee scheds that were hidden.</summary> public static long HideUnusedScheds() { if (RemotingClient.RemotingRole == RemotingRole.ClientWeb) { return(Meth.GetLong(MethodBase.GetCurrentMethod())); } ODEvent.Fire(ODEventType.HideUnusedFeeSchedules, Lans.g("FormFeeScheds", "Finding unused fee schedules...")); string command = @"SELECT feesched.FeeSchedNum FROM feesched LEFT JOIN provider ON provider.FeeSched=feesched.FeeSchedNum LEFT JOIN patient ON patient.FeeSched=feesched.FeeSchedNum LEFT JOIN insplan ON insplan.FeeSched=feesched.FeeSchedNum OR insplan.AllowedFeeSched=feesched.FeeSchedNum OR insplan.CopayFeeSched=feesched.FeeSchedNum LEFT JOIN discountplan ON discountplan.FeeSchedNum=feesched.FeeSchedNum WHERE COALESCE(provider.FeeSched,patient.FeeSched,insplan.FeeSched,discountplan.FeeSchedNum) IS NULL AND feesched.IsHidden=0" ; List <long> listFeeScheds = Db.GetListLong(command); if (listFeeScheds.Count == 0) { return(0); } ODEvent.Fire(ODEventType.HideUnusedFeeSchedules, Lans.g("FormFeeScheds", "Hiding unused fee schedules...")); command = "UPDATE feesched SET IsHidden=1 WHERE FeeSchedNum IN(" + string.Join(",", listFeeScheds.Select(x => POut.Long(x))) + ")"; long rowsChanged = Db.NonQ(command); return(rowsChanged); }
private void butHideUnused_Click(object sender, EventArgs e) { if (!MsgBox.Show(this, MsgBoxButtons.OKCancel, "Hide fee schedules that are not in use by insurance plans, patients, or providers?\r\n" + "A backup of the database will be made first.")) { return; } bool hasChanged = FeeScheds.Sync(_listFeeScheds, _listFeeSchedsOld); Action actionProgress = ODProgress.Show(ODEventType.HideUnusedFeeSchedules, startingMessage: Lans.g(this, "Backing up database...")); try { MiscData.MakeABackup(); } catch (Exception ex) { actionProgress?.Invoke(); FriendlyException.Show(Lans.g(this, "Unable to make a backup. No fee schedules have been altered."), ex); return; } ODEvent.Fire(ODEventType.HideUnusedFeeSchedules, Lans.g(this, "Hiding unused fee schedules...")); long countChanged = FeeScheds.HideUnusedScheds(); if (hasChanged || countChanged > 0) { DataValid.SetInvalid(InvalidType.FeeScheds); } actionProgress?.Invoke(); MessageBox.Show(countChanged.ToString() + " " + Lans.g(this, "unused fee schedules hidden.")); _listFeeScheds = FeeScheds.GetDeepCopy(_isSelectionMode); _listFeeSchedsOld = _listFeeScheds.Select(x => x.Copy()).ToList(); FillGrid(); }
private void butMovePats_Click(object sender, EventArgs e) { if (gridMain.SelectedIndices.Length < 1) { MsgBox.Show(this, "You must select at least one clinic to move patients from."); return; } List <Clinic> listClinicsFrom = gridMain.SelectedIndices.OfType <int>().Select(x => (Clinic)gridMain.Rows[x].Tag).ToList(); List <Clinic> listClinicsTo = gridMain.Rows.Select(x => x.Tag as Clinic).ToList(); if (_clinicNumTo == -1) { MsgBox.Show(this, "You must pick a 'To' clinic in the box above to move patients to."); return; } Clinic clinicTo = listClinicsTo.FirstOrDefault(x => x.ClinicNum == _clinicNumTo); if (clinicTo == null) { MsgBox.Show(this, "The clinic could not be found."); return; } Action actionCloseProgress = ODProgressOld.ShowProgressStatus("ClinicReassign", this, Lan.g(this, "Gathering patient data") + "..."); Dictionary <long, List <long> > dictClinicPats = Patients.GetPatNumsByClinic(listClinicsFrom.Select(x => x.ClinicNum).ToList()).Select() .GroupBy(x => PIn.Long(x["ClinicNum"].ToString()), x => PIn.Long(x["PatNum"].ToString())) .ToDictionary(x => x.Key, x => x.ToList()); actionCloseProgress?.Invoke(); int totalPatCount = dictClinicPats.Sum(x => x.Value.Count); if (totalPatCount == 0) { MsgBox.Show(this, "The selected clinics are not clinics for any patients."); return; } string strClinicFromDesc = string.Join(", ", listClinicsFrom.FindAll(x => dictClinicPats.ContainsKey(x.ClinicNum)).Select(x => (x.ClinicNum == 0?"HQ":x.Abbr))); string strClinicToDesc = clinicTo.Abbr; string msg = Lan.g(this, "Move all patients to") + " " + strClinicToDesc + " " + Lan.g(this, "from the following clinics") + ": " + strClinicFromDesc + "?"; if (MessageBox.Show(msg, "", MessageBoxButtons.OKCancel) != DialogResult.OK) { return; } actionCloseProgress = ODProgressOld.ShowProgressStatus("ClinicReassign", this, Lan.g(this, "Moving patients") + "..."); int patsMoved = 0; List <Action> listActions = dictClinicPats.Select(x => new Action(() => { patsMoved += x.Value.Count; ODEvent.Fire(new ODEventArgs("ClinicReassign", Lan.g(this, "Moving patients") + ": " + patsMoved + " out of " + totalPatCount)); Patients.ChangeClinicsForAll(x.Key, clinicTo.ClinicNum); //update all clinicNums to new clinic SecurityLogs.MakeLogEntry(Permissions.PatientEdit, 0, "Clinic changed for " + x.Value.Count + " patients from " + (x.Key == 0 ? "HQ" : Clinics.GetAbbr(x.Key)) + " to " + clinicTo.Abbr + "."); })).ToList(); ODThread.RunParallel(listActions, TimeSpan.FromMinutes(2)); actionCloseProgress?.Invoke(); _dictClinicalCounts = Clinics.GetClinicalPatientCount(); FillGrid(); MsgBox.Show(this, "Done"); }
private void butGetNextBatch_Click(object sender, EventArgs e) { Action actionCloseProgress = ODProgress.Show(ODEventType.Billing, startingMessage: "Retrieving family information. Please wait..."); ODEvent.Fire(ODEventType.Billing, Lan.g(this, "Filling the grid. This may take awhile...")); FillNextBatch(); actionCloseProgress(); }
///<summary>Uses reflection to invoke private methods of the ConvertDatabase class in order from least to greatest if needed. ///The old way of converting the database was to manually daisy chain methods together. ///The new way is to just add a method that follows a strict naming pattern which this method will invoke when needed.</summary> private void InvokeConvertMethods() { DataConnection.CommandTimout = 7200; //2 hours, because conversion commands may take longer to run. //Loop through the list of convert databases methods from front to back because it has already been sorted (least to greatest). foreach (ConvertDatabasesMethodInfo convertMethodInfo in ListConvertMethods) { //This pattern of using reflection to invoke our convert methods started in v17.1 so we will skip all methods prior to that version. if (convertMethodInfo.VersionCur < new Version(17, 1)) { continue; } //Skip all methods that are below or equal to our "from" version. if (convertMethodInfo.VersionCur <= FromVersion) { continue; } //This convert method needs to be invoked. ODEvent.Fire(new ODEventArgs("ConvertDatabases", "Upgrading database to version: " //No translations in convert script. + convertMethodInfo.VersionCur.ToString(3))); //Only show the major, minor, build (preserves old functionality). try { //Use reflection to invoke the private static method. convertMethodInfo.MethodInfoCur.Invoke(this, new object[] { }); } catch (Exception ex) { string message = Lan.g(this, "Convert Database failed "); try { string methodName = convertMethodInfo.MethodInfoCur.Name; if (!string.IsNullOrEmpty(methodName)) { message += Lan.g(this, "during: ") + methodName + "() "; } string command = Db.LastCommand; if (!string.IsNullOrEmpty(command)) { message += Lan.g(this, "while running: ") + command + ";"; } } catch (Exception e) { e.DoNothing(); //If this fails for any reason then just continue. } throw new Exception(message + " " + ex.Message + " " + ex.InnerException.Message, ex.InnerException); } //Update the preference that keeps track of what version Open Dental has successfully upgraded to. //Always require major, minor, build, revision. Will throw an exception if the revision was not explicitly set (which we always set). Prefs.UpdateStringNoCache(PrefName.DataBaseVersion, convertMethodInfo.VersionCur.ToString(4)); } DataConnection.CommandTimout = 3600; //Set back to default of 1 hour. }
/* * ///<summary>Must make sure Refresh is done first. Returns the sum of all adjustments for this patient. Amount might be pos or neg.</summary> * public static double ComputeBal(Adjustment[] List){ * double retVal=0; * for(int i=0;i<List.Length;i++){ * retVal+=List[i].AdjAmt; * } * return retVal; * }*/ ///<summary>Returns the number of finance or billing charges deleted.</summary> public static long UndoFinanceOrBillingCharges(DateTime dateUndo, bool isBillingCharges) { if (RemotingClient.RemotingRole == RemotingRole.ClientWeb) { return(Meth.GetLong(MethodBase.GetCurrentMethod(), dateUndo, isBillingCharges)); } string adjTypeStr = "Finance"; long adjTypeDefNum = PrefC.GetLong(PrefName.FinanceChargeAdjustmentType); if (isBillingCharges) { adjTypeStr = "Billing"; adjTypeDefNum = PrefC.GetLong(PrefName.BillingChargeAdjustmentType); } string command = "SELECT adjustment.AdjAmt,patient.PatNum,patient.Guarantor,patient.LName,patient.FName,patient.Preferred,patient.MiddleI," + "adjustment.SecDateTEdit " + "FROM adjustment " + "INNER JOIN patient ON patient.PatNum=adjustment.PatNum " + "WHERE AdjDate=" + POut.Date(dateUndo) + " " + "AND AdjType=" + POut.Long(adjTypeDefNum); DataTable table = Db.GetTable(command); List <Action> listActions = new List <Action>(); int loopCount = 0; foreach (DataRow row in table.Rows) //loops through the rows and creates audit trail entry for every row to be deleted { listActions.Add(new Action(() => { SecurityLogs.MakeLogEntry(Permissions.AdjustmentEdit, PIn.Long(row["PatNum"].ToString()), "Delete adjustment for patient, undo " + adjTypeStr.ToLower() + " charges: " + Patients.GetNameLF(row["LName"].ToString(), row["FName"].ToString(), row["Preferred"].ToString(), row["MiddleI"].ToString()) + ", " + PIn.Double(row["AdjAmt"].ToString()).ToString("c"), 0, PIn.DateT(row["SecDateTEdit"].ToString())); if (++loopCount % 5 == 0) { ODEvent.Fire(new ODEventArgs(adjTypeStr + "Charge", Lans.g("FinanceCharge", "Creating log entries for " + adjTypeStr.ToLower() + " charges") + ": " + loopCount + " out of " + table.Rows.Count)); } })); } ODThread.RunParallel(listActions, TimeSpan.FromMinutes(2)); command = "DELETE FROM adjustment WHERE AdjDate=" + POut.Date(dateUndo) + " AND AdjType=" + POut.Long(adjTypeDefNum); ODEvent.Fire(new ODEventArgs(adjTypeStr + "Charge", Lans.g("FinanceCharge", "Deleting") + " " + table.Rows.Count + " " + Lans.g("FinanceCharge", adjTypeStr.ToLower() + " charge adjustments") + "...")); return(Db.NonQ(command)); }
private void menuItemGridGoToAccount_Click(object sender, EventArgs e) { //accessed by right clicking the history grid if (gridMain.SelectedIndices.Length != 1) { MsgBox.Show(this, "Please select exactly one item first."); return; } DataRow row = (DataRow)gridMain.ListGridRows[gridMain.GetSelectedIndex()].Tag; long patNum = PIn.Long(row["PatNum"].ToString()); if (patNum == 0) { MsgBox.Show(this, "Please select an item with a patient."); return; } ODEvent.Fire(ODEventType.FormProcNotBilled_GoTo, patNum); SendToBack(); }
private void FillSubGrid(bool isRefreshNeeded = false, string grouping95 = "") { Action loadingProgress = null; Cursor = Cursors.WaitCursor; bugSubmissionControl.ClearCustomerInfo(); bugSubmissionControl.SetTextDevNoteEnabled(false); if (isRefreshNeeded) { loadingProgress = ODProgressOld.ShowProgressStatus("FormBugSubmissions", this, Lan.g(this, "Refreshing Data") + "...", false); #region Refresh Logic if (_viewMode.In(FormBugSubmissionMode.ViewOnly, FormBugSubmissionMode.ValidationMode)) { _listAllSubs = ListViewedSubs; } else { _listAllSubs = BugSubmissions.GetAllInRange(dateRangePicker.GetDateTimeFrom(), dateRangePicker.GetDateTimeTo()); } try { _dictPatients = RegistrationKeys.GetPatientsByKeys(_listAllSubs.Select(x => x.RegKey).ToList()); } catch (Exception e) { e.DoNothing(); _dictPatients = new Dictionary <string, Patient>(); } #endregion } gridSubs.BeginUpdate(); #region gridSubs columns gridSubs.Columns.Clear(); gridSubs.Columns.Add(new ODGridColumn("Submitter", 140)); gridSubs.Columns.Add(new ODGridColumn("Vers.", 55, GridSortingStrategy.VersionNumber)); if (comboGrouping.SelectedIndex == 0) //Group by 'None' { gridSubs.Columns.Add(new ODGridColumn("DateTime", 75, GridSortingStrategy.DateParse)); } else { gridSubs.Columns.Add(new ODGridColumn("Count", 75, GridSortingStrategy.AmountParse)); } gridSubs.Columns.Add(new ODGridColumn("HasBug", 50, HorizontalAlignment.Center)); gridSubs.Columns.Add(new ODGridColumn("Msg Text", 300)); gridSubs.AllowSortingByColumn = true; #endregion #region Filter Logic ODEvent.Fire(new ODEventArgs("FormBugSubmissions", "Filtering Data")); List <string> listSelectedVersions = comboVersions.ListSelectedItems.Select(x => (string)x).ToList(); if (listSelectedVersions.Contains("All")) { listSelectedVersions.Clear(); } List <string> listSelectedRegKeys = comboRegKeys.ListSelectedItems.Select(x => (string)x).ToList(); if (listSelectedRegKeys.Contains("All")) { listSelectedRegKeys.Clear(); } List <string> listStackFilters = textStackFilter.Text.Split(',') .Where(x => !string.IsNullOrWhiteSpace(x)) .Select(x => x.ToLower()).ToList(); List <string> listPatNumFilters = textPatNums.Text.Split(',') .Where(x => !string.IsNullOrWhiteSpace(x)) .Select(x => x.ToLower()).ToList(); _listAllSubs.ForEach(x => x.TagOD = null); List <BugSubmission> listFilteredSubs = _listAllSubs.Where(x => PassesFilterValidation(x, listSelectedRegKeys, listStackFilters, listPatNumFilters, listSelectedVersions, grouping95) ).ToList(); if (isRefreshNeeded) { FillVersionsFilter(listFilteredSubs); FillRegKeyFilter(listFilteredSubs); } #endregion #region Grouping Logic List <BugSubmission> listGroupedSubs; int index = 0; List <BugSubmission> listGridSubmissions = new List <BugSubmission>(); foreach (BugSubmission sub in listFilteredSubs) { ODEvent.Fire(new ODEventArgs("FormBugSubmissions", "Grouping Data: " + POut.Double(((double)index++ / (double)listFilteredSubs.Count) * 100) + "%")); if (sub.TagOD != null) { continue; } switch (comboGrouping.SelectedIndex) { case 0: #region None sub.TagOD = new List <BugSubmission>() { sub }; //Tag is a specific bugSubmission listGridSubmissions.Add(sub.Copy()); #endregion break; case 1: #region RegKey/Ver/Stack listGroupedSubs = listFilteredSubs.FindAll(x => x.TagOD == null && x.RegKey == sub.RegKey && x.Info.DictPrefValues[PrefName.ProgramVersion] == sub.Info.DictPrefValues[PrefName.ProgramVersion] && x.ExceptionStackTrace == sub.ExceptionStackTrace && x.BugId == sub.BugId); if (listGroupedSubs.Count == 0) { continue; } listGroupedSubs = listGroupedSubs.OrderByDescending(x => new Version(x.Info.DictPrefValues[PrefName.ProgramVersion])) .ThenByDescending(x => x.SubmissionDateTime).ToList(); listGroupedSubs.ForEach(x => x.TagOD = true); //So we don't considered previously handled submissions. listGroupedSubs.First().TagOD = listGroupedSubs; //First element is what is shown in grid, still wont be considered again. listGridSubmissions.Add(listGroupedSubs.First().Copy()); #endregion break; case 2: #region StackTrace listGroupedSubs = listFilteredSubs.FindAll(x => x.TagOD == null && x.ExceptionStackTrace == sub.ExceptionStackTrace && x.BugId == sub.BugId); if (listGroupedSubs.Count == 0) { continue; } listGroupedSubs = listGroupedSubs.OrderByDescending(x => new Version(x.Info.DictPrefValues[PrefName.ProgramVersion])) .ThenByDescending(x => x.SubmissionDateTime).ToList(); listGroupedSubs.ForEach(x => x.TagOD = true); //So we don't considered previously handled submissions. listGroupedSubs.First().TagOD = listGroupedSubs; //First element is what is shown in grid, still wont be considered again. listGridSubmissions.Add(listGroupedSubs.First().Copy()); #endregion break; case 3: #region 95% //At this point all bugSubmissions in listFilteredSubs is at least a 95% match. Group them all together in a single row. listGroupedSubs = listFilteredSubs; listGroupedSubs = listGroupedSubs.OrderByDescending(x => new Version(x.Info.DictPrefValues[PrefName.ProgramVersion])) .ThenByDescending(x => x.SubmissionDateTime).ToList(); listGroupedSubs.ForEach(x => x.TagOD = true); //So we don't considered previously handled submissions. listGroupedSubs.First().TagOD = listGroupedSubs; //First element is what is shown in grid, still wont be considered again. listGridSubmissions.Add(listGroupedSubs.First().Copy()); #endregion break; } } #endregion #region Sorting Logic ODEvent.Fire(new ODEventArgs("FormBugSubmissions", "Sorting Data")); switch (comboSortBy.SelectedIndex) { case 0: listGridSubmissions = listGridSubmissions.OrderByDescending(x => new Version(x.Info.DictPrefValues[PrefName.ProgramVersion])) .ThenByDescending(x => GetGroupCount(x)) .ThenByDescending(x => x.SubmissionDateTime).ToList(); break; } #endregion #region Fill gridSubs gridSubs.Rows.Clear(); index = 0; foreach (BugSubmission sub in listGridSubmissions) { ODEvent.Fire(new ODEventArgs("FormBugSubmissions", "Filling Grid: " + POut.Double(((double)index++ / (double)listFilteredSubs.Count) * 100) + "%")); gridSubs.Rows.Add(GetODGridRowForSub(sub)); } gridSubs.EndUpdate(); #endregion try { loadingProgress?.Invoke(); //When this function executes quickly this can fail rarely, fail silently because of WaitCursor. } catch (Exception ex) { ex.DoNothing(); } Cursor = Cursors.Default; }
///<summary>Uses reflection to invoke private methods of the ConvertDatabase class in order from least to greatest if needed. ///The old way of converting the database was to manually daisy chain methods together. ///The new way is to just add a method that follows a strict naming pattern which this method will invoke when needed.</summary> public static void InvokeConvertMethods() { DataConnection.CommandTimeout = 43200; //12 hours, because conversion commands may take longer to run. ConvertDatabases.To2_8_2(); //begins going through the chain of conversion steps Logger.DoVerboseLoggingArgs doVerboseLogging = Logger.DoVerboseLogging; ODException.SwallowAnyException(() => { //Need to run queries here because PrefC has not been initialized. string command = "SELECT ValueString FROM preference WHERE PrefName='HasVerboseLogging'"; string valueString = Db.GetScalar(command); if (valueString.ToLower().Split(',').ToList().Exists(x => x == Environment.MachineName.ToLower())) { Logger.DoVerboseLogging = () => true; //Switch logger to a directory that won't have permissions issues. Logger.UseMyDocsDirectory(); } Logger.LogVerbose("Starting convert script"); }); //Continue going through the chain of conversion methods starting at v17.1.1 via reflection. //Loop through the list of convert databases methods from front to back because it has already been sorted (least to greatest). foreach (ConvertDatabasesMethodInfo convertMethodInfo in ListConvertMethods) { //This pattern of using reflection to invoke our convert methods started in v17.1 so we will skip all methods prior to that version. if (convertMethodInfo.VersionCur < new Version(17, 1)) { continue; } //Skip all methods that are below or equal to our "from" version. if (convertMethodInfo.VersionCur <= FromVersion) { continue; } //This convert method needs to be invoked. ODEvent.Fire(ODEventType.ConvertDatabases, "Upgrading database to version: " //No translations in convert script. + convertMethodInfo.VersionCur.ToString(3)); //Only show the major, minor, build (preserves old functionality). try { //Use reflection to invoke the private static method. convertMethodInfo.MethodInfoCur.Invoke(null, new object[] { }); } catch (Exception ex) { string message = Lans.g("ClassConvertDatabase", "Convert Database failed "); try { string methodName = convertMethodInfo.MethodInfoCur.Name; if (!string.IsNullOrEmpty(methodName)) { message += Lans.g("ClassConvertDatabase", "during: ") + methodName + "() "; } string command = Db.LastCommand; if (!string.IsNullOrEmpty(command)) { message += Lans.g("ClassConvertDatabase", "while running: ") + command + ";"; } } catch (Exception e) { e.DoNothing(); //If this fails for any reason then just continue. } throw new Exception(message + " " + ex.Message + " " + ex.InnerException.Message, ex.InnerException); } //Update the preference that keeps track of what version Open Dental has successfully upgraded to. //Always require major, minor, build, revision. Will throw an exception if the revision was not explicitly set (which we always set). Prefs.UpdateStringNoCache(PrefName.DataBaseVersion, convertMethodInfo.VersionCur.ToString(4)); } ODException.SwallowAnyException(() => { Logger.LogVerbose("Ending convert script"); Logger.DoVerboseLogging = doVerboseLogging; }); DataConnection.CommandTimeout = 3600; //Set back to default of 1 hour. }
///<summary>Balances all selected accounts.</summary> private void butTransfer_Click(object sender, EventArgs e) { //FormIncomeTransferManage requires PaymentCreate to run. //This form requires SecurityAdmin and a password to open, and although rare, // a SecuirtyAdmin doesn't have to have PaymentCreate permission if (!Security.IsAuthorized(Permissions.PaymentCreate)) { return; } //Make sure the user wants to run the tool. if (!MsgBox.Show(this, MsgBoxButtons.YesNo, "This process can take a long time and cannot be reversed.\r\n\r\nContinue?")) { return; } Action actionCloseProgress = ODProgress.Show(ODEventType.Billing); //Build list of families based off of selected rows. _logger.WriteLine("This is the summary of all transactions that took place.This can be saved for later reference if one or more" + " transactions need to be undone outside of the tool.\r\n", LogLevel.Information); //Do income transfers if applicable. for (int i = _batchNum - 1; i < _listBatches.Count(); i++) //_batchNum is 1-based, so drop i by 1 to make sure the current batch is included { string logText = ""; if (checkAllocateCharges.Checked) { ODEvent.Fire(ODEventType.Billing, Lan.g(this, $"Creating income transfers for batch {_batchNum}/{_listBatches.Count()} please wait...")); foreach (FamilyAccount famAccountCur in _dictCurrentFamilyBatch.Select(x => x.Value)) { //Clear the list of splits in case any are hanging around from a previous run. famAccountCur.ListSplits.Clear(); famAccountCur.ListSplitsAssociated.Clear(); //Make lists of positive and negative charges. List <AccountEntry> listPosCharges = famAccountCur.Account.ListAccountCharges.Where(x => x.AmountEnd > 0).ToList(); List <AccountEntry> listNegCharges = famAccountCur.Account.ListAccountCharges.Where(x => x.AmountEnd < 0).ToList(); List <long> listPatNumsForCharges = listPosCharges.Select(x => x.PatNum).Distinct().ToList(); List <AccountEntry> listEntriesForPats = famAccountCur.Account.ListAccountCharges.FindAll(x => x.PatNum.In(listPatNumsForCharges)); //This catch will save us some time if they run the tool on the same family twice. if (listPosCharges.Count() == 0 || listNegCharges.Count() == 0) { continue; //No need to add to logText, CreateTransfers wouldn't return anything either. } Payment payCur = CreatePaymentTransferHelper(famAccountCur.Guarantor); logText += CreateTransfers(listPosCharges, listNegCharges, listEntriesForPats, famAccountCur, payCur); logText += CreditsToUnallocated(listNegCharges, famAccountCur, payCur); //Remove any $0 splits created from CreateTransfers. famAccountCur.ListSplits.RemoveAll(x => x.SplitAmt == 0); foreach (PaySplit split in famAccountCur.ListSplits) { PaySplits.Insert(split); } //Go through family accounts and update FSplitNums. foreach (PaySplits.PaySplitAssociated split in famAccountCur.ListSplitsAssociated) { //Update the FSplitNum after inserts are made. if (split.PaySplitLinked != null && split.PaySplitOrig != null) { PaySplits.UpdateFSplitNum(split.PaySplitOrig.SplitNum, split.PaySplitLinked.SplitNum); } } } } //Transfer balances to guarantor if applicable. if (checkGuarAllocate.Checked) { ODEvent.Fire(ODEventType.Billing, Lan.g(this, $"Transferring remaining balance to guarantor for batch {_batchNum}/{_listBatches.Count()} please wait...")); logText += "Balances transferred to Guarantor:\r\n"; logText += TransferToGuarantor(); } //load up the next batch ODEvent.Fire(ODEventType.Billing, Lan.g(this, $"Loading next batch please wait...")); FillNextBatch(); _logger.WriteLine(logText, LogLevel.Information); } actionCloseProgress(); }
protected override void OnClosed(EventArgs e) { base.OnClosed(e); ODEvent.Fire(new ODEventArgs("ErxBrowserClosed", PatCur)); }
///<summary>Creates actions and runs them in parallel threads to process the insert commands in the queue.</summary> public static void InsertBatches() { if (RemotingClient.RemotingRole == RemotingRole.ClientWeb) { Meth.GetVoid(MethodBase.GetCurrentMethod()); return; } #if DEBUG Stopwatch s = new Stopwatch(); s.Start(); #endif _insertBatchCount = 0; try { #region Create List of Actions List <Action> listActions = new List <Action>(); int numThreads = Math.Max(INSERT_THREAD_MIN_COUNT, Environment.ProcessorCount); //use at least 8 threads, but use ProcessorCount if more than 8 cores for (int i = 0; i < numThreads; i++) //create numThreads number of actions, 1 per thread to run in parallel { listActions.Add(new Action(() => { if (!string.IsNullOrEmpty(_serverTo)) //SetDbT here if server is specified { DataConnection dcon = new DataConnection(); dcon.SetDbT(_serverTo, _databaseTo, _userTo, _passwordTo, "", "", DatabaseType.MySql); } bool isBatchQueued = false; bool insertFailed = true; while (!_areQueueBatchThreadsDone || isBatchQueued) //if queue batch thread is done and queue is empty, loop is finished { BatchQueries batch = null; try { lock (_lockObjQueueBatchQueries) { if (_queueBatchQueries.Count == 0) { //queueBatchThread must not be finished gathering batches but the queue is empty, give the batch thread time to catch up continue; } batch = _queueBatchQueries.Dequeue(); } if (batch == null || (string.IsNullOrEmpty(batch.CommandValuesInsert) && string.IsNullOrEmpty(batch.CommandBulkInsert))) { continue; } Db.NonQ(batch.CommandValuesInsert); insertFailed = false; } catch (Exception ex) { //just loop again and wait if necessary ex.DoNothing(); insertFailed = true; if (!string.IsNullOrEmpty(batch.CommandBulkInsert)) { try { //If multiple bulk insert commands get here at the same time they will fail 100% of the time for InnoDB an table due to //a MySQL deadlock issue caused by the sub-select that makes sure it is not trying to insert duplicate rows. Db.NonQ(batch.CommandBulkInsert); insertFailed = false; } catch (Exception ex2) { ex2.DoNothing(); insertFailed = true; } } continue; } finally { lock (_lockObjQueueBatchQueries) { if (!insertFailed) { insertFailed = true; _insertBatchCount++; } isBatchQueued = _queueBatchQueries.Count > 0; } } } //end of while loop })); //end of listActions.Add } //end of for loop #endregion Create List of Actions ODThread.RunParallel(listActions, TimeSpan.FromHours(12), numThreads, new ODThread.ExceptionDelegate((ex) => { ODEvent.Fire(ODEventType.ConvertDatabases, new ProgressBarHelper("Error processing batch insert: " + ex.Message, progressBarEventType: ProgBarEventType.TextMsg)); })); } catch (Exception ex) { ODEvent.Fire(ODEventType.ConvertDatabases, new ProgressBarHelper("Error inserting batch: " + ex.Message, progressBarEventType: ProgBarEventType.TextMsg)); } #if DEBUG s.Stop(); Console.WriteLine("InsertDataThread - Done, inserted " + _insertBatchCount + " batches: " + (s.Elapsed.Hours > 0?(s.Elapsed.Hours + " hours "):"") + (s.Elapsed.Minutes > 0?(s.Elapsed.Minutes + " min "):"") + (s.Elapsed.TotalSeconds - (s.Elapsed.Hours * 60 * 60) - (s.Elapsed.Minutes * 60)) + " sec"); #endif }
///<summary>Creates actions that load batches of data into the queue for inserting by the insert threads and runs them with ///QUEUE_BATCHES_THREAD_COUNT number of parallel threads. The threads will wait for the queue to drop below MAX_QUEUE_COUNT number of items ///before queuing another item.</summary> public static void QueueBatches(ODThread odThread) { if (RemotingClient.RemotingRole == RemotingRole.ClientWeb) { Meth.GetVoid(MethodBase.GetCurrentMethod(), odThread); return; } #if DEBUG Stopwatch s = new Stopwatch(); s.Start(); #endif int queueCount = 0; try { string dbName = GetCurrentDatabase(); string cmd = "SELECT COLUMN_NAME,DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS " + "WHERE TABLE_SCHEMA='" + POut.String(dbName) + "' AND TABLE_NAME='" + POut.String(_tempTableName) + "'"; //Dictionary of key=column name, value=data type for the current table. Used to determine whether a row value will need to have special //characters escaped and will need to be surrounded with quotes (using the MySql QUOTE() method). Dictionary <string, string> dictColNamesAndTypes = Db.GetTable(cmd).Select() .ToDictionary(x => PIn.String(x["COLUMN_NAME"].ToString()), x => PIn.String(x["DATA_TYPE"].ToString())); if (dictColNamesAndTypes.Count < 1) { return; //table doesn't have any columns? nothing to do? } #region Get Query Strings //data types that require special characters escaped and will be surrounded by quotes (using the MySql QUOTE() method). string[] dataTypeQuotesArr = new[] { "date", "datetime", "timestamp", "time", "char", "varchar", "text", "mediumtext", "longtext", "blob", "mediumblob", "longblob" }; StringBuilder sbGetSelectCommand = new StringBuilder(@"SELECT CONCAT('('"); List <string> listWheres = new List <string>(); int index = 0; foreach (KeyValuePair <string, string> kvp in dictColNamesAndTypes) { sbGetSelectCommand.Append(@","); if (index > 0) { sbGetSelectCommand.Append(@"',',"); } if (dataTypeQuotesArr.Contains(kvp.Value)) { sbGetSelectCommand.Append(@"QUOTE(" + kvp.Key + @")"); } else { sbGetSelectCommand.Append(POut.String(kvp.Key)); } index++; } sbGetSelectCommand.Append(@",')') vals FROM `" + _tempTableName + "` "); for (int i = 0; i < _listPriKeyMaxPerBatch.Count; i++) { string where = "WHERE " + POut.String(_tablePriKeyField) + "<=" + POut.Long(_listPriKeyMaxPerBatch[i]); if (i > 0) { where += " AND " + POut.String(_tablePriKeyField) + ">" + POut.Long(_listPriKeyMaxPerBatch[i - 1]); } listWheres.Add(where); } #endregion Get Query Strings #region Run Commands and Queue Results #region Create List of Actions List <Action> listActions = new List <Action>(); string colNames = string.Join(",", dictColNamesAndTypes.Keys.Select(x => POut.String(x))); foreach (string whereStr in listWheres) { listActions.Add(new Action(() => { List <string> listRowVals = Db.GetListString(sbGetSelectCommand.ToString() + whereStr); if (listRowVals == null || listRowVals.Count == 0) { return; } string commandValuesInsert = "REPLACE INTO `" + _tableName + "` (" + colNames + ") VALUES " + string.Join(",", listRowVals); string commandBulkInsert = "REPLACE INTO `" + _tableName + "` (" + colNames + ") SELECT " + colNames + " FROM `" + _tempTableName + "` " + whereStr + " " + "AND " + POut.String(_tablePriKeyField) + " NOT IN (SELECT " + POut.String(_tablePriKeyField) + " FROM `" + _tableName + "` " + whereStr + ")"; bool isDataQueued = false; while (!isDataQueued) { lock (_lockObjQueueBatchQueries) { if (_queueBatchQueries.Count < MAX_QUEUE_COUNT) //Wait until queue is a reasonable size before queueing more. { _queueBatchQueries.Enqueue(new BatchQueries(commandValuesInsert, commandBulkInsert)); isDataQueued = true; queueCount++; } } if (!isDataQueued) { Thread.Sleep(100); } } })); } //end of command loop #endregion Create List of Actions ODThread.RunParallel(listActions, TimeSpan.FromHours(12), QUEUE_BATCHES_THREAD_COUNT, new ODThread.ExceptionDelegate((ex) => { ODEvent.Fire(ODEventType.ConvertDatabases, new ProgressBarHelper("Error queuing batch: " + ex.Message, progressBarEventType: ProgBarEventType.TextMsg)); })); #endregion Run Commands and Queue Results } catch (Exception ex) { //Don't pass along any exceptions because the main thread will validate that the table was successfully copied and will throw for us. ODEvent.Fire(ODEventType.ConvertDatabases, new ProgressBarHelper("Error queuing batch: " + ex.Message, progressBarEventType: ProgBarEventType.TextMsg)); } finally { //always make sure to notify the main thread that the thread is done so the main thread doesn't wait for eternity _areQueueBatchThreadsDone = true; #if DEBUG s.Stop(); Console.WriteLine("QueueQueryBatches - Done, queued " + queueCount + " out of " + _listPriKeyMaxPerBatch.Count + " batches of " + _rowsPerBatch + " rows: " + (s.Elapsed.Hours > 0?(s.Elapsed.Hours + " hours "):"") + (s.Elapsed.Minutes > 0?(s.Elapsed.Minutes + " min "):"") + (s.Elapsed.TotalSeconds - (s.Elapsed.Hours * 60 * 60) - (s.Elapsed.Minutes * 60)) + " sec"); #endif } }
///<summary>Backs up the database to the same directory as the original just in case the user did not have sense enough to do a backup first. ///Does not work for Oracle, due to some MySQL specific commands inside.</summary> public static long MakeABackup() { //This function should always make the backup on the server itself, and since no directories are //referred to (all handled with MySQL), this function will always be referred to the server from //client machines. if (RemotingClient.RemotingRole == RemotingRole.ClientWeb) { return(Meth.GetLong(MethodBase.GetCurrentMethod())); } //UpdateStreamLinePassword is purposefully named poorly and used in an odd fashion to sort of obfuscate it from our users. //GetStringNoCache() will return blank if pref does not exist. if (PrefC.GetStringNoCache(PrefName.UpdateStreamLinePassword) == "abracadabra") { return(0); } //only used in two places: upgrading version, and upgrading mysql version. //Both places check first to make sure user is using mysql. //we have to be careful to throw an exception if the backup is failing. DataConnection dcon = new DataConnection(); string command = "SELECT database()"; DataTable table = dcon.GetTable(command); string oldDb = PIn.String(table.Rows[0][0].ToString()); string newDb = oldDb + "backup_" + DateTime.Today.ToString("MM_dd_yyyy"); command = "SHOW DATABASES"; table = dcon.GetTable(command); string[] databases = new string[table.Rows.Count]; for (int i = 0; i < table.Rows.Count; i++) { databases[i] = table.Rows[i][0].ToString(); } if (Contains(databases, newDb)) //if the new database name already exists //find a unique one { int uniqueID = 1; string originalNewDb = newDb; do { newDb = originalNewDb + "_" + uniqueID.ToString(); uniqueID++; }while(Contains(databases, newDb)); } command = "CREATE DATABASE `" + newDb + "` CHARACTER SET utf8"; dcon.NonQ(command); command = "SHOW FULL TABLES WHERE Table_type='BASE TABLE'"; //Tables, not views. Does not work in MySQL 4.1, however we test for MySQL version >= 5.0 in PrefL. table = dcon.GetTable(command); string[] tableName = new string[table.Rows.Count]; for (int i = 0; i < table.Rows.Count; i++) { tableName[i] = table.Rows[i][0].ToString(); } //switch to using the new database DataConnection newDcon = new DataConnection(newDb); for (int i = 0; i < tableName.Length; i++) { //Alert anyone that cares that we are backing up this table. ODEvent.Fire(new ODEventArgs("BackupProgress", Lans.g("MiscData", "Backing up table") + ": " + tableName[i])); command = "SHOW CREATE TABLE `" + oldDb + "`.`" + tableName[i] + "`"; //also works with views. Added backticks around table name for unusual characters. table = newDcon.GetTable(command); command = PIn.ByteArray(table.Rows[0][1]); newDcon.NonQ(command); //this has to be run using connection with new database command = "INSERT INTO `" + newDb + "`.`" + tableName[i] + "` " + "SELECT * FROM `" + oldDb + "`.`" + tableName[i] + "`"; //Added backticks around table name for unusual characters. newDcon.NonQ(command); } return(0); }
protected override void OnClosed(EventArgs e) { base.OnClosed(e); ODEvent.Fire(ODEventType.ErxBrowserClosed, PatCur); }
///<summary>Fills grid based on values in _listEtrans. ///Set isRefreshNeeded to true when we need to reinitialize local dictionarys after in memory list is also updated. Required true for first time running. ///Also allows you to passed in predetermined filter options.</summary> private void FillGrid(bool isRefreshNeeded, List <string> listSelectedStatuses, List <long> listSelectedClinicNums, string carrierName, string checkTraceNum, string amountMin, string amountMax) { Action actionCloseProgress = null; if (isRefreshNeeded) { actionCloseProgress = ODProgressOld.ShowProgressStatus("Etrans835", this, Lan.g(this, "Gathering data") + "...", false); _dictEtrans835s.Clear(); _dictEtransClaims.Clear(); _dictClaimPayExists.Clear(); List <Etrans835Attach> listAttached = Etrans835Attaches.GetForEtrans(_listEtranss.Select(x => x.EtransNum).ToArray()); Dictionary <long, string> dictEtransMessages = new Dictionary <long, string>(); List <X12ClaimMatch> list835ClaimMatches = new List <X12ClaimMatch>(); Dictionary <long, int> dictClaimMatchCount = new Dictionary <long, int>(); //1:1 with _listEtranss. Stores how many claim matches each 835 has. int batchQueryInterval = 500; //Every 500 rows we get the next 500 message texts to save memory. int rowCur = 0; foreach (Etrans etrans in _listEtranss) { if (rowCur % batchQueryInterval == 0) { int range = Math.Min(batchQueryInterval, _listEtranss.Count - rowCur); //Either the full batchQueryInterval amount or the remaining amount of etrans. dictEtransMessages = EtransMessageTexts.GetMessageTexts(_listEtranss.GetRange(rowCur, range).Select(x => x.EtransMessageTextNum).ToList(), false); } rowCur++; ODEvent.Fire(new ODEventArgs("Etrans835", Lan.g(this, "Processing 835: ") + ": " + rowCur + " out of " + _listEtranss.Count)); List <Etrans835Attach> listAttachedTo835 = listAttached.FindAll(x => x.EtransNum == etrans.EtransNum); X835 x835 = new X835(etrans, dictEtransMessages[etrans.EtransMessageTextNum], etrans.TranSetId835, listAttachedTo835, true); _dictEtrans835s.Add(etrans.EtransNum, x835); List <X12ClaimMatch> listClaimMatches = x835.GetClaimMatches(); dictClaimMatchCount.Add(etrans.EtransNum, listClaimMatches.Count); list835ClaimMatches.AddRange(listClaimMatches); } #region Set 835 unattached in batch and build _dictEtransClaims and _dictClaimPayCheckNums. ODEvent.Fire(new ODEventArgs("Etrans835", Lan.g(this, "Gathering internal claim matches."))); List <long> listClaimNums = Claims.GetClaimFromX12(list835ClaimMatches); //Can return null. ODEvent.Fire(new ODEventArgs("Etrans835", Lan.g(this, "Building data sets."))); int claimIndexCur = 0; List <long> listMatchedClaimNums = new List <long>(); foreach (Etrans etrans in _listEtranss) { X835 x835 = _dictEtrans835s[etrans.EtransNum]; if (listClaimNums != null) { x835.SetClaimNumsForUnattached(listClaimNums.GetRange(claimIndexCur, dictClaimMatchCount[etrans.EtransNum])); } claimIndexCur += dictClaimMatchCount[etrans.EtransNum]; listMatchedClaimNums.AddRange(x835.ListClaimsPaid.FindAll(x => x.ClaimNum != 0).Select(x => x.ClaimNum).ToList()); } List <Claim> listClaims = Claims.GetClaimsFromClaimNums(listMatchedClaimNums.Distinct().ToList()); _dictClaimPayExists = ClaimPayments.HasClaimPayment(listMatchedClaimNums); //Every claim num is associated to a bool. True when there is an existing claimPayment. foreach (Etrans etrans in _listEtranss) { X835 x835 = _dictEtrans835s[etrans.EtransNum]; #region _dictEtransClaims, _dictClaimPayCheckNums _dictEtransClaims.Add(etrans.EtransNum, new List <Claim>()); List <long> listSubClaimNums = x835.ListClaimsPaid.FindAll(x => x.ClaimNum != 0).Select(y => y.ClaimNum).ToList(); List <Claim> listClaimsFor835 = listClaims.FindAll(x => listSubClaimNums.Contains(x.ClaimNum)); foreach (Hx835_Claim claim in x835.ListClaimsPaid) { Claim claimCur = listClaimsFor835.FirstOrDefault(x => x.ClaimNum == claim.ClaimNum); //Can be null. if (claimCur == null && claim.IsAttachedToClaim && claim.ClaimNum == 0) { claimCur = new Claim(); //Create empty claim since user detached claim manually, will not be considered in GetStringStatus(...). } if (claimCur != null && claim.IsPreauth) //User attached preauth to internal claim, no payment needed to be considered 'Finalized' in GetStringStatus(...). { _dictClaimPayExists[claim.ClaimNum] = true; } _dictEtransClaims[etrans.EtransNum].Add(claimCur); } #endregion } ODEvent.Fire(new ODEventArgs("Etrans835", Lan.g(this, "Filling Grid."))); #endregion } gridMain.BeginUpdate(); #region Initilize columns only once if (gridMain.Columns.Count == 0) { ODGridColumn col; col = new ODGridColumn(Lan.g("TableEtrans835s", "Patient Name"), 250); gridMain.Columns.Add(col); col = new ODGridColumn(Lan.g("TableEtrans835s", "Carrier Name"), 190); gridMain.Columns.Add(col); col = new ODGridColumn(Lan.g("TableEtrans835s", "Status"), 80); gridMain.Columns.Add(col); col = new ODGridColumn(Lan.g("TableEtrans835s", "Date"), 80); gridMain.Columns.Add(col); col = new ODGridColumn(Lan.g("TableEtrans835s", "Amount"), 80); gridMain.Columns.Add(col); if (PrefC.HasClinicsEnabled) { col = new ODGridColumn(Lan.g("TableEtrans835s", "Clinic"), 70); gridMain.Columns.Add(col); } col = new ODGridColumn(Lan.g("TableEtrans835s", "Code"), 37, HorizontalAlignment.Center); gridMain.Columns.Add(col); col = new ODGridColumn(Lan.g("TableEtrans835s", "Note"), 0); gridMain.Columns.Add(col); } #endregion gridMain.Rows.Clear(); foreach (Etrans etrans in _listEtranss) { X835 x835 = _dictEtrans835s[etrans.EtransNum]; #region Filter: Carrier Name if (carrierName != "" && !x835.PayerName.ToLower().Contains(carrierName.ToLower())) { continue; } #endregion string status = GetStringStatus(etrans.EtransNum); #region Filter: Status if (!listSelectedStatuses.Contains(status.Replace("*", ""))) //The filter will ignore finalized with detached claims. { continue; } #endregion //List of ClinicNums for the current etrans.ListClaimsPaid from the DB. List <long> listClinicNums = _dictEtransClaims[etrans.EtransNum].Select(x => x == null? 0 :x.ClinicNum).Distinct().ToList(); #region Filter: Clinics if (PrefC.HasClinicsEnabled && !listClinicNums.Exists(x => listSelectedClinicNums.Contains(x))) { continue; //The ClinicNums associated to the 835 do not match any of the selected ClinicNums, so nothing to show in this 835. } #endregion #region Filter: Check and Trace Value if (checkTraceNum != "" && !x835.TransRefNum.Contains(checkTraceNum)) //Trace Number does not match { continue; } #endregion #region Filter: Insurance Check Range Min and Max if (amountMin != "" && x835.InsPaid < PIn.Decimal(amountMin) || amountMax != "" && x835.InsPaid > PIn.Decimal(amountMax)) { continue; //Either the InsPaid is below or above our range. } #endregion ODGridRow row = new ODGridRow(); #region Column: Patient Name List <string> listPatNames = x835.ListClaimsPaid.Select(x => x.PatientName.ToString()).Distinct().ToList(); string patName = (listPatNames.Count > 0 ? listPatNames[0] : ""); if (listPatNames.Count > 1) { patName = "(" + POut.Long(listPatNames.Count) + ")"; } row.Cells.Add(patName); #endregion row.Cells.Add(x835.PayerName); row.Cells.Add(status); //See GetStringStatus(...) for possible values. row.Cells.Add(POut.Date(etrans.DateTimeTrans)); row.Cells.Add(POut.Decimal(x835.InsPaid)); #region Column: Clinic if (PrefC.HasClinicsEnabled) { string clinicAbbr = ""; if (listClinicNums.Count == 1) { if (listClinicNums[0] == 0) { clinicAbbr = Lan.g(this, "Unassigned"); } else { clinicAbbr = Clinics.GetAbbr(listClinicNums[0]); } } else if (listClinicNums.Count > 1) { clinicAbbr = "(" + Lan.g(this, "Multiple") + ")"; } row.Cells.Add(clinicAbbr); } #endregion row.Cells.Add(x835._paymentMethodCode); row.Cells.Add(etrans.Note); row.Tag = etrans; gridMain.Rows.Add(row); } gridMain.EndUpdate(); actionCloseProgress?.Invoke(); }
private void butOK_Click(object sender, System.EventArgs e) { if (textDate.errorProvider1.GetError(textDate) != "" || textAPR.errorProvider1.GetError(textAPR) != "" || textAtLeast.errorProvider1.GetError(textAtLeast) != "" || textOver.errorProvider1.GetError(textOver) != "") { MsgBox.Show(this, "Please fix data entry errors first."); return; } DateTime date = PIn.Date(textDate.Text); if (PrefC.GetDate(PrefName.FinanceChargeLastRun).AddDays(25) > date) { if (!MsgBox.Show(this, true, "Warning. Finance charges should not be run more than once per month. Continue?")) { return; } } else if (PrefC.GetDate(PrefName.BillingChargeLastRun).AddDays(25) > date) { if (!MsgBox.Show(this, true, "Warning. Billing charges should not be run more than once per month. Continue?")) { return; } } if (listBillType.SelectedIndices.Count == 0) { MsgBox.Show(this, "Please select at least one billing type first."); return; } if (PIn.Long(textAPR.Text) < 2) { if (!MsgBox.Show(this, true, "The APR is much lower than normal. Do you wish to proceed?")) { return; } } if (PrefC.GetBool(PrefName.AgingCalculatedMonthlyInsteadOfDaily) && PrefC.GetDate(PrefName.DateLastAging).AddMonths(1) <= DateTime.Today) { if (!MsgBox.Show(this, MsgBoxButtons.OKCancel, "It has been more than a month since aging has been run. It is recommended that you update the " + "aging date and run aging before continuing.")) { return; } //we might also consider a warning if textDate.Text does not match DateLastAging. Probably not needed for daily aging, though. } string chargeType = (radioFinanceCharge.Checked?"Finance":"Billing");//For display only List <long> listSelectedBillTypes = listBillType.SelectedIndices.OfType <int>().Select(x => _listBillingTypeDefs[x].DefNum).ToList(); Action actionCloseProgress = null; int chargesAdded = 0; try { actionCloseProgress = ODProgressOld.ShowProgressStatus("FinanceCharge", this, Lan.g(this, "Gathering patients with aged balances") + "..."); List <PatAging> listPatAgings = Patients.GetAgingListSimple(listSelectedBillTypes, new List <long> { }); //Ordered by PatNum, for thread concurrency long adjType = PrefC.GetLong(PrefName.FinanceChargeAdjustmentType); Dictionary <long, List <Adjustment> > dictPatAdjustments = new Dictionary <long, List <Adjustment> >(); if (!checkCompound.Checked) { int daysOver = (radio30.Checked ? 30 : radio60.Checked ? 60 : 90); DateTime maxAdjDate = MiscData.GetNowDateTime().Date.AddDays(-daysOver); dictPatAdjustments = Adjustments.GetAdjustForPatsByType(listPatAgings.Select(x => x.PatNum).ToList(), adjType, maxAdjDate); } int chargesProcessed = 0; List <Action> listActions = new List <Action>(); foreach (PatAging patAgingCur in listPatAgings) { listActions.Add(new Action(() => { if (++chargesProcessed % 5 == 0) { ODEvent.Fire(new ODEventArgs("FinanceCharge", Lan.g(this, "Processing " + chargeType + " charges") + ": " + chargesProcessed + " out of " + listPatAgings.Count)); } //This WILL NOT be the same as the patient's total balance. Start with BalOver90 since all options include that bucket. Add others if needed. double overallBalance = patAgingCur.BalOver90 + (radio60.Checked?patAgingCur.Bal_61_90:radio30.Checked?(patAgingCur.Bal_31_60 + patAgingCur.Bal_61_90):0); if (overallBalance <= .01d) { return; } if (radioBillingCharge.Checked) { AddBillingCharge(patAgingCur.PatNum, date, textBillingCharge.Text, patAgingCur.PriProv); } else //Finance charge { if (dictPatAdjustments.ContainsKey(patAgingCur.PatNum)) //Only contains key if checkCompound is not checked. { overallBalance -= dictPatAdjustments[patAgingCur.PatNum].Sum(x => x.AdjAmt); //Dict always contains patNum as key, but list can be empty. } if (!AddFinanceCharge(patAgingCur.PatNum, date, textAPR.Text, textAtLeast.Text, textOver.Text, overallBalance, patAgingCur.PriProv, adjType)) { return; } } chargesAdded++; })); } ODThread.RunParallel(listActions, TimeSpan.FromMinutes(2)); //each group of actions gets X minutes. if (radioFinanceCharge.Checked) { if (Prefs.UpdateString(PrefName.FinanceChargeAPR, textAPR.Text) | Prefs.UpdateString(PrefName.FinanceChargeLastRun, POut.Date(date, false)) | Prefs.UpdateString(PrefName.FinanceChargeAtLeast, textAtLeast.Text) | Prefs.UpdateString(PrefName.FinanceChargeOnlyIfOver, textOver.Text) | Prefs.UpdateString(PrefName.BillingChargeOrFinanceIsDefault, "Finance")) { DataValid.SetInvalid(InvalidType.Prefs); } } else if (radioBillingCharge.Checked) { if (Prefs.UpdateString(PrefName.BillingChargeAmount, textBillingCharge.Text) | Prefs.UpdateString(PrefName.BillingChargeLastRun, POut.Date(date, false)) | Prefs.UpdateString(PrefName.BillingChargeOrFinanceIsDefault, "Billing")) { DataValid.SetInvalid(InvalidType.Prefs); } } } finally { actionCloseProgress?.Invoke(); //terminates progress bar } MessageBox.Show(Lan.g(this, chargeType + " charges added") + ": " + chargesAdded); if (PrefC.GetBool(PrefName.AgingIsEnterprise)) { if (!RunAgingEnterprise()) { MsgBox.Show(this, "There was an error calculating aging after the " + chargeType.ToLower() + " charge adjustments were added.\r\n" + "You should run aging later to update affected accounts."); } } else { DateTime asOfDate = (PrefC.GetBool(PrefName.AgingCalculatedMonthlyInsteadOfDaily)?PrefC.GetDate(PrefName.DateLastAging):DateTime.Today); actionCloseProgress = ODProgressOld.ShowProgressStatus("FinanceCharge", this, Lan.g(this, "Calculating aging for all patients as of") + " " + asOfDate.ToShortDateString() + "..."); Cursor = Cursors.WaitCursor; try { Ledgers.RunAging(); } catch (MySqlException ex) { actionCloseProgress?.Invoke(); //terminates progress bar Cursor = Cursors.Default; if (ex == null || ex.Number != 1213) //not a deadlock error, just throw { throw; } MsgBox.Show(this, "There was a deadlock error calculating aging after the " + chargeType.ToLower() + " charge adjustments were added.\r\n" + "You should run aging later to update affected accounts."); } finally { actionCloseProgress?.Invoke(); //terminates progress bar Cursor = Cursors.Default; } } DialogResult = DialogResult.OK; }