///<summary>Update only data that is different in newOrthoCase.</summary> public static void Update(OrthoCase newOrthoCase, OrthoCase oldOrthoCase) { if (RemotingClient.RemotingRole == RemotingRole.ClientWeb) { Meth.GetVoid(MethodBase.GetCurrentMethod(), newOrthoCase, oldOrthoCase); return; } Crud.OrthoCaseCrud.Update(newOrthoCase, oldOrthoCase); }
///<summary>Insert an OrthoCase into the database. Returns OrthoCaseNum.</summary> public static long Insert(OrthoCase orthoCase) { if (RemotingClient.RemotingRole == RemotingRole.ClientWeb) { orthoCase.OrthoCaseNum = Meth.GetLong(MethodBase.GetCurrentMethod(), orthoCase); return(orthoCase.OrthoCaseNum); } return(Crud.OrthoCaseCrud.Insert(orthoCase)); }
///<summary>Activates an OrthoCase and its associated OrthoSchedule and OrthoPlanLink. Sets all other OrthoCases for Pat inactive. ///Returns the refreshed list of OrthoCases.</summary> public static List <OrthoCase> Activate(OrthoCase orthoCaseToActivate, long patNum) { //No need to check RemotingRole; no call to db. OrthoPlanLink scheduleOrthoPlanLink = OrthoPlanLinks.GetOneForOrthoCaseByType(orthoCaseToActivate.OrthoCaseNum, OrthoPlanLinkType.OrthoSchedule); OrthoSchedule orthoSchedule = OrthoSchedules.GetOne(scheduleOrthoPlanLink.FKey); SetActiveState(orthoCaseToActivate, scheduleOrthoPlanLink, orthoSchedule, true); DeactivateOthersForPat(orthoCaseToActivate.OrthoCaseNum, orthoSchedule.OrthoScheduleNum, patNum); return(Refresh(patNum)); }
///<summary>Update the IsActive property for the OrthoCase, OrthoSchedule, and OrthoPlanLink between them.</summary> public static void SetActiveState(OrthoCase orthoCase, OrthoPlanLink scheduleOrthoPlanLink, OrthoSchedule orthoSchedule, bool isActive) { //No remoting role check; no call to db OrthoCase oldOrthoCase = orthoCase.Copy(); OrthoSchedule oldOrthoSchedule = orthoSchedule.Copy(); OrthoPlanLink oldScheduleOrthoPlanLink = scheduleOrthoPlanLink.Copy(); orthoCase.IsActive = isActive; orthoSchedule.IsActive = isActive; scheduleOrthoPlanLink.IsActive = isActive; Update(orthoCase, oldOrthoCase); OrthoSchedules.Update(orthoSchedule, oldOrthoSchedule); OrthoPlanLinks.Update(scheduleOrthoPlanLink, oldScheduleOrthoPlanLink); }
///<summary>Determines whether the passed in procedure is a Banding, Visit, or Debond procedure and ///sets the ProcFee accordingly. Does not update the procedure in the database.</summary> public static void SetProcFeeForLinkedProc(OrthoCase orthoCase, Procedure proc, OrthoProcType procType, List <OrthoProcLink> listVisitProcLinks, OrthoPlanLink scheduleOrthoPlanLink = null, OrthoSchedule orthoSchedule = null) { //No remoting role check; no call to db if (scheduleOrthoPlanLink == null && orthoSchedule == null) { scheduleOrthoPlanLink = OrthoPlanLinks.GetOneForOrthoCaseByType(orthoCase.OrthoCaseNum, OrthoPlanLinkType.OrthoSchedule); } if (orthoSchedule == null) { orthoSchedule = OrthoSchedules.GetOne(scheduleOrthoPlanLink.FKey); } double procFee = 0; switch (procType) { case OrthoProcType.Banding: procFee = orthoSchedule.BandingAmount; break; case OrthoProcType.Debond: procFee = orthoSchedule.DebondAmount; break; case OrthoProcType.Visit: double allVisitsAmount = Math.Round((orthoCase.Fee - orthoSchedule.BandingAmount - orthoSchedule.DebondAmount) * 100) / 100; int plannedVisitCount = OrthoSchedules.CalculatePlannedVisitsCount(orthoSchedule.BandingAmount, orthoSchedule.DebondAmount , orthoSchedule.VisitAmount, orthoCase.Fee); if (listVisitProcLinks.Count == plannedVisitCount) { procFee = Math.Round((allVisitsAmount - orthoSchedule.VisitAmount * (plannedVisitCount - 1)) * 100) / 100; } else if (listVisitProcLinks.Count < plannedVisitCount) { procFee = orthoSchedule.VisitAmount; } break; } proc.ProcFee = procFee; proc.BaseUnits = 0; proc.UnitQty = 1; }
///<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); }
///<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(); } }