Пример #1
0
        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");
        }
Пример #2
0
        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;
        }
Пример #3
0
        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");
        }
Пример #4
0
        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.");
            }
        }
Пример #5
0
        /*
         * ///<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));
        }
Пример #6
0
        ///<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.
        }
Пример #7
0
        ///<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();
            }
        }
Пример #8
0
 ///<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
 }
Пример #9
0
 ///<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
     }
 }
Пример #10
0
        ///<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);
        }
Пример #11
0
        ///<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);
        }
Пример #12
0
        /// <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);
        }
Пример #13
0
        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;
        }