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; } if (_clinicNumTo == -1) { MsgBox.Show(this, "You must pick a 'To' clinic in the box above to move patients to."); return; } Dictionary <long, Clinic> dictClinicsFrom = gridMain.SelectedTags <Clinic>().ToDictionary(x => x.ClinicNum); Clinic clinicTo = gridMain.GetTags <Clinic>().FirstOrDefault(x => x.ClinicNum == _clinicNumTo); if (clinicTo == null) { MsgBox.Show(this, "The clinic could not be found."); return; } if (dictClinicsFrom.ContainsKey(clinicTo.ClinicNum)) { MsgBox.Show(this, "The 'To' clinic should not also be one of the 'From' clinics."); return; } Dictionary <long, int> dictClinFromCounts = Clinics.GetClinicalPatientCount(true) .Where(x => dictClinicsFrom.ContainsKey(x.Key)).ToDictionary(x => x.Key, x => x.Value); if (dictClinFromCounts.Sum(x => x.Value) == 0) { MsgBox.Show(this, "There are no patients assigned to the selected clinics."); return; } string msg = Lan.g(this, "This will move all patients to") + " " + clinicTo.Abbr + " " + Lan.g(this, "from the following clinics") + ":\r\n" + string.Join("\r\n", dictClinFromCounts.Select(x => dictClinicsFrom[x.Key].Abbr)) + "\r\n" + Lan.g(this, "Continue?"); if (MessageBox.Show(msg, "", MessageBoxButtons.YesNo) != DialogResult.Yes) { return; } ODProgress.ShowAction(() => { int patsMoved = 0; List <Action> listActions = dictClinFromCounts.Select(x => new Action(() => { Patients.ChangeClinicsForAll(x.Key, clinicTo.ClinicNum); //update all clinicNums to new clinic Clinic clinicCur; SecurityLogs.MakeLogEntry(Permissions.PatientEdit, 0, "Clinic changed for " + x.Value + " patients from " + (dictClinicsFrom.TryGetValue(x.Key, out clinicCur)?clinicCur.Abbr:"") + " to " + clinicTo.Abbr + "."); patsMoved += x.Value; ClinicEvent.Fire(ODEventType.Clinic, Lan.g(this, "Moved patients") + ": " + patsMoved + " " + Lan.g(this, "out of") + " " + dictClinFromCounts.Sum(y => y.Value)); })).ToList(); ODThread.RunParallel(listActions, TimeSpan.FromMinutes(2)); }, startingMessage: Lan.g(this, "Moving patients") + "...", eventType: typeof(ClinicEvent), odEventType: ODEventType.Clinic); _dictClinicalCounts = Clinics.GetClinicalPatientCount(); FillGrid(); MsgBox.Show(this, "Done"); }
public void SecurityLogs_MakeLogEntry_DuplicateEntryParallel() { Patient patient = PatientT.CreatePatient(MethodBase.GetCurrentMethod().Name); //There are lots of bug submissions with exception text like "Duplicate entry 'XXXXX' for key 'PRIMARY'". //OpenDentBusiness.SecurityLogs.MakeLogEntry() seems to be the common theme for most of the submissions. //Spawn parallel threads to insert 200 security logs trying to get a duplicate entry exception. List <Action> listActions = new List <Action>(); for (int i = 0; i < 200; i++) { listActions.Add(() => SecurityLogs.MakeLogEntry(Permissions.Accounting, patient.PatNum, "", 0, DateTime.Now.AddDays(-7))); } //Parallel threads do not support Middle Tier mode when unit testing due to how we have to fake being both the client and the server. RemotingRole remotingRoleOld = RemotingClient.RemotingRole; if (RemotingClient.RemotingRole != RemotingRole.ClientDirect) { RemotingClient.RemotingRole = RemotingRole.ClientDirect; } ODThread.RunParallel(listActions, onException: (ex) => { RemotingClient.RemotingRole = remotingRoleOld; Assert.Fail(ex.Message); }); RemotingClient.RemotingRole = remotingRoleOld; }
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 RunTransfer() { //Get data from the source connection. This will have all the patients selected from the sourceconnection string stringFailedConns = ""; ODThread odThread = new ODThread(GetDataFromSourceConnection, new object[] { _sourceConnection }); odThread.GroupName = "FetchSheets"; odThread.Start(); ODThread.JoinThreadsByGroupName(Timeout.Infinite, "FetchSheets"); if (_listSheetsForSelectedPats.IsNullOrEmpty()) { string connString = CentralConnections.GetConnectionString(_sourceConnection); stringFailedConns += connString + "\r\n"; } //Insert the sheets to each of the databases. The sheets will have a patnum=0; List <Action> listActions = new List <Action>(); object locker = new object(); foreach (CentralConnection conn in _listConnectionsToTransferTo) { List <Sheet> listSheets = _listSheetsForSelectedPats.Select(x => x.Copy()).ToList(); listActions.Add(() => { if (!InsertSheetsToConnection(conn, listSheets)) { string connString = CentralConnections.GetConnectionString(conn); lock (locker) { stringFailedConns += connString + "\r\n"; } } }); } ODThread.RunParallel(listActions); if (stringFailedConns != "") { stringFailedConns = Lans.g(this, "There were some transfers that failed due to connection issues. Fix connections and try again.\r\n" + "Failed Connections:") + "\r\n" + stringFailedConns; CodeBase.MsgBoxCopyPaste msgBoxCP = new CodeBase.MsgBoxCopyPaste(stringFailedConns); msgBoxCP.ShowDialog(); } else { MsgBox.Show(this, "Transfers Completed Successfully\r\nGo to each database you transferred patients to and retrieve Webforms to finish the " + "transfer process."); } }
/* * ///<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)); }
///<summary>Tests that RunParallel itself is a synchronous method call, meaning that any subsequent code will not execute until either the timeout ///is reached or all actions are completed.</summary> public void ODThread_RunParallel_IsSynchronous() { int timeoutMS = 1; DateTime start = DateTime.Now; object testObject = null; bool isRunParallelFinished = false; List <Action> listActions = new List <Action>(); listActions.Add(() => { while (!isRunParallelFinished) { //Wait here until ODThread.RunParallel finishes. } testObject = new object(); //This line should never execute. }); int numThreads = 4; //Mimic what is used in AccountModules.GetAll(...). ODThread.RunParallel(listActions, timeoutMS, numThreads); isRunParallelFinished = true; Assert.IsNull(testObject); //Thread did not finish naturally; was aborted due to timeout. }
///<summary>Handles all double buffer drawing and rendering to screen. ///Creates one bitmap image for each appointment if visible, and draws those bitmaps onto the main appt background image.</summary> /// <param name="listAptNumsOnlyRedraw">Specify which appts to redraw. If null or empty then all appts will be redrawn.</param> /// <param name="drawSheetBackground">Recreates the background and everything other than the appointments. Typically only used by RedrawAll().</param> /// <param name="createApptShadows">Each individual child ContrApptSingle control will have it's own double buffer bitmap re-created.</param> /// <param name="drawToScreen">Draws the double buffer bitmap directly to screen once it has been modified. In rare cases the screen has already been updated so this wouldn't be necessary here.</param> /// <param name="isThreaded">Spreads the work of generating each appt shadow over multiple threads. Typically only used by RedrawAll().</param> /// <param name="drawCachedBitmapOnly">Skips all double buffer bitmap modifications and simply redraws existing to screen. Typically only used by OnPaint().</param> public void DoubleBufferDraw(List <long> listAptNumsOnlyRedraw = null, bool drawSheetBackground = false, bool createApptShadows = false, bool drawToScreen = false, bool isThreaded = false, bool drawCachedBitmapOnly = false) { if (_isRedrawingOnThread && !isThreaded) //We are already performing a RedrawAll on a thread so do not allow re-entrance at this time. { return; } if (!_isShadowValid) //if user resizes window to be very narrow { return; } Action drawShadowToScreen = new Action(() => { if (!_isShadowValid) { return; } using (Graphics g = this.CreateGraphics()) { g.DrawImage(_shadow, 0, 0); } }); if (drawCachedBitmapOnly) { drawShadowToScreen(); return; } if (createApptShadows) { //Make a list of actions. We will process these in threads below. List <Action> actions = new List <Action>(); foreach (ContrApptSingle ctrl in ListContrApptSingles) { if (listAptNumsOnlyRedraw != null && !listAptNumsOnlyRedraw.Contains(ctrl.AptNum)) { continue; } actions.Add(new Action(() => { ctrl.CreateShadow(); })); } if (isThreaded) //Spread the workload over a group of threads. { ODThread.RunParallel(actions, TimeSpan.FromMinutes(1)); } else //Syncronous. { actions.ForEach(x => x()); } } using (Graphics g = Graphics.FromImage(_shadow)) { if (drawSheetBackground) //Draw background first. { ApptDrawing.DrawAllButAppts(g, true, new DateTime(2011, 1, 1, 0, 0, 0), new DateTime(2011, 1, 1, 0, 0, 0), ApptDrawing.VisOps.Count, 0, 8, false); } foreach (ContrApptSingle ctrl in ListContrApptSingles) { //Filter based on AptNum where applicable. if (listAptNumsOnlyRedraw != null && !listAptNumsOnlyRedraw.Contains(ctrl.AptNum)) { continue; } //Make sure that appointment shadow was created one way or another. if (!ctrl.IsShadowValid) { continue; } if (ctrl.Location.X >= ApptDrawing.TimeWidth + (ApptDrawing.ProvWidth * ApptDrawing.ProvCount) && ctrl.Width > 3) { g.DrawImage(ctrl.Shadow, ctrl.Location.X, ctrl.Location.Y); } //We are done with these so get rid of them. ctrl.DisposeShadow(); } } if (drawToScreen) { drawShadowToScreen(); } }
///<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>Updates writeoff estimated for claimprocs for the passed in clinics. Called only in FormFeeSchedTools, located here to allow unit ///testing. Requires an ODProgressExtended to display UI updates. If clinics are enabled and the user is not clinic restricted and chooses to run ///for all clinics, set doUpdatePrevClinicPref to true so that the ClinicNums will be stored in the preference table as they are finished to allow ///for pausing/resuming the process.</summary> public static long GlobalUpdateWriteoffs(List <long> listWriteoffClinicNums, ODProgressExtended progress, bool doUpdatePrevClinicPref = false) { //No need to check RemotingRole; no call to db. long totalWriteoffsUpdated = 0; List <Fee> listFeesHQ = Fees.GetByClinicNum(0);//All HQ fees Dictionary <long, List <Procedure> > dictPatProcs; List <FamProc> listFamProcs; Dictionary <long, List <ClaimProc> > dictClaimProcs; List <Fee> listFeesHQandClinic; Lookup <FeeKey2, Fee> lookupFeesByCodeAndSched; List <InsSub> listInsSubs; List <InsPlan> listInsPlans; List <PatPlan> listPatPlans; List <Benefit> listBenefits; List <Action> listActions; //Get all objects needed to check if procedures are linked to an orthocase here to avoid querying in loops. List <OrthoProcLink> listOrthoProcLinksAll = new List <OrthoProcLink>(); Dictionary <long, OrthoProcLink> dictOrthoProcLinksAll = new Dictionary <long, OrthoProcLink>(); Dictionary <long, OrthoCase> dictOrthoCases = new Dictionary <long, OrthoCase>(); Dictionary <long, OrthoSchedule> dictOrthoSchedules = new Dictionary <long, OrthoSchedule>(); OrthoCases.GetDataForAllProcLinks(ref listOrthoProcLinksAll, ref dictOrthoProcLinksAll, ref dictOrthoCases, ref dictOrthoSchedules); OrthoProcLink orthoProcLink = null; OrthoCase orthoCase = null; OrthoSchedule orthoSchedule = null; List <OrthoProcLink> listOrthoProcLinksForOrthoCase = null; foreach (long clinicNumCur in listWriteoffClinicNums) { progress.Fire(ODEventType.FeeSched, new ProgressBarHelper(Clinics.GetAbbr(clinicNumCur), "0%", 0, 100, ProgBarStyle.Blocks, "WriteoffProgress")); long rowCurIndex = 0; //reset for each clinic. object lockObj = new object(); //used to lock rowCurIndex so the threads will correctly increment the count progress.Fire(ODEventType.FeeSched, new ProgressBarHelper(Lans.g("FeeSchedEvent", "Getting list to update writeoffs..."), progressBarEventType: ProgBarEventType.TextMsg)); listFeesHQandClinic = Fees.GetByClinicNum(clinicNumCur); //could be empty for some clinics that don't use overrides listFeesHQandClinic.AddRange(listFeesHQ); lookupFeesByCodeAndSched = (Lookup <FeeKey2, Fee>)listFeesHQandClinic.ToLookup(x => new FeeKey2(x.CodeNum, x.FeeSched)); dictPatProcs = Procedures.GetAllTp(clinicNumCur) .GroupBy(x => x.PatNum) .ToDictionary(x => x.Key, x => Procedures.SortListByTreatPlanPriority(x.ToList()).ToList()); #region Has Paused or Cancelled while (progress.IsPaused) { progress.AllowResume(); if (progress.IsCanceled) { break; } } if (progress.IsCanceled) { break; } #endregion Has Paused or Cancelled if (dictPatProcs.Count == 0) { continue; } int procCount = dictPatProcs.Sum(x => x.Value.Count); listFamProcs = Patients.GetFamilies(dictPatProcs.Keys.ToList()).Where(x => x.Guarantor != null) .Select(x => new FamProc { GuarNum = x.Guarantor.PatNum, ListPatProcs = x.ListPats.Select(y => new PatProc { PatNum = y.PatNum, Age = y.Age, ListProcs = dictPatProcs.TryGetValue(y.PatNum, out List <Procedure> listProcsCurr)?listProcsCurr:new List <Procedure>() }).ToList() }).ToList(); listPatPlans = PatPlans.GetPatPlansForPats(dictPatProcs.Keys.ToList()); listInsSubs = InsSubs.GetListInsSubs(dictPatProcs.Keys.ToList()); List <long> listInsSubNums = listInsSubs.Select(x => x.InsSubNum).ToList(); listInsSubs.AddRange(InsSubs.GetMany(listPatPlans.Select(x => x.InsSubNum).Distinct().Where(x => !listInsSubNums.Contains(x)).ToList())); listInsSubs = listInsSubs.DistinctBy(x => x.InsSubNum).ToList(); listInsPlans = InsPlans.RefreshForSubList(listInsSubs); listBenefits = Benefits.GetAllForPatPlans(listPatPlans, listInsSubs); #region Has Paused or Cancelled while (progress.IsPaused) { progress.AllowResume(); if (progress.IsCanceled) { break; } } if (progress.IsCanceled) { break; } #endregion Has Paused or Cancelled //dictionary of key=PatNum, value=list of claimprocs, i.e. a dictionary linking each PatNum to a list of claimprocs for the given procs dictClaimProcs = ClaimProcs.GetForProcs(dictPatProcs.SelectMany(x => x.Value.Select(y => y.ProcNum)).ToList(), useDataReader: true) .GroupBy(x => x.PatNum) .ToDictionary(x => x.Key, x => x.ToList()); #region Has Paused or Cancelled while (progress.IsPaused) { progress.AllowResume(); if (progress.IsCanceled) { break; } } if (progress.IsCanceled) { break; } #endregion Has Paused or Cancelled progress.Fire(ODEventType.FeeSched, new ProgressBarHelper(Lans.g("FeeSchedEvent", "Updating writeoff estimates for patients..."), progressBarEventType: ProgBarEventType.TextMsg)); listActions = listFamProcs.Select(x => new Action(() => { #region Has Cancelled if (progress.IsCanceled) { return; } #endregion Has Cancelled List <long> listPatNums = x.ListPatProcs.Select(y => y.PatNum).ToList(); List <long> listInsSubNumsPatPlanCur = listPatPlans.Where(y => y.PatNum.In(listPatNums)).Select(y => y.InsSubNum).ToList(); List <InsSub> listInsSubsCur = listInsSubs.FindAll(y => listPatNums.Contains(y.Subscriber) || y.InsSubNum.In(listInsSubNumsPatPlanCur)); List <long> listInsSubPlanNumsCur = listInsSubsCur.Select(y => y.PlanNum).ToList(); List <InsPlan> listInsPlansCur = listInsPlans.FindAll(y => listInsSubPlanNumsCur.Contains(y.PlanNum)); List <SubstitutionLink> listSubstitutionLinks = SubstitutionLinks.GetAllForPlans(listInsPlansCur); List <PatPlan> listPatPlansCur; List <Benefit> listBenefitsCur; foreach (PatProc patProc in x.ListPatProcs) //foreach patient in the family { if (patProc.ListProcs.IsNullOrEmpty()) { continue; } listPatPlansCur = listPatPlans.FindAll(y => y.PatNum == patProc.PatNum); List <long> listInsPlanNumsCur = listInsPlansCur.Select(y => y.PlanNum).ToList(); List <long> listPatPlanNumsCur = listPatPlansCur.Select(y => y.PatPlanNum).ToList(); listBenefitsCur = listBenefits .FindAll(y => listInsPlanNumsCur.Contains(y.PlanNum) || listPatPlanNumsCur.Contains(y.PatPlanNum)); listBenefitsCur.Sort(Benefits.SortBenefits); if (!dictClaimProcs.TryGetValue(patProc.PatNum, out List <ClaimProc> listClaimProcsCur)) { listClaimProcsCur = new List <ClaimProc>(); } foreach (Procedure procCur in patProc.ListProcs) //foreach proc for this patient { OrthoCases.FillOrthoCaseObjectsForProc(procCur.ProcNum, ref orthoProcLink, ref orthoCase, ref orthoSchedule , ref listOrthoProcLinksForOrthoCase, dictOrthoProcLinksAll, dictOrthoCases, dictOrthoSchedules, listOrthoProcLinksAll); Procedures.ComputeEstimates(procCur, patProc.PatNum, ref listClaimProcsCur, false, listInsPlansCur, listPatPlansCur, listBenefitsCur, null, null, true, patProc.Age, listInsSubsCur, listSubstLinks: listSubstitutionLinks, lookupFees: lookupFeesByCodeAndSched, orthoProcLink: orthoProcLink, orthoCase: orthoCase, orthoSchedule: orthoSchedule, listOrthoProcLinksForOrthoCase: listOrthoProcLinksForOrthoCase); double percentage = 0; lock (lockObj) { percentage = Math.Ceiling(((double)(++rowCurIndex) / procCount) * 100); } progress.Fire(ODEventType.FeeSched, new ProgressBarHelper(Clinics.GetAbbr(clinicNumCur), (int)percentage + "%", (int)percentage, 100, ProgBarStyle.Blocks, "WriteoffProgress")); } } })).ToList(); ODThread.RunParallel(listActions, TimeSpan.FromHours(3), onException: new ODThread.ExceptionDelegate((ex) => { //Notify the user what went wrong via the text box. progress.Fire(ODEventType.FeeSched, new ProgressBarHelper("Error updating writeoffs: " + ex.Message, progressBarEventType: ProgBarEventType.TextMsg)); }) ); if (listWriteoffClinicNums.Count > 1) //only show if more than one clinic { progress.Fire(ODEventType.FeeSched, new ProgressBarHelper(rowCurIndex + " " + Lans.g("FeeSchedTools", "procedures processed from") + " " + Clinics.GetAbbr(clinicNumCur), progressBarEventType: ProgBarEventType.TextMsg)); } totalWriteoffsUpdated += rowCurIndex; if (doUpdatePrevClinicPref && rowCurIndex == procCount) { //if storing previously completed clinic and we actually completed this clinic's procs, update the pref if (listWriteoffClinicNums.Last() == clinicNumCur) { //if this is the last clinic in the list, clear the last clinic pref so the next time it will run for all clinics Prefs.UpdateString(PrefName.GlobalUpdateWriteOffLastClinicCompleted, ""); } else { Prefs.UpdateString(PrefName.GlobalUpdateWriteOffLastClinicCompleted, POut.Long(clinicNumCur)); } Signalods.SetInvalid(InvalidType.Prefs); } #region Has Cancelled if (progress.IsCanceled) { break; } #endregion Has Cancelled } progress.OnProgressDone(); progress.Fire(ODEventType.FeeSched, new ProgressBarHelper("Writeoffs updated. " + totalWriteoffsUpdated + " procedures processed.\r\nDone.", progressBarEventType: ProgBarEventType.TextMsg)); return(totalWriteoffsUpdated); }
///<summary>Copies one fee schedule to one or more fee schedules. fromClinicNum, fromProvNum, and toProvNum can be zero. Set listClinicNumsTo to copy to multiple clinic overrides. If this list is null or empty, clinicNum 0 will be used.</summary> public static void CopyFeeSchedule(FeeSched fromFeeSched, long fromClinicNum, long fromProvNum, FeeSched toFeeSched, List <long> listClinicNumsTo, long toProvNum) { if (RemotingClient.RemotingRole == RemotingRole.ClientWeb) { Meth.GetVoid(MethodBase.GetCurrentMethod(), fromFeeSched, fromClinicNum, fromProvNum, toFeeSched, listClinicNumsTo, toProvNum); return; } if (listClinicNumsTo == null) { listClinicNumsTo = new List <long>(); } if (listClinicNumsTo.Count == 0) { listClinicNumsTo.Add(0); } //Store a local copy of the fees from the old FeeSched List <Fee> listFeeLocalCopy = Fees.GetListExact(toFeeSched.FeeSchedNum, listClinicNumsTo, toProvNum); //Delete all fees that exactly match setting in "To" combo selections. foreach (long clinicNum in listClinicNumsTo) { Fees.DeleteFees(toFeeSched.FeeSchedNum, clinicNum, toProvNum); } //Copy: List <Fee> listNewFees = Fees.GetListExact(fromFeeSched.FeeSchedNum, fromClinicNum, fromProvNum); int blockValue = 0; int blockMax = (listNewFees.Count * listClinicNumsTo.Count); object locker = new object(); List <Action> listActions = new List <Action>(); foreach (long clinicNumTo in listClinicNumsTo) { listActions.Add(() => { foreach (Fee fee in listNewFees) { bool isReplacementFee = false; Fee newFee = fee.Copy(); newFee.FeeNum = 0; newFee.ProvNum = toProvNum; newFee.ClinicNum = clinicNumTo; newFee.FeeSched = toFeeSched.FeeSchedNum; Fees.Insert(newFee); //Check to see if this replaced an old fee with the same fee details Fee oldFee = listFeeLocalCopy.Where(x => x.ProvNum == newFee.ProvNum) .Where(x => x.ClinicNum == newFee.ClinicNum) .Where(x => x.CodeNum == newFee.CodeNum) .Where(x => x.FeeSched == newFee.FeeSched) .FirstOrDefault(); if (oldFee != null) { isReplacementFee = true; } ProcedureCode procCode = ProcedureCodes.GetProcCode(fee.CodeNum); string securityLogText = "Fee Schedule \"" + fromFeeSched.Description + "\" copied to Fee Schedule \"" + toFeeSched.Description + "\", "; if (clinicNumTo != 0) { securityLogText += "To Clinic \"" + Clinics.GetDesc(clinicNumTo) + "\", "; } securityLogText += "Proc Code \"" + procCode.ProcCode + "\", Fee \"" + fee.Amount + "\", "; if (isReplacementFee) { securityLogText += "Replacing Previous Fee \"" + oldFee.Amount + "\""; } SecurityLogs.MakeLogEntry(Permissions.FeeSchedEdit, 0, securityLogText); FeeSchedEvent.Fire(ODEventType.FeeSched, new ProgressBarHelper(Lans.g("FormFeeSchedTools", "Copying fees, please wait") + "...", blockValue: blockValue, blockMax: blockMax, progressStyle: ProgBarStyle.Continuous)); lock (locker) { blockValue++; } } }); } //Research and testing will determine whether we can run this on multiple threads. ODThread.RunParallel(listActions, TimeSpan.FromMinutes(30), numThreads: 1); }
/// <summary>Runs all the necessary POST and GET requests for XVWeb and saves the results to a list containing the image information.</summary> public static List <ApteryxImage> GetImagesList(Patient patCur) { UriBuilder uriBuilder = GetApiUri(); List <ApteryxImage> listImages = new List <ApteryxImage>(); string token = GetAuthorizationToken();//perform initial post request to get an authorization token UriBuilder uriBuilderPatient = new UriBuilder(uriBuilder.ToString()); uriBuilderPatient.Path += "patient"; uriBuilderPatient.Query = "PrimaryId=" + patCur.PatNum + "&lastname=" + patCur.LName + "&firstname=" + patCur.FName; List <long> listPatientIds = GetRequestIds(token, uriBuilderPatient); if (listPatientIds.Count < 1) { return(listImages); } object locker = new object(); List <Action> listActions = new List <Action>(); List <long> listStudyIds = new List <long>(); foreach (long id in listPatientIds) //get studies for all patients. (Patients can have one ODpatnum but several different patientIDs) { listActions.Add(new Action(() => { UriBuilder uriBuilderStudy = new UriBuilder(uriBuilder.ToString()); uriBuilderStudy.Path += "study"; uriBuilderStudy.Query = "patient=" + id; List <long> listIds = GetRequestIds(token, uriBuilderStudy); lock (locker) { listStudyIds.AddRange(listIds); } })); } ODThread.RunParallel(listActions, TimeSpan.FromMinutes(1)); if (listStudyIds.Count < 1) { return(listImages); } listActions = new List <Action>(); List <long> listSeriesIds = new List <long>(); foreach (long id in listStudyIds) //get series for all studies for all patients { listActions.Add(new Action(() => { UriBuilder uriBuilderSeries = new UriBuilder(uriBuilder.ToString()); uriBuilderSeries.Path += "series"; uriBuilderSeries.Query = "study=" + id; List <long> listIds = GetRequestIds(token, uriBuilderSeries); lock (locker) { listSeriesIds.AddRange(listIds); } })); } ODThread.RunParallel(listActions, TimeSpan.FromMinutes(1)); if (listSeriesIds.Count < 1) { return(listImages); } listActions = new List <Action>(); foreach (long id in listSeriesIds) //get images for all series for all studies for all patients { listActions.Add(new Action(() => { UriBuilder uriBuilderImage = new UriBuilder(uriBuilder.ToString()); uriBuilderImage.Path += "Image"; uriBuilderImage.Query = "series=" + id; List <ApteryxImage> listImageDownloads = GetImages(token, uriBuilderImage); lock (locker) { listImages.AddRange(listImageDownloads); } })); } ODThread.RunParallel(listActions, TimeSpan.FromMinutes(1)); return(listImages); }
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; }