/// <summary>Update only data that is different in newOrthoProcLink</summary> public static void Update(OrthoProcLink newOrthoProcLink, OrthoProcLink oldOrthoProcLink) { if (RemotingClient.RemotingRole == RemotingRole.ClientWeb) { Meth.GetVoid(MethodBase.GetCurrentMethod(), newOrthoProcLink, oldOrthoProcLink); return; } Crud.OrthoProcLinkCrud.Update(newOrthoProcLink, oldOrthoProcLink); }
///<summary>Inserts an OrthoProcLink into the database. Returns the OrthoProcLinkNum.</summary> public static long Insert(OrthoProcLink orthoProcLink) { if (RemotingClient.RemotingRole == RemotingRole.ClientWeb) { orthoProcLink.OrthoProcLinkNum = Meth.GetLong(MethodBase.GetCurrentMethod(), orthoProcLink); return(orthoProcLink.OrthoProcLinkNum); } return(Crud.OrthoProcLinkCrud.Insert(orthoProcLink)); }
///<summary>Does not insert it in the DB. Returns an OrthoProcLink of the specified type for the OrthoCaseNum and procNum passed in.</summary> public static OrthoProcLink CreateHelper(long orthoCaseNum, long procNum, OrthoProcType procType) { //No remoting role check; no call to db OrthoProcLink orthoProcLink = new OrthoProcLink(); orthoProcLink.OrthoCaseNum = orthoCaseNum; orthoProcLink.ProcNum = procNum; orthoProcLink.ProcLinkType = procType; orthoProcLink.SecUserNumEntry = Security.CurUser.UserNum; return(orthoProcLink); }
///<summary>Fills ref parameters for an orthoProcLink, orthoCase, orthoSchedule, and list of orthoProcLinks for the orthoCase. ///These objects are used in several places to call Procedures.ComputeEstimates()</summary> public static void FillOrthoCaseObjectsForProc(long procNum, ref OrthoProcLink orthoProcLink, ref OrthoCase orthoCase, ref OrthoSchedule orthoSchedule, ref List <OrthoProcLink> listOrthoProcLinksForOrthoCase, Dictionary <long, OrthoProcLink> dictOrthoProcLinksForProcList, Dictionary <long, OrthoCase> dictOrthoCases, Dictionary <long, OrthoSchedule> dictOrthoSchedules, List <OrthoProcLink> listOrthoProcLinksAll) { //No remoting role check; no call to db listOrthoProcLinksForOrthoCase = null; dictOrthoProcLinksForProcList.TryGetValue(procNum, out orthoProcLink); //If proc is linked to an OrthoCase, get other OrthoCase data needed to update estimates. if (orthoProcLink != null) { long orthoCaseNum = orthoProcLink.OrthoCaseNum; dictOrthoCases.TryGetValue(orthoCaseNum, out orthoCase); dictOrthoSchedules.TryGetValue(orthoCaseNum, out orthoSchedule); listOrthoProcLinksForOrthoCase = listOrthoProcLinksAll.Where(x => x.OrthoCaseNum == orthoCaseNum).ToList(); } }
///<summary>Sets the BandingDate or DebondDate for an OrthoCase.</summary> public static void UpdateDatesByLinkedProc(OrthoProcLink procLink, Procedure proc) { //No remoting role check; no call to db if (procLink.ProcLinkType == OrthoProcType.Visit) { return; } OrthoCase orthoCase = GetOne(procLink.OrthoCaseNum); OrthoCase oldOrthoCase = orthoCase.Copy(); //Update banding date only if banding proc is complete or it is treatment planned and attached to an appointment. if (procLink.ProcLinkType == OrthoProcType.Banding && proc.ProcStatus == ProcStat.C || (proc.ProcStatus == ProcStat.TP && proc.AptNum != 0)) { orthoCase.BandingDate = proc.ProcDate; } else if (procLink.ProcLinkType == OrthoProcType.Debond) { orthoCase.DebondDate = proc.ProcDate; } Update(orthoCase, oldOrthoCase); }
///<summary>Returns true if OrthoCase feature is enabled, pat has an active ortho case, no debond procedures are linked to ortho case, ///banding procedure is complete if a link for one exists.</summary> public static bool WillProcLinkToOrthoCase(long patNum, string procCode, ref OrthoCase activeOrthoCase , ref List <OrthoProcLink> listOrthoProcLinksForCase) { //No remoting role check; no call to db List <string> listVisitAndDebondCodes = new List <string>(); listVisitAndDebondCodes.AddRange(OrthoCases.GetListProcTypeProcCodes(PrefName.OrthoVisitCodes)); listVisitAndDebondCodes.AddRange(OrthoCases.GetListProcTypeProcCodes(PrefName.OrthoDebondCodes)); //If Orthocases aren't enabled or code in question is not a visit or debond code return false. if (!OrthoCases.HasOrthoCasesEnabled() || !listVisitAndDebondCodes.Contains(procCode)) { return(false); } if (activeOrthoCase == null) { activeOrthoCase = OrthoCases.GetActiveForPat(patNum); } if (activeOrthoCase == null) { return(false); //If patient doesn't have an active ortho case return false. } if (listOrthoProcLinksForCase == null) { listOrthoProcLinksForCase = GetManyByOrthoCase(activeOrthoCase.OrthoCaseNum); } //If active ortho case already has a debond procedure completed return false. Ortho case is considered completed. if (listOrthoProcLinksForCase.Where(x => x.ProcLinkType == OrthoProcType.Debond).ToList().Count > 0) { return(false); } //If banding procedure is linked and it is not complete, return false. OrthoProcLink bandingProcLink = listOrthoProcLinksForCase.Where(x => x.ProcLinkType == OrthoProcType.Banding).ToList().FirstOrDefault(); if (bandingProcLink != null && Procedures.GetOneProc(bandingProcLink.ProcNum, false).ProcStatus != ProcStat.C) { return(false); } return(true); }
///<summary>Changes procFee. Links procedure to an active OrthoCase. Returns new ProcLink if the procedure is linked, else returns null. ///Should only be used when a procedure is set complete. This will set the procedure's ProcFee but does not update the procedure in the DB. ///This must be done if this function returns true.</summary> public static OrthoProcLink LinkProcForActiveOrthoCase(Procedure proc, OrthoCase orthoCase = null, List <OrthoProcLink> listProcLinksForCase = null , OrthoPlanLink schedulePlanLink = null, OrthoSchedule orthoSchedule = null) { //No remoting role check; no call to db if (orthoCase == null) { orthoCase = OrthoCases.GetActiveForPat(proc.PatNum); } if (orthoCase == null) //No active ortho case for pat so return. { return(null); } OrthoCase orthoCaseOld = orthoCase.Copy(); if (listProcLinksForCase == null) { listProcLinksForCase = GetManyByOrthoCase(orthoCase.OrthoCaseNum); } List <OrthoProcLink> listAllVisitProcLinks = listProcLinksForCase.Where(x => x.ProcLinkType == OrthoProcType.Visit).ToList(); //Don't link procs to an OrthoCase with a completed debond procedure if (listProcLinksForCase.FirstOrDefault(x => x.ProcLinkType == OrthoProcType.Debond) != null) { return(null); } if (!orthoCase.IsTransfer) { OrthoProcLink bandingProcLink = listProcLinksForCase.FirstOrDefault(x => x.ProcLinkType == OrthoProcType.Banding); //If proc being set complete is the banding, it is already linked. We just need to set the fee. if (bandingProcLink.ProcNum == proc.ProcNum) { SetProcFeeForLinkedProc(orthoCase, proc, OrthoProcType.Banding, listAllVisitProcLinks); orthoCase.BandingDate = proc.ProcDate; OrthoCases.Update(orthoCase, orthoCaseOld); return(bandingProcLink); } Procedure bandingProc = Procedures.GetOneProc(bandingProcLink.ProcNum, false); //If proc is not banding and banding is not complete yet, don't link procedure if (bandingProc.ProcStatus != ProcStat.C) { return(null); } } if (listProcLinksForCase.Select(x => x.ProcNum).ToList().Contains(proc.ProcNum)) { return(null); //Procedure is not banding and is already linked so do nothing. } string procCode = ProcedureCodes.GetProcCode(proc.CodeNum).ProcCode; List <string> listDebondProcs = OrthoCases.GetListProcTypeProcCodes(PrefName.OrthoDebondCodes); List <string> listVisitProcs = OrthoCases.GetListProcTypeProcCodes(PrefName.OrthoVisitCodes); if (listVisitProcs.Contains(procCode) || listDebondProcs.Contains(procCode)) { if (schedulePlanLink == null) { schedulePlanLink = OrthoPlanLinks.GetOneForOrthoCaseByType(orthoCase.OrthoCaseNum, OrthoPlanLinkType.OrthoSchedule); } if (orthoSchedule == null) { orthoSchedule = OrthoSchedules.GetOne(schedulePlanLink.FKey); } //Link visit procedure if (listVisitProcs.Contains(procCode)) { OrthoProcLink newVisitProcLink = CreateHelper(orthoCase.OrthoCaseNum, proc.ProcNum, OrthoProcType.Visit); newVisitProcLink.OrthoProcLinkNum = Insert(newVisitProcLink); listAllVisitProcLinks.Add(newVisitProcLink); listProcLinksForCase.Add(newVisitProcLink); SetProcFeeForLinkedProc(orthoCase, proc, OrthoProcType.Visit, listAllVisitProcLinks, schedulePlanLink, orthoSchedule); return(newVisitProcLink); } //Link debond procedure else if (listDebondProcs.Contains(procCode)) { OrthoProcLink newDebondProcLink = CreateHelper(orthoCase.OrthoCaseNum, proc.ProcNum, OrthoProcType.Debond); newDebondProcLink.OrthoProcLinkNum = Insert(newDebondProcLink); listProcLinksForCase.Add(newDebondProcLink); OrthoCases.SetActiveState(orthoCase, schedulePlanLink, orthoSchedule, false); //deactivate the ortho case SetProcFeeForLinkedProc(orthoCase, proc, OrthoProcType.Debond, listAllVisitProcLinks, schedulePlanLink, orthoSchedule); orthoCase.DebondDate = proc.ProcDate; OrthoCases.Update(orthoCase, orthoCaseOld); return(newDebondProcLink); } } return(null); //Procedure is not a Banding, Visit, or Debond. Do nothing. }
///<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); }