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; }