diff --git a/__TODO_devex_docket_number_sorting.md.m b/__TODO_devex_docket_number_sorting.md.m new file mode 100644 index 00000000000..abc91bd6452 --- /dev/null +++ b/__TODO_devex_docket_number_sorting.md.m @@ -0,0 +1,8 @@ +- Make DocketRecordSortInfo more agnostic (we can use this for anywhere we sort any field), or use the state key idea +- Figure out how to update old docketRecordSort, which I have slowly been doing +- Figure out how to avoid the different sorts we currently have going on as a result of docket number table sorting, which I started to do in generateDocketRecordPdfProxy (we were sorting the docket entries multiple times unnecessarily). It looks like we are passing along docketRecordSort and docketRecordTableSort, sometimes using one and sometimes the other (in fact, the latter was overwriting whatever we did in the former in generateDocketRecordPdfProxy). This is a code smell. +-- Do we need separate state for sorting the table vs. sorting the entries? I suspect not. But why is sessionMetaData by docket number + +TODOS lists as DEVEX TODO + +X - sortDocketEntryTable should be replaced by the existing sort function, but the existing sort function should use the dynamic type idea of sortDocketEntryTable diff --git a/shared/src/business/entities/cases/Case.ts b/shared/src/business/entities/cases/Case.ts index a1a06d696e3..77fc4d38da9 100644 --- a/shared/src/business/entities/cases/Case.ts +++ b/shared/src/business/entities/cases/Case.ts @@ -225,17 +225,6 @@ export class Case extends JoiValidationEntity { return parseInt(`${yearFiledAdjusted}${sequentialNumberPadded}`); } - /** - * sorts the given array of cases by docket number - * @param {Array} cases the cases to check for lead case computation - * @returns {Case} the lead Case entity - */ - static sortByDocketNumber(cases: (T & { docketNumber: string })[]): T[] { - return cases.sort((a, b) => { - return Case.docketNumberSort(a.docketNumber, b.docketNumber); - }); - } - static docketNumberSort(docketNumberA, docketNumberB) { return ( (Case.getSortableDocketNumber(docketNumberA) || 0) - @@ -243,47 +232,6 @@ export class Case extends JoiValidationEntity { ); } - static sortByDocketNumberAndGroupConsolidatedCases< - T extends { leadDocketNumber?: string; docketNumber: string }, - >(cases: T[]): T[] { - let nonMemberCases: T[] = []; - let memberCases: { [key: string]: T[] } = {}; - - // Create a set of docket numbers for quick lookup - const docketNumbers = new Set(cases.map(c => c.docketNumber)); - - // Group cases into 1) "top-level" lead or non-member cases and 2) valid non-lead, member cases - for (const c of cases) { - if ( - c.leadDocketNumber && - c.leadDocketNumber !== c.docketNumber && - docketNumbers.has(c.leadDocketNumber) // Check if the lead case exists; if not, treat this case as a non-member case - ) { - (memberCases[c.leadDocketNumber] ||= []).push(c); - } else { - nonMemberCases.push(c); - } - } - - // Sort the lead/non-member cases - Case.sortByDocketNumber(nonMemberCases); - - // Then, sort and interpolate the non-lead, member cases - const interpolatedCases: T[] = []; - for (const caseItem of nonMemberCases) { - interpolatedCases.push(caseItem); - - // Append and sort member cases inline if applicable - if (memberCases[caseItem.docketNumber]) { - interpolatedCases.push( - ...Case.sortByDocketNumber(memberCases[caseItem.docketNumber]), - ); - } - } - - return interpolatedCases; - } - /** * return the lead case for the given set of cases based on createdAt * (does NOT evaluate leadDocketNumber) diff --git a/shared/src/business/test/createTestApplicationContext.ts b/shared/src/business/test/createTestApplicationContext.ts index 133d866af52..99c1d712e65 100644 --- a/shared/src/business/test/createTestApplicationContext.ts +++ b/shared/src/business/test/createTestApplicationContext.ts @@ -36,11 +36,6 @@ import { bulkIndexRecords } from '@web-api/persistence/elasticsearch/bulkIndexRe import { calculateDaysElapsedSinceLastStatusChange } from '@shared/business/utilities/calculateDaysElapsedSinceLastStatusChange'; import { caseStatusWithTrialInformation } from '@shared/business/utilities/caseStatusWithTrialInformation'; import { combineTwoPdfs } from '@shared/business/utilities/documentGenerators/combineTwoPdfs'; -import { - compareCasesByDocketNumber, - formatCaseForTrialSession, - getFormattedTrialSessionDetails, -} from '@shared/business/utilities/trialSession/getFormattedTrialSessionDetails'; import { compareISODateStrings, compareStrings, @@ -60,8 +55,11 @@ import { formatCase, formatDocketEntry, getFormattedCaseDetail, - sortDocketEntries, } from '@shared/business/utilities/getFormattedCaseDetail'; +import { + formatCaseForTrialSession, + getFormattedTrialSessionDetails, +} from '@shared/business/utilities/trialSession/getFormattedTrialSessionDetails'; import { formatDollars } from '@shared/business/utilities/formatDollars'; import { formatJudgeName, @@ -117,6 +115,7 @@ import { setNoticesForCalendaredTrialSessionInteractor } from '@shared/proxies/t import { setPdfFormFields } from '@web-api/business/useCaseHelper/pdf/setPdfFormFields'; import { setServiceIndicatorsForCase } from '@shared/business/utilities/setServiceIndicatorsForCase'; import { setupPdfDocument } from '@shared/business/utilities/setupPdfDocument'; +import { sortDocketEntries } from '@shared/business/utilities/sorting/docketEntrySorting'; import { unsealDocketEntryInteractor } from '@web-api/business/useCases/docketEntry/unsealDocketEntryInteractor'; import { updateCase } from '@web-api/persistence/dynamo/cases/updateCase'; import { updateCaseAndAssociations } from '@web-api/business/useCaseHelper/caseAssociation/updateCaseAndAssociations'; @@ -207,9 +206,6 @@ export const createTestApplicationContext = () => { .mockImplementation(caseStatusWithTrialInformation), checkDate: jest.fn().mockImplementation(DateHandler.checkDate), combineTwoPdfs: jest.fn().mockImplementation(combineTwoPdfs), - compareCasesByDocketNumber: jest - .fn() - .mockImplementation(compareCasesByDocketNumber), compareISODateStrings: jest.fn().mockImplementation(compareISODateStrings), compareStrings: jest.fn().mockImplementation(compareStrings), copyPagesAndAppendToTargetPdf: jest diff --git a/shared/src/business/useCases/getCasesForUserInteractor.ts b/shared/src/business/useCases/getCasesForUserInteractor.ts index 45abd6ba23d..b34393f8ca7 100644 --- a/shared/src/business/useCases/getCasesForUserInteractor.ts +++ b/shared/src/business/useCases/getCasesForUserInteractor.ts @@ -1,9 +1,3 @@ -import { - Case, - isClosed, - isLeadCase, - userIsDirectlyAssociated, -} from '../entities/cases/Case'; import { PaymentStatusTypes } from '@shared/business/entities/EntityConstants'; import { UnauthorizedError } from '@web-api/errors/errors'; import { @@ -11,7 +5,13 @@ import { isAuthUser, } from '@shared/business/entities/authUser/AuthUser'; import { compareISODateStrings } from '../utilities/sortFunctions'; +import { + isClosed, + isLeadCase, + userIsDirectlyAssociated, +} from '../entities/cases/Case'; import { partition, uniqBy } from 'lodash'; +import { sortByDocketNumber } from '@shared/business/utilities/sorting/caseSorting'; interface UserCaseDTO { caseCaption: string; @@ -145,7 +145,7 @@ async function fetchConsolidatedGroupsAndNest({ return { ...aCase, consolidatedCases: aCase.consolidatedCases.length - ? Case.sortByDocketNumber(aCase.consolidatedCases) + ? sortByDocketNumber(aCase.consolidatedCases) : undefined, }; }); diff --git a/shared/src/business/useCases/getPractitionerCasesInteractor.ts b/shared/src/business/useCases/getPractitionerCasesInteractor.ts index 21652858a22..a200a532fe5 100644 --- a/shared/src/business/useCases/getPractitionerCasesInteractor.ts +++ b/shared/src/business/useCases/getPractitionerCasesInteractor.ts @@ -1,4 +1,3 @@ -import { Case, isClosed } from '../entities/cases/Case'; import { PractitionerCaseDetail } from '@web-client/presenter/state'; import { ROLE_PERMISSIONS, @@ -8,7 +7,9 @@ import { ServerApplicationContext } from '@web-api/applicationContext'; import { UnauthorizedError } from '@web-api/errors/errors'; import { UnknownAuthUser } from '@shared/business/entities/authUser/AuthUser'; import { formatCase } from '@shared/business/utilities/getFormattedCaseDetail'; +import { isClosed } from '../entities/cases/Case'; import { partition } from 'lodash'; +import { sortByDocketNumber } from '@shared/business/utilities/sorting/caseSorting'; export const getPractitionerCasesInteractor = async ( applicationContext: ServerApplicationContext, @@ -59,7 +60,7 @@ export const getPractitionerCasesInteractor = async ( }); const [closedCases, openCases] = partition( - Case.sortByDocketNumber(caseDetails).reverse(), + sortByDocketNumber(caseDetails).reverse(), theCase => isClosed(theCase), ); diff --git a/shared/src/business/utilities/getFormattedCaseDetail.sortDocketEntries.test.ts b/shared/src/business/utilities/getFormattedCaseDetail.sortDocketEntries.test.ts deleted file mode 100644 index 5ad7a39a527..00000000000 --- a/shared/src/business/utilities/getFormattedCaseDetail.sortDocketEntries.test.ts +++ /dev/null @@ -1,204 +0,0 @@ -import { MOCK_DOCUMENTS } from '../../test/mockDocketEntry'; -import { applicationContext } from '../../../../web-client/src/applicationContext'; -import { sortDocketEntries } from './getFormattedCaseDetail'; - -describe('sortDocketEntries', () => { - const getDateISO = () => - applicationContext.getUtilities().createISODateString(); - - const mockDocketEntry = { - ...MOCK_DOCUMENTS[0], - createdAtFormatted: '2019-08-04T00:10:02.000Z', - }; - - it('should sort docket records by date by default', () => { - // following dates selected to ensure test coverage of 'dateStringsCompared' - const result = sortDocketEntries( - [ - { - ...mockDocketEntry, - filingDate: '2019-07-08', - index: 2, - }, - { - ...mockDocketEntry, - filingDate: '2019-08-03T00:06:44.000Z', - index: 1, - }, - { - ...mockDocketEntry, - filingDate: '2019-07-08T00:01:19.000Z', - index: 4, - }, - { - ...mockDocketEntry, - filingDate: '2017-01-01T00:01:02.025Z', - index: 3, - }, - { - ...mockDocketEntry, - filingDate: '2017-01-01T00:01:12.025Z', - index: 5, - }, - ], - 'Desc', - ); - - expect(result[0].index).toEqual(1); - }); - - it('should sort items by index when item calendar dates match', () => { - const result = sortDocketEntries( - [ - { - ...mockDocketEntry, - filingDate: '2019-08-03T00:10:02.000Z', // 8/2 @ 8:10:02PM EST - index: 2, - }, - { - ...mockDocketEntry, - filingDate: '2019-08-03T00:10:00.000Z', // 8/2 @ 8:10:00PM EST - index: 1, - }, - { - ...mockDocketEntry, - filingDate: '2019-08-03T02:06:10.000Z', // 8/2 @ 10:10:00PM EST - index: 4, - }, - { - ...mockDocketEntry, - filingDate: '2019-08-03T06:06:44.000Z', // 8/3 @ 2:10:02AM EST - index: 3, - }, - { - ...mockDocketEntry, - filingDate: '2019-09-01T00:01:12.025Z', // 8/31 @ 8:01:12AM EST - index: 5, - }, - ], - 'byDate', - ); - - expect(result[0].index).toEqual(1); - expect(result).toMatchObject([ - { - index: 1, - }, - { - index: 2, - }, - { - index: 4, - }, - { - index: 3, - }, - { - index: 5, - }, - ]); - }); - - it('should sort docket records by index when sortBy is byIndex', () => { - const result = sortDocketEntries( - [ - { - ...mockDocketEntry, - filingDate: getDateISO(), - index: 2, - }, - { - ...mockDocketEntry, - filingDate: getDateISO(), - index: 3, - }, - { - ...mockDocketEntry, - filingDate: getDateISO(), - index: 1, - }, - ], - 'byIndex', - ); - - expect(result[1].index).toEqual(2); - }); - - it('should sort docket records in reverse if Desc is included in sortBy', () => { - const result = sortDocketEntries( - [ - { - ...mockDocketEntry, - filingDate: getDateISO(), - index: 2, - }, - { - ...mockDocketEntry, - filingDate: getDateISO(), - index: 3, - }, - { - ...mockDocketEntry, - filingDate: getDateISO(), - index: 1, - }, - ], - 'byIndexDesc', - ); - - expect(result[0].index).toEqual(3); - }); - - it('should return empty array if nothing is passed in', () => { - const result = sortDocketEntries(); - - expect(result).toEqual([]); - }); - - it('should sort items that do not display a filingDate (based on createdAtFormatted) at the bottom', () => { - const result = sortDocketEntries( - [ - { - ...mockDocketEntry, - createdAtFormatted: '2019-08-04T00:10:02.000Z', - index: 2, - }, - { - ...mockDocketEntry, - createdAtFormatted: undefined, - }, - { - ...mockDocketEntry, - createdAtFormatted: '2019-08-03T00:10:02.000Z', - index: 1, - }, - { - ...mockDocketEntry, - createdAtFormatted: undefined, - }, - ], - 'byIndexDesc', - ); - - expect(result).toEqual([ - { - ...mockDocketEntry, - createdAtFormatted: '2019-08-04T00:10:02.000Z', - index: 2, - }, - { - ...mockDocketEntry, - createdAtFormatted: '2019-08-03T00:10:02.000Z', - index: 1, - }, - { - ...mockDocketEntry, - createdAtFormatted: undefined, - }, - { - ...mockDocketEntry, - createdAtFormatted: undefined, - }, - ]); - }); -}); diff --git a/shared/src/business/utilities/getFormattedCaseDetail.ts b/shared/src/business/utilities/getFormattedCaseDetail.ts index 2a96130c19a..09e4dfde044 100644 --- a/shared/src/business/utilities/getFormattedCaseDetail.ts +++ b/shared/src/business/utilities/getFormattedCaseDetail.ts @@ -7,10 +7,14 @@ import { TRANSCRIPT_EVENT_CODE, } from '../entities/EntityConstants'; import { Case, isSealedCase } from '../entities/cases/Case'; +import { + DOCKET_ENTRY_SORT_FIELDS, + DocketRecordSortInfo, + sortDocketEntries, +} from '@shared/business/utilities/sorting/docketEntrySorting'; import { DocketEntry } from '../entities/DocketEntry'; import { FORMATS, - calculateDifferenceInDays, combineISOandEasternTime, formatDateString, } from './DateHandler'; @@ -294,7 +298,10 @@ export const formatCase = ( formatDocketEntry(applicationContext, d), ); // establish an initial sort by ascending index - result.formattedDocketEntries.sort(byIndexSortFunction); + result.formattedDocketEntries = sortDocketEntries({ + docketEntries: result.formattedDocketEntries, + sortByField: DOCKET_ENTRY_SORT_FIELDS.index, + }); result.pendingItemsDocketEntries = result.formattedDocketEntries.filter( entry => applicationContext.getUtilities().isPending(entry), ); @@ -398,37 +405,6 @@ export const formatCase = ( return result; }; -const byIndexSortFunction = (a, b) => { - if (!a.index && !b.index) { - return 0; - } else if (!a.index) { - return 1; - } else if (!b.index) { - return -1; - } - return a.index - b.index; -}; - -const getDocketRecordSortFunc = sortByString => { - const byDate = (a, b) => { - const compared = calculateDifferenceInDays(a.filingDate, b.filingDate); - if (compared === 0) { - return byIndexSortFunction(a, b); - } - return compared; - }; - - switch (sortByString) { - case 'byIndex': // fall-through - case 'byIndexDesc': - return byIndexSortFunction; - case 'byDate': // fall through, is the default sort method - case 'byDateDesc': - default: - return byDate; - } -}; - const formatCounsel = ({ caseDetail, counsel }) => { let formattedName = counsel.name; @@ -454,46 +430,16 @@ const formatCounsel = ({ caseDetail, counsel }) => { return counsel; }; -// sort items that do not display a filingDate (based on createdAtFormatted) at the bottom -export const sortUndefined = ( - a: { createdAtFormatted: string }, - b: { createdAtFormatted: string }, -) => { - if (a.createdAtFormatted && !b.createdAtFormatted) { - return -1; - } - - if (!a.createdAtFormatted && b.createdAtFormatted) { - return 1; - } -}; - -export const sortDocketEntries = ( - docketEntries: (RawDocketEntry & { - createdAtFormatted: string | undefined; - })[] = [], - sortByString = '', -) => { - const sortFunc = getDocketRecordSortFunc(sortByString); - const isReversed = sortByString.includes('Desc'); - docketEntries.sort(sortFunc); - if (isReversed) { - // reversing AFTER the sort keeps sorting stable - return docketEntries.reverse().sort(sortUndefined); - } - return docketEntries.sort(sortUndefined); -}; - // Used by both front and backend export const getFormattedCaseDetail = ({ applicationContext, authorizedUser, caseDetail, - docketRecordSort, + docketRecordSortInfo, }: { applicationContext: IApplicationContext; caseDetail: RawCase; - docketRecordSort?: string; + docketRecordSortInfo?: DocketRecordSortInfo; authorizedUser: UnknownAuthUser; }) => { const result = { @@ -502,11 +448,12 @@ export const getFormattedCaseDetail = ({ .setServiceIndicatorsForCase(caseDetail), ...formatCase(applicationContext, caseDetail, authorizedUser), }; - result.formattedDocketEntries = sortDocketEntries( - result.formattedDocketEntries, - docketRecordSort, - ); - result.docketRecordSort = docketRecordSort; + result.formattedDocketEntries = sortDocketEntries({ + ascending: docketRecordSortInfo?.ascending, + docketEntries: result.formattedDocketEntries, + sortByField: docketRecordSortInfo?.sortByField, + }); + result.docketRecordSortInfo = docketRecordSortInfo; return result; }; diff --git a/shared/src/business/entities/cases/Case.sortByDocketNumber.test.ts b/shared/src/business/utilities/sorting/caseSorting.test.ts similarity index 88% rename from shared/src/business/entities/cases/Case.sortByDocketNumber.test.ts rename to shared/src/business/utilities/sorting/caseSorting.test.ts index e2b2029bb9b..eb04ac0afa8 100644 --- a/shared/src/business/entities/cases/Case.sortByDocketNumber.test.ts +++ b/shared/src/business/utilities/sorting/caseSorting.test.ts @@ -1,8 +1,11 @@ -import { Case } from './Case'; +import { + sortByDocketNumber, + sortByDocketNumberAndGroupConsolidatedCases, +} from '@shared/business/utilities/sorting/caseSorting'; describe('sortByDocketNumber', () => { it('Should return the cases as an array sorted by docket number for cases filed in the same year', () => { - const result = Case.sortByDocketNumber([ + const result = sortByDocketNumber([ { docketNumber: '910-19', }, @@ -28,7 +31,7 @@ describe('sortByDocketNumber', () => { }); it('Should return the cases as an array sorted by docket number for cases filed in different years', () => { - const result = Case.sortByDocketNumber([ + const result = sortByDocketNumber([ { docketNumber: '100-19', }, @@ -67,8 +70,8 @@ describe('sortByDocketNumber', () => { }); describe('sortByDocketNumberAndGroupConsolidatedCases', () => { - it('should return the cases sorted properly, with a consolidated case appearing after its lead case', () => { - const result = Case.sortByDocketNumberAndGroupConsolidatedCases([ + it('should return the cases sorted properly, with a consolidated case appearing after its lead cases', () => { + const result = sortByDocketNumberAndGroupConsolidatedCases([ { docketNumber: '100-19', }, @@ -97,9 +100,8 @@ describe('sortByDocketNumberAndGroupConsolidatedCases', () => { }, ]); }); - it('should return the cases sorted properly, when cases contains a consolidated grouping and the lead case is missing', () => { - const cases = Case.sortByDocketNumberAndGroupConsolidatedCases([ + const cases = sortByDocketNumberAndGroupConsolidatedCases([ { docketNumber: '102-20', leadDocketNumber: '101-20', @@ -131,9 +133,8 @@ describe('sortByDocketNumberAndGroupConsolidatedCases', () => { }), ]); }); - it('should return the cases sorted properly, with multiple consolidated cases appearing (in sorted order) after their lead cases', () => { - const cases = Case.sortByDocketNumberAndGroupConsolidatedCases([ + const cases = sortByDocketNumberAndGroupConsolidatedCases([ { docketNumber: '101-20', leadDocketNumber: '101-20', diff --git a/shared/src/business/utilities/sorting/caseSorting.ts b/shared/src/business/utilities/sorting/caseSorting.ts new file mode 100644 index 00000000000..1f7b12fa3c1 --- /dev/null +++ b/shared/src/business/utilities/sorting/caseSorting.ts @@ -0,0 +1,76 @@ +const getSortableDocketNumber = (docketNumber?: string) => { + if (!docketNumber) { + return; + } + + // NOTE: 1574-65 is the oldest case in DAWSON, which was filed in 1965 + const oldestYear = 65; + + const [sequentialNumber, yearFiled] = docketNumber.split('-'); + const sequentialNumberPadded = sequentialNumber.padStart(6, '0'); + const yearFiledAdjusted = + parseInt(yearFiled) >= oldestYear ? `19${yearFiled}` : `20${yearFiled}`; + + return parseInt(`${yearFiledAdjusted}${sequentialNumberPadded}`); +}; + +export const sortByDocketNumber = ( + cases: (T & { docketNumber: string })[], +): T[] => { + return cases.sort((a, b) => { + return docketNumberComparator(a.docketNumber, b.docketNumber); + }); +}; + +const docketNumberComparator = ( + docketNumberA?: string, + docketNumberB?: string, +) => { + return ( + (getSortableDocketNumber(docketNumberA) || 0) - + (getSortableDocketNumber(docketNumberB) || 0) + ); +}; + +export const sortByDocketNumberAndGroupConsolidatedCases = < + T extends { leadDocketNumber?: string; docketNumber: string }, +>( + cases: T[], +): T[] => { + let nonMemberCases: T[] = []; + let memberCases: { [key: string]: T[] } = {}; + + // Create a set of docket numbers for quick lookup + const docketNumbers = new Set(cases.map(c => c.docketNumber)); + + // Group cases into 1) lead or non-member cases and 2) valid non-lead, member cases + for (const c of cases) { + if ( + c.leadDocketNumber && + c.leadDocketNumber !== c.docketNumber && + docketNumbers.has(c.leadDocketNumber) // Check if the lead case exists; if not, treat as a lead/non-member case + ) { + (memberCases[c.leadDocketNumber] ||= []).push(c); + } else { + nonMemberCases.push(c); + } + } + + // Sort the lead/non-member cases + sortByDocketNumber(nonMemberCases); + + // Then, sort and interpolate the non-lead, member cases + const interpolatedCases: T[] = []; + for (const caseItem of nonMemberCases) { + interpolatedCases.push(caseItem); + + // Append and sort member cases inline if leadDocketNumber exists + if (memberCases[caseItem.docketNumber]) { + interpolatedCases.push( + ...sortByDocketNumber(memberCases[caseItem.docketNumber]), + ); + } + } + + return interpolatedCases; +}; diff --git a/shared/src/business/utilities/sorting/docketEntrySorting.test.ts b/shared/src/business/utilities/sorting/docketEntrySorting.test.ts new file mode 100644 index 00000000000..5a854d3e50e --- /dev/null +++ b/shared/src/business/utilities/sorting/docketEntrySorting.test.ts @@ -0,0 +1,257 @@ +import { + DOCKET_ENTRY_SORT_FIELDS, + sortDocketEntries, +} from '@shared/business/utilities/sorting/docketEntrySorting'; +import { MOCK_DOCUMENTS } from '@shared/test/mockDocketEntry'; +import { cloneDeep } from 'lodash'; + +describe('sortDocketEntries', () => { + const mockDocketEntry = { + ...MOCK_DOCUMENTS[0], + createdAtFormatted: '2019-08-04T00:10:02.000Z', + }; + + // following dates selected to ensure test coverage of 'dateStringsCompared' + const docketEntriesUnsorted = [ + { + ...mockDocketEntry, + filingDate: '2019-07-08', + index: 2, + }, + { + ...mockDocketEntry, + filingDate: '2019-08-03T00:06:44.000Z', + index: 1, + }, + { + ...mockDocketEntry, + filingDate: '2019-07-08T00:01:19.000Z', + index: 4, + }, + { + ...mockDocketEntry, + filingDate: '2017-01-01T00:01:02.025Z', + index: 3, + }, + { + ...mockDocketEntry, + filingDate: '2017-01-01T00:01:12.025Z', + index: 5, + }, + ]; + + it('should return empty array if nothing is passed in', () => { + const result = sortDocketEntries({ docketEntries: [] }); + + expect(result).toEqual([]); + }); + + describe('sorting by filing date', () => { + const expectedAscending = [ + { + ...mockDocketEntry, + filingDate: '2017-01-01T00:01:02.025Z', + index: 3, + }, + { + ...mockDocketEntry, + filingDate: '2017-01-01T00:01:12.025Z', + index: 5, + }, + { + ...mockDocketEntry, + filingDate: '2019-07-08T00:01:19.000Z', + index: 4, + }, + { + ...mockDocketEntry, + filingDate: '2019-07-08', // Since it has no timezone, it is converted to Eastern offset, and therefore before 2019-07-08T00:01:19.000Z + index: 2, + }, + { + ...mockDocketEntry, + filingDate: '2019-08-03T00:06:44.000Z', + index: 1, + }, + ]; + + it.each([true, false, undefined])( + 'should sort docket records by date by default, and ascending unless otherwise specified (ascending: %s)', + ascending => { + const expected = cloneDeep(expectedAscending); + if (ascending === false) { + expected.reverse(); + } + const result = sortDocketEntries({ + ascending, + docketEntries: docketEntriesUnsorted, + }); + + expect(result).toMatchObject(expected); + }, + ); + }); + + describe('sorting by index', () => { + const expectedAscending = [ + { + ...mockDocketEntry, + filingDate: '2019-08-03T00:06:44.000Z', + index: 1, + }, + { + ...mockDocketEntry, + filingDate: '2019-07-08', + index: 2, + }, + { + ...mockDocketEntry, + filingDate: '2017-01-01T00:01:02.025Z', + index: 3, + }, + { + ...mockDocketEntry, + filingDate: '2019-07-08T00:01:19.000Z', + index: 4, + }, + { + ...mockDocketEntry, + filingDate: '2017-01-01T00:01:12.025Z', + index: 5, + }, + ]; + + it.each([true, false, undefined])( + 'should sort docket records by index when specified, and ascending unless otherwise specified (ascending: %s)', + ascending => { + const expected = cloneDeep(expectedAscending); + if (ascending === false) { + expected.reverse(); + } + const result = sortDocketEntries({ + ascending, + docketEntries: docketEntriesUnsorted, + sortByField: DOCKET_ENTRY_SORT_FIELDS.index, + }); + + expect(result).toMatchObject(expected); + }, + ); + }); + + describe('sorting by date and then index', () => { + it('should sort items by index when item calendar dates match', () => { + const result = sortDocketEntries({ + docketEntries: [ + { + ...mockDocketEntry, + filingDate: '2019-08-03T00:10:02.000Z', // 8/2 @ 8:10:02PM EST + index: 2, + }, + { + ...mockDocketEntry, + filingDate: '2019-08-03T00:10:00.000Z', // 8/2 @ 8:10:00PM EST + index: 1, + }, + { + ...mockDocketEntry, + filingDate: '2019-08-03T02:06:10.000Z', // 8/2 @ 10:10:00PM EST + index: 4, + }, + { + ...mockDocketEntry, + filingDate: '2019-08-03T06:06:44.000Z', // 8/3 @ 2:10:02AM EST + index: 3, + }, + { + ...mockDocketEntry, + filingDate: '2019-09-01T00:01:12.025Z', // 8/31 @ 8:01:12AM EST + index: 5, + }, + ], + sortByField: DOCKET_ENTRY_SORT_FIELDS.filingDate, + }); + + expect(result[0].index).toEqual(1); + expect(result).toMatchObject([ + { + index: 1, + }, + { + index: 2, + }, + { + index: 4, + }, + { + index: 3, + }, + { + index: 5, + }, + ]); + }); + }); + + describe('sorting with entries that have no filingDate (based on createdAtFormatted)', () => { + it.each([ + [true, 'top'], + [false, 'bottom'], + [undefined, 'top'], + ])( + 'should sort items that do not display a filingDate (based on createdAtFormatted) with ascending=%s at the %s', + // eslint-disable-next-line @typescript-eslint/no-unused-vars + (ascending, _) => { + const result = sortDocketEntries({ + ascending, + docketEntries: [ + { + ...mockDocketEntry, + createdAtFormatted: '2019-08-04T00:10:02.000Z', + index: 2, + }, + { + ...mockDocketEntry, + createdAtFormatted: undefined, + }, + { + ...mockDocketEntry, + createdAtFormatted: '2019-08-03T00:10:02.000Z', + index: 1, + }, + { + ...mockDocketEntry, + createdAtFormatted: undefined, + }, + ], + sortByField: DOCKET_ENTRY_SORT_FIELDS.index, + }); + + const expectedAscending = [ + { + ...mockDocketEntry, + createdAtFormatted: undefined, + }, + { + ...mockDocketEntry, + createdAtFormatted: undefined, + }, + { + ...mockDocketEntry, + createdAtFormatted: '2019-08-03T00:10:02.000Z', + index: 1, + }, + { + ...mockDocketEntry, + createdAtFormatted: '2019-08-04T00:10:02.000Z', + index: 2, + }, + ]; + + expect(result).toEqual( + ascending !== false ? expectedAscending : expectedAscending.reverse(), + ); + }, + ); + }); +}); diff --git a/shared/src/business/utilities/sorting/docketEntrySorting.ts b/shared/src/business/utilities/sorting/docketEntrySorting.ts new file mode 100644 index 00000000000..15e2ffd88bc --- /dev/null +++ b/shared/src/business/utilities/sorting/docketEntrySorting.ts @@ -0,0 +1,87 @@ +import { calculateDifferenceInDays } from '@shared/business/utilities/DateHandler'; + +export const DOCKET_ENTRY_SORT_FIELDS = { + filingDate: 'filingDate', + index: 'index', +} as const; + +export type DocketEntrySortField = + (typeof DOCKET_ENTRY_SORT_FIELDS)[keyof typeof DOCKET_ENTRY_SORT_FIELDS]; + +export type DocketRecordSortInfo = { + sortByField: DocketEntrySortField; + ascending: boolean; +}; + +type Comparator = (a: T, b: T) => number; + +// This specifies the minimum fields we need to sort by +type SortableDocketEntry = { + index: number; + filingDate: string; + createdAtFormatted?: string; + [key: string]: unknown; +}; + +export const sortDocketEntries = ({ + ascending = true, + docketEntries = [], + sortByField = DOCKET_ENTRY_SORT_FIELDS.filingDate, +}: { + docketEntries: T[]; + sortByField?: DocketEntrySortField; + ascending?: boolean; +}): T[] => { + // Get the comparison function to use, then sort using it + const comparator = createDocketEntryComparator(sortByField, ascending); + return docketEntries.sort(comparator); +}; + +const createDocketEntryComparator = ( + fieldToSortBy: DocketEntrySortField, + ascending: boolean, +): ((a: SortableDocketEntry, b: SortableDocketEntry) => number) => { + const fieldComparators: Record< + DocketEntrySortField, + Comparator + > = { + filingDate: compareByFilingDate, + index: compareByIndex, + }; + + const fieldToSortByComparator = fieldComparators[fieldToSortBy]; + if (!fieldToSortByComparator) { + throw new Error(`Unsupported fieldToSortBy: ${fieldToSortBy}`); + } + + return (a, b) => { + // We group all entries without createdAtFormatted together, so do that comparison first. + // If it doesn't apply, continue to compare using the fieldToSortBy. + let comparisonResult = compareByCreatedAtExists(a, b); + if (comparisonResult === 0) { + comparisonResult = fieldToSortByComparator(a, b); + } + + return ascending ? comparisonResult : -1 * comparisonResult; + }; +}; + +const compareByFilingDate: Comparator = (a, b) => { + const dateComparison = calculateDifferenceInDays(a.filingDate, b.filingDate); + return dateComparison !== 0 ? dateComparison : compareByIndex(a, b); +}; + +const compareByIndex: Comparator = (a, b) => { + if (a.index === undefined && b.index === undefined) return 0; + if (a.index === undefined) return 1; + if (b.index === undefined) return -1; + return a.index - b.index; +}; + +const compareByCreatedAtExists: Comparator<{ + createdAtFormatted?: string; +}> = (a, b) => { + if (a.createdAtFormatted && !b.createdAtFormatted) return 1; + if (!a.createdAtFormatted && b.createdAtFormatted) return -1; + return 0; +}; diff --git a/shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails.compareCasesByDocketNumber.test.ts b/shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails.compareCasesByDocketNumber.test.ts deleted file mode 100644 index 74617e3f580..00000000000 --- a/shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails.compareCasesByDocketNumber.test.ts +++ /dev/null @@ -1,222 +0,0 @@ -import { - compareCasesByDocketNumber, - compareCasesByDocketNumberFactory, -} from './getFormattedTrialSessionDetails'; - -describe('getFormattedTrialSessionDetails.compareCasesByDocketNumber', () => { - it('101-19 should come before 102-19', () => { - const result = compareCasesByDocketNumber( - { - docketNumber: '101-19', - docketNumberSuffix: '', - docketNumberWithSuffix: '101-19', - isDocketSuffixHighPriority: false, - }, - { - docketNumber: '102-19', - docketNumberSuffix: 'P', - docketNumberWithSuffix: '102-19P', - isDocketSuffixHighPriority: true, - }, - ); - expect(result).toBe(-1); - }); - - it('190-07 should come before 102-19', () => { - const result = compareCasesByDocketNumber( - { - docketNumber: '190-07', - }, - { - docketNumber: '102-19', - }, - ); - expect(result).toBe(-1); - }); - - it('102-19 should equal 102-19', () => { - const result = compareCasesByDocketNumber( - { - docketNumber: '102-19', - docketNumberSuffix: '', - docketNumberWithSuffix: '102-19', - isDocketSuffixHighPriority: false, - }, - { - docketNumber: '102-19', - docketNumberSuffix: 'P', - docketNumberWithSuffix: '102-19P', - isDocketSuffixHighPriority: true, - }, - ); - expect(result).toBe(0); - }); -}); - -describe('getFormattedTrialSessionDetails.compareCasesByDocketNumberFactory', () => { - it('101-19 should come before 102-19', () => { - const result = compareCasesByDocketNumberFactory({ - allCases: [], - })( - { - docketNumber: '101-19', - docketNumberSuffix: '', - docketNumberWithSuffix: '101-19', - isDocketSuffixHighPriority: false, - }, - { - docketNumber: '102-19', - docketNumberSuffix: 'P', - docketNumberWithSuffix: '102-19P', - isDocketSuffixHighPriority: true, - }, - ); - expect(result).toBe(-1); - }); - - it('190-07 should come before 102-19', () => { - const result = compareCasesByDocketNumberFactory({ - allCases: [], - })( - { - docketNumber: '190-07', - }, - { - docketNumber: '102-19', - }, - ); - expect(result).toBe(-1); - }); - - it('102-19 should equal 102-19', () => { - const result = compareCasesByDocketNumberFactory({ - allCases: [], - })( - { - docketNumber: '102-19', - docketNumberSuffix: '', - docketNumberWithSuffix: '102-19', - isDocketSuffixHighPriority: false, - }, - { - docketNumber: '102-19', - docketNumberSuffix: 'P', - docketNumberWithSuffix: '102-19P', - isDocketSuffixHighPriority: true, - }, - ); - expect(result).toBe(0); - }); - - it('103-19 should come after 102-19', () => { - const result = compareCasesByDocketNumberFactory({ - allCases: [], - })( - { - docketNumber: '103-19', - docketNumberSuffix: '', - docketNumberWithSuffix: '103-19', - isDocketSuffixHighPriority: false, - }, - { - docketNumber: '102-19', - docketNumberSuffix: 'P', - docketNumberWithSuffix: '102-19P', - isDocketSuffixHighPriority: true, - }, - ); - expect(result).toBe(1); - }); - - it('a set of cases that contains a consolidated grouping', () => { - const cases = [ - { - docketNumber: '101-20', - leadDocketNumber: '101-20', - }, - { - docketNumber: '102-20', - leadDocketNumber: '101-20', - }, - { - docketNumber: '104-20', - leadDocketNumber: '101-20', - }, - { - docketNumber: '103-20', - }, - { - docketNumber: '110-19', - }, - ]; - - cases.sort( - compareCasesByDocketNumberFactory({ - allCases: [ - { - docketNumber: '101-20', - leadDocketNumber: '101-20', - }, - ], - }), - ); - - expect(cases).toEqual([ - expect.objectContaining({ - docketNumber: '110-19', - }), - expect.objectContaining({ - docketNumber: '101-20', - }), - expect.objectContaining({ - docketNumber: '102-20', - }), - expect.objectContaining({ - docketNumber: '104-20', - }), - expect.objectContaining({ - docketNumber: '103-20', - }), - ]); - }); - - it('a set of cases that contains a consolidated grouping and the lead case is missing', () => { - const cases = [ - { - docketNumber: '102-20', - leadDocketNumber: '101-20', - }, - { - docketNumber: '104-20', - leadDocketNumber: '101-20', - }, - { - docketNumber: '103-20', - }, - { - docketNumber: '110-19', - }, - ]; - - cases.sort( - compareCasesByDocketNumberFactory({ - allCases: [], - }), - ); - - expect(cases).toEqual([ - expect.objectContaining({ - docketNumber: '110-19', - }), - expect.objectContaining({ - docketNumber: '102-20', - }), - expect.objectContaining({ - docketNumber: '103-20', - }), - expect.objectContaining({ - docketNumber: '104-20', - }), - ]); - }); -}); diff --git a/shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails.ts b/shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails.ts index 84999be50b4..4a0dc00d33a 100644 --- a/shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails.ts +++ b/shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails.ts @@ -10,6 +10,7 @@ import { FORMATS } from '../DateHandler'; import { RawEligibleCase } from '../../entities/cases/EligibleCase'; import { RawIrsCalendarAdministratorInfo } from '@shared/business/entities/trialSessions/IrsCalendarAdministratorInfo'; import { compact, partition } from 'lodash'; +import { sortByDocketNumberAndGroupConsolidatedCases } from '@shared/business/utilities/sorting/caseSorting'; export const setPretrialMemorandumFiler = ({ caseItem }): string => { if (caseItem.PMTServedPartiesCode !== undefined) { @@ -111,47 +112,6 @@ export const formatCaseForTrialSession = ({ }; }; -const getDocketNumberSortString = ({ allCases = [], theCase }) => { - const leadCase = (allCases as { docketNumber: string }[]).find( - aCase => aCase.docketNumber === theCase.leadDocketNumber, - ); - - const isLeadCaseInList = !!theCase.leadDocketNumber && !!leadCase; - - return `${getSortableDocketNumber( - isLeadCaseInList - ? theCase.docketNumber === theCase.leadDocketNumber - ? theCase.docketNumber - : theCase.leadDocketNumber - : theCase.docketNumber, - )}-${getSortableDocketNumber(theCase.docketNumber)}`; -}; - -export const compareCasesByDocketNumberFactory = - ({ allCases }) => - (a, b) => { - const aSortString = getDocketNumberSortString({ - allCases, - theCase: a, - }); - const bSortString = getDocketNumberSortString({ - allCases, - theCase: b, - }); - return aSortString.localeCompare(bSortString); - }; - -const getSortableDocketNumber = docketNumber => { - const [number, year] = docketNumber.split('-'); - return `${year}-${number.padStart(6, '0')}`; -}; - -export const compareCasesByDocketNumber = (a, b) => { - const aSortString = getSortableDocketNumber(a.docketNumber); - const bSortString = getSortableDocketNumber(b.docketNumber); - return aSortString.localeCompare(bSortString); -}; - export type FormattedTrialSessionDetailsType = TrialSessionState & { allCases: any; formattedChambersPhoneNumber: string; @@ -194,37 +154,29 @@ export const getFormattedTrialSessionDetails = ({ item => item.removedFromTrial === true, ); - const openCasesFormatted = openCases - .map(caseItem => + const openCasesFormatted = sortByDocketNumberAndGroupConsolidatedCases( + openCases.map(caseItem => applicationContext .getUtilities() .setConsolidationFlagsForDisplay(caseItem, openCases), - ) - .sort(compareCasesByDocketNumberFactory({ allCases: openCases })); + ), + ); - const inactiveCasesFormatted = inactiveCases - .map(caseItem => + const inactiveCasesFormatted = sortByDocketNumberAndGroupConsolidatedCases( + inactiveCases.map(caseItem => applicationContext .getUtilities() .setConsolidationFlagsForDisplay(caseItem, inactiveCases), - ) - .sort( - compareCasesByDocketNumberFactory({ - allCases: inactiveCases, - }), - ); - - const allCasesFormatted = allCases - .map(caseItem => + ), + ); + + const allCasesFormatted = sortByDocketNumberAndGroupConsolidatedCases( + allCases.map(caseItem => applicationContext .getUtilities() .setConsolidationFlagsForDisplay(caseItem, allCases), - ) - .sort( - compareCasesByDocketNumberFactory({ - allCases, - }), - ); + ), + ); const formattedTerm = `${trialSession.term} ${trialSession.termYear.substr( -2, diff --git a/shared/src/proxies/generateDocketRecordPdfProxy.ts b/shared/src/proxies/generateDocketRecordPdfProxy.ts index 28633474aeb..4c102d26d5e 100644 --- a/shared/src/proxies/generateDocketRecordPdfProxy.ts +++ b/shared/src/proxies/generateDocketRecordPdfProxy.ts @@ -12,8 +12,7 @@ export const generateDocketRecordPdfInteractor = ( applicationContext, { docketNumber, - docketRecordSort, - docketRecordTableSort, + docketRecordSortInfo, includePartyDetail, isIndirectlyAssociated, }, @@ -22,8 +21,7 @@ export const generateDocketRecordPdfInteractor = ( applicationContext, body: { docketNumber, - docketRecordSort, - docketRecordTableSort, + docketRecordSortInfo, includePartyDetail, isIndirectlyAssociated, }, diff --git a/web-api/src/business/useCaseHelper/caseInventoryReport/generateCaseInventoryReportPdf.ts b/web-api/src/business/useCaseHelper/caseInventoryReport/generateCaseInventoryReportPdf.ts index 8abd58223fb..ba86e1d225d 100644 --- a/web-api/src/business/useCaseHelper/caseInventoryReport/generateCaseInventoryReportPdf.ts +++ b/web-api/src/business/useCaseHelper/caseInventoryReport/generateCaseInventoryReportPdf.ts @@ -5,6 +5,7 @@ import { import { ServerApplicationContext } from '@web-api/applicationContext'; import { UnauthorizedError } from '@web-api/errors/errors'; import { UnknownAuthUser } from '@shared/business/entities/authUser/AuthUser'; +import { sortByDocketNumberAndGroupConsolidatedCases } from '@shared/business/utilities/sorting/caseSorting'; export const generateCaseInventoryReportPdf = async ({ applicationContext, @@ -27,8 +28,7 @@ export const generateCaseInventoryReportPdf = async ({ applicationContext.logger.info('generateCaseInventoryReportPdf - start'); const { setConsolidationFlagsForDisplay } = applicationContext.getUtilities(); - const formattedCases = cases - .sort(applicationContext.getUtilities().compareCasesByDocketNumber) + const formattedCases = sortByDocketNumberAndGroupConsolidatedCases(cases) .map(caseItem => setConsolidationFlagsForDisplay(caseItem, [])) .map(caseItem => ({ ...caseItem, diff --git a/web-api/src/business/useCases/courtIssuedOrder/createCourtIssuedOrderPdfFromHtmlInteractor.ts b/web-api/src/business/useCases/courtIssuedOrder/createCourtIssuedOrderPdfFromHtmlInteractor.ts index 47fcf0b5f46..7cfb34a926d 100644 --- a/web-api/src/business/useCases/courtIssuedOrder/createCourtIssuedOrderPdfFromHtmlInteractor.ts +++ b/web-api/src/business/useCases/courtIssuedOrder/createCourtIssuedOrderPdfFromHtmlInteractor.ts @@ -2,7 +2,6 @@ import { CLERK_OF_THE_COURT_CONFIGURATION, NOTICE_EVENT_CODE, } from '@shared/business/entities/EntityConstants'; -import { Case } from '@shared/business/entities/cases/Case'; import { ROLE_PERMISSIONS, isAuthorized, @@ -11,6 +10,7 @@ import { ServerApplicationContext } from '@web-api/applicationContext'; import { UnauthorizedError } from '@web-api/errors/errors'; import { UnknownAuthUser } from '@shared/business/entities/authUser/AuthUser'; import { getCaseCaptionMeta } from '../../../../../shared/src/business/utilities/getCaseCaptionMeta'; +import { sortByDocketNumber } from '@shared/business/utilities/sorting/caseSorting'; export const createCourtIssuedOrderPdfFromHtmlInteractor = async ( applicationContext: ServerApplicationContext, @@ -65,9 +65,11 @@ export const createCourtIssuedOrderPdfFromHtmlInteractor = async ( const orderPdf = await applicationContext.getDocumentGenerators().order({ applicationContext, data: { - addedDocketNumbers: addedDocketNumbers.sort((a, b) => - Case.docketNumberSort(a, b), - ), + addedDocketNumbers: sortByDocketNumber<{ docketNumber: string }>( + addedDocketNumbers.map(d => { + return { docketNumber: d }; + }), + ).map(d => d.docketNumber), caseCaptionExtension, caseTitle, docketNumberWithSuffix, diff --git a/web-api/src/business/useCases/generateDocketRecordPdfInteractor.ts b/web-api/src/business/useCases/generateDocketRecordPdfInteractor.ts index 315a2469fec..b5f7b35442d 100644 --- a/web-api/src/business/useCases/generateDocketRecordPdfInteractor.ts +++ b/web-api/src/business/useCases/generateDocketRecordPdfInteractor.ts @@ -2,6 +2,7 @@ import { Case, getPractitionersRepresenting, } from '../../../../shared/src/business/entities/cases/Case'; +import { DocketRecordSortInfo } from '@shared/business/utilities/sorting/docketEntrySorting'; import { ROLE_PERMISSIONS, isAuthorized, @@ -10,32 +11,33 @@ import { ServerApplicationContext } from '@web-api/applicationContext'; import { UnauthorizedError } from '@web-api/errors/errors'; import { UnknownAuthUser } from '@shared/business/entities/authUser/AuthUser'; import { getCaseCaptionMeta } from '../../../../shared/src/business/utilities/getCaseCaptionMeta'; -import { sortDocketEntryTable } from '@web-client/presenter/computeds/formattedDocketEntries'; export const generateDocketRecordPdfInteractor = async ( applicationContext: ServerApplicationContext, { docketNumber, - docketRecordSort, - docketRecordTableSort, + docketRecordSortInfo, includePartyDetail = false, isIndirectlyAssociated = false, }: { docketNumber: string; - docketRecordSort?: string; - docketRecordTableSort?: { sortField: string; sortOrder: 'asc' | 'desc' }; + docketRecordSortInfo?: DocketRecordSortInfo; includePartyDetail: boolean; isIndirectlyAssociated?: boolean; }, authorizedUser: UnknownAuthUser, ) => { - const isDirectlyAssociated = await applicationContext - .getPersistenceGateway() - .verifyCaseForUser({ - applicationContext, - docketNumber, - userId: authorizedUser?.userId, - }); + let isDirectlyAssociated = false; + + if (authorizedUser?.userId) { + isDirectlyAssociated = await applicationContext + .getPersistenceGateway() + .verifyCaseForUser({ + applicationContext, + docketNumber, + userId: authorizedUser?.userId, + }); + } const caseSource = await applicationContext .getPersistenceGateway() @@ -80,7 +82,7 @@ export const generateDocketRecordPdfInteractor = async ( applicationContext, authorizedUser, caseDetail: caseEntity, - docketRecordSort, + docketRecordSortInfo, }); formattedCaseDetail.formattedDocketEntries = @@ -89,14 +91,6 @@ export const generateDocketRecordPdfInteractor = async ( numberOfPages: docketEntry.numberOfPages || 0, })); - const sortedDocketEntries = sortDocketEntryTable( - formattedCaseDetail.formattedDocketEntries, - docketRecordTableSort && docketRecordTableSort.sortField, - docketRecordTableSort && docketRecordTableSort.sortOrder, - ); - - formattedCaseDetail.formattedDocketEntries = sortedDocketEntries; - formattedCaseDetail.petitioners.forEach(petitioner => { petitioner.counselDetails = []; diff --git a/web-api/src/business/useCases/trialSessions/generateTrialCalendarPdfInteractor.ts b/web-api/src/business/useCases/trialSessions/generateTrialCalendarPdfInteractor.ts index b0a92b2801b..9827c8782af 100644 --- a/web-api/src/business/useCases/trialSessions/generateTrialCalendarPdfInteractor.ts +++ b/web-api/src/business/useCases/trialSessions/generateTrialCalendarPdfInteractor.ts @@ -1,9 +1,9 @@ import { NotFoundError } from '@web-api/errors/errors'; import { ServerApplicationContext } from '@web-api/applicationContext'; import { compact } from 'lodash'; -import { compareCasesByDocketNumberFactory } from '@shared/business/utilities/trialSession/getFormattedTrialSessionDetails'; import { formatDateString } from '@shared/business/utilities/DateHandler'; import { saveFileAndGenerateUrl } from '../../useCaseHelper/saveFileAndGenerateUrl'; +import { sortByDocketNumberAndGroupConsolidatedCases } from '@shared/business/utilities/sorting/caseSorting'; export const generateTrialCalendarPdfInteractor = async ( applicationContext: ServerApplicationContext, @@ -93,9 +93,8 @@ const formatCases = ({ applicationContext, calendaredCases }) => { const openCases = calendaredCases.filter( calendaredCase => !calendaredCase.removedFromTrial, ); - return openCases - .sort(compareCasesByDocketNumberFactory({ allCases: openCases })) - .map(openCase => { + return sortByDocketNumberAndGroupConsolidatedCases(openCases).map( + openCase => { const { inConsolidatedGroup, isLeadCase, shouldIndent } = applicationContext .getUtilities() @@ -113,7 +112,8 @@ const formatCases = ({ applicationContext, calendaredCases }) => { respondentCounsel: openCase.irsPractitioners.map(getPractitionerName), shouldIndent, }; - }); + }, + ); }; const getPractitionerName = practitioner => { diff --git a/web-api/src/getUtilities.ts b/web-api/src/getUtilities.ts index 7a4a921098b..fdcb9351d2f 100644 --- a/web-api/src/getUtilities.ts +++ b/web-api/src/getUtilities.ts @@ -14,10 +14,6 @@ import { } from '../../shared/src/business/utilities/DateHandler'; import { caseStatusWithTrialInformation } from '@shared/business/utilities/caseStatusWithTrialInformation'; import { combineTwoPdfs } from '../../shared/src/business/utilities/documentGenerators/combineTwoPdfs'; -import { - compareCasesByDocketNumber, - getFormattedTrialSessionDetails, -} from '../../shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails'; import { compareISODateStrings, compareStrings, @@ -40,6 +36,7 @@ import { import { getDocketEntriesByFilter } from '@shared/business/utilities/getDocketEntriesByFilter'; import { getDocumentTitleWithAdditionalInfo } from '../../shared/src/business/utilities/getDocumentTitleWithAdditionalInfo'; import { getFormattedCaseDetail } from '../../shared/src/business/utilities/getFormattedCaseDetail'; +import { getFormattedTrialSessionDetails } from '../../shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails'; import { getStampBoxCoordinates } from '../../shared/src/business/utilities/getStampBoxCoordinates'; import { isLeadCase, @@ -59,7 +56,6 @@ const utilities = { calculateISODate, caseStatusWithTrialInformation, combineTwoPdfs, - compareCasesByDocketNumber, compareISODateStrings, compareStrings, copyPagesAndAppendToTargetPdf, diff --git a/web-client/src/applicationContext.ts b/web-client/src/applicationContext.ts index 469b7b8a958..ebf343254b5 100644 --- a/web-client/src/applicationContext.ts +++ b/web-client/src/applicationContext.ts @@ -73,11 +73,6 @@ import { caseStatusWithTrialInformation } from '@shared/business/utilities/caseS import { changePasswordInteractor } from '@shared/proxies/auth/changePasswordProxy'; import { checkEmailAvailabilityInteractor } from '../../shared/src/proxies/users/checkEmailAvailabilityProxy'; import { closeTrialSessionInteractor } from '../../shared/src/proxies/trialSessions/closeTrialSessionProxy'; -import { - compareCasesByDocketNumber, - formatCaseForTrialSession, - getFormattedTrialSessionDetails, -} from '../../shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails'; import { compareISODateStrings, compareStrings, @@ -124,8 +119,11 @@ import { formatDocketEntry, getFilingsAndProceedings, getFormattedCaseDetail, - sortDocketEntries, } from '../../shared/src/business/utilities/getFormattedCaseDetail'; +import { + formatCaseForTrialSession, + getFormattedTrialSessionDetails, +} from '../../shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails'; import { formatDollars } from '../../shared/src/business/utilities/formatDollars'; import { formatJudgeName, @@ -279,6 +277,7 @@ import { setWorkItemAsReadInteractor } from '../../shared/src/proxies/workitems/ import { setupPdfDocument } from '../../shared/src/business/utilities/setupPdfDocument'; import { signUpUserInteractor } from '../../shared/src/proxies/signUpUserProxy'; import { sleep } from '@shared/tools/helpers'; +import { sortDocketEntries } from '@shared/business/utilities/sorting/docketEntrySorting'; import { startPollingForResultsInteractor } from '@shared/proxies/polling/startPollingForResultsProxy'; import { strikeDocketEntryInteractor } from '../../shared/src/proxies/editDocketEntry/strikeDocketEntryProxy'; import { submitCaseAssociationRequestInteractor } from '../../shared/src/proxies/documents/submitCaseAssociationRequestProxy'; @@ -768,7 +767,6 @@ const applicationContext = { caseHasServedPetition, caseStatusWithTrialInformation, checkDate, - compareCasesByDocketNumber, compareISODateStrings, compareStrings, createEndOfDayISO, diff --git a/web-client/src/applicationContextPublic.ts b/web-client/src/applicationContextPublic.ts index 6ff15b6d942..2cc1e509ac8 100644 --- a/web-client/src/applicationContextPublic.ts +++ b/web-client/src/applicationContextPublic.ts @@ -42,7 +42,6 @@ import { } from '../../shared/src/sharedAppContext'; import { User } from '../../shared/src/business/entities/User'; import { casePublicSearchInteractor } from '../../shared/src/proxies/casePublicSearchProxy'; -import { compareCasesByDocketNumber } from '../../shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails'; import { confirmSignUpInteractor } from '@shared/proxies/auth/confirmSignUpProxy'; import { createISODateString, @@ -50,10 +49,7 @@ import { formatNow, prepareDateFromString, } from '../../shared/src/business/utilities/DateHandler'; -import { - formatDocketEntry, - sortDocketEntries, -} from '../../shared/src/business/utilities/getFormattedCaseDetail'; +import { formatDocketEntry } from '../../shared/src/business/utilities/getFormattedCaseDetail'; import { generatePublicDocketRecordPdfInteractor } from '../../shared/src/proxies/public/generatePublicDocketRecordPdfProxy'; import { getAllFeatureFlagsInteractor } from '../../shared/src/proxies/featureFlag/getAllFeatureFlagsProxy'; import { getCaseForPublicDocketSearchInteractor } from '../../shared/src/proxies/public/getCaseForPublicDocketNumberSearchProxy'; @@ -86,6 +82,7 @@ import { removeItemInteractor } from '../../shared/src/business/useCases/removeI import { setItem } from './persistence/localStorage/setItem'; import { setItemInteractor } from '../../shared/src/business/useCases/setItemInteractor'; import { signUpUserInteractor } from '../../shared/src/proxies/signUpUserProxy'; +import { sortDocketEntries } from '@shared/business/utilities/sorting/docketEntrySorting'; import { tryCatchDecorator } from './tryCatchDecorator'; import { validateCaseAdvancedSearchInteractor } from '../../shared/src/business/useCases/validateCaseAdvancedSearchInteractor'; import { validateOpinionAdvancedSearchInteractor } from '../../shared/src/business/useCases/validateOpinionAdvancedSearchInteractor'; @@ -216,7 +213,6 @@ const applicationContextPublic = { getUseCases: () => allUseCases, getUtilities: () => { return { - compareCasesByDocketNumber, createISODateString, formatDateString, formatDocketEntry, diff --git a/web-client/src/presenter/actions/DocketRecord/setDefaultDocketRecordSortAction.test.ts b/web-client/src/presenter/actions/DocketRecord/setDefaultDocketRecordSortAction.test.ts index d8a7cb6cdae..91be1919eee 100644 --- a/web-client/src/presenter/actions/DocketRecord/setDefaultDocketRecordSortAction.test.ts +++ b/web-client/src/presenter/actions/DocketRecord/setDefaultDocketRecordSortAction.test.ts @@ -10,9 +10,9 @@ describe('setDefaultDocketRecordSortAction', () => { }, }); - expect(result.state.sessionMetadata.docketRecordSort['123-45']).toEqual( - 'something', - ); + expect( + result.state.sessionMetadata.docketRecordSortInfoByDocketNumber['123-45'], + ).toEqual('something'); }); it('should default docketRecordSort if current docketNumber does not match sessionMetadata docketNumber', async () => { @@ -23,8 +23,8 @@ describe('setDefaultDocketRecordSortAction', () => { }, }); - expect(result.state.sessionMetadata.docketRecordSort['987-65']).toEqual( - 'byDate', - ); + expect( + result.state.sessionMetadata.docketRecordSortInfoByDocketNumber['987-65'], + ).toEqual('byDate'); }); }); diff --git a/web-client/src/presenter/actions/DocketRecord/setDefaultDocketRecordSortAction.ts b/web-client/src/presenter/actions/DocketRecord/setDefaultDocketRecordSortAction.ts index 4a08bfbe59c..f82038b51ec 100644 --- a/web-client/src/presenter/actions/DocketRecord/setDefaultDocketRecordSortAction.ts +++ b/web-client/src/presenter/actions/DocketRecord/setDefaultDocketRecordSortAction.ts @@ -14,9 +14,14 @@ export const setDefaultDocketRecordSortAction = ({ store, }: ActionProps) => { const docketNumber = get(state.caseDetail.docketNumber); - const hasSort = get(state.sessionMetadata.docketRecordSort[docketNumber]); + const hasSort = get( + state.sessionMetadata.docketRecordSortInfoByDocketNumber[docketNumber], + ); if (!hasSort) { - store.set(state.sessionMetadata.docketRecordSort[docketNumber], 'byDate'); + store.set( + state.sessionMetadata.docketRecordSortInfoByDocketNumber[docketNumber], + 'byDate', + ); } }; diff --git a/web-client/src/presenter/actions/DocketRecord/toggleMobileDocketSortAction.test.ts b/web-client/src/presenter/actions/DocketRecord/toggleMobileDocketSortAction.test.ts index f608dc02df8..2d5ae6b2844 100644 --- a/web-client/src/presenter/actions/DocketRecord/toggleMobileDocketSortAction.test.ts +++ b/web-client/src/presenter/actions/DocketRecord/toggleMobileDocketSortAction.test.ts @@ -10,9 +10,9 @@ describe('toggleMobileDocketSortAction', () => { }, }); - expect(result.state.sessionMetadata.docketRecordSort['987-65']).toEqual( - 'byDateDesc', - ); + expect( + result.state.sessionMetadata.docketRecordSortInfoByDocketNumber['987-65'], + ).toEqual('byDateDesc'); }); it('should set sessionMetadata.docketRecordSort to byDate if it is currently byDateDesc', async () => { @@ -23,8 +23,8 @@ describe('toggleMobileDocketSortAction', () => { }, }); - expect(result.state.sessionMetadata.docketRecordSort['987-65']).toEqual( - 'byDate', - ); + expect( + result.state.sessionMetadata.docketRecordSortInfoByDocketNumber['987-65'], + ).toEqual('byDate'); }); }); diff --git a/web-client/src/presenter/actions/DocketRecord/toggleMobileDocketSortAction.ts b/web-client/src/presenter/actions/DocketRecord/toggleMobileDocketSortAction.ts index 90c7c3ed4a7..39f348d044f 100644 --- a/web-client/src/presenter/actions/DocketRecord/toggleMobileDocketSortAction.ts +++ b/web-client/src/presenter/actions/DocketRecord/toggleMobileDocketSortAction.ts @@ -9,7 +9,9 @@ import { state } from '@web-client/presenter/app.cerebral'; */ export const toggleMobileDocketSortAction = ({ get, store }: ActionProps) => { const docketNumber = get(state.caseDetail.docketNumber); - const currentSort = get(state.sessionMetadata.docketRecordSort[docketNumber]); + const currentSort = get( + state.sessionMetadata.docketRecordSortInfoByDocketNumber[docketNumber], + ); let newSort; switch (currentSort) { case 'byDate': @@ -20,5 +22,8 @@ export const toggleMobileDocketSortAction = ({ get, store }: ActionProps) => { newSort = 'byDate'; break; } - store.set(state.sessionMetadata.docketRecordSort[docketNumber], newSort); + store.set( + state.sessionMetadata.docketRecordSortInfoByDocketNumber[docketNumber], + newSort, + ); }; diff --git a/web-client/src/presenter/actions/generateDocketRecordPdfUrlAction.ts b/web-client/src/presenter/actions/generateDocketRecordPdfUrlAction.ts index a165b341f8d..dcd8fde43b1 100644 --- a/web-client/src/presenter/actions/generateDocketRecordPdfUrlAction.ts +++ b/web-client/src/presenter/actions/generateDocketRecordPdfUrlAction.ts @@ -1,4 +1,4 @@ -import { STATE_KEYS } from '@shared/business/entities/EntityConstants'; +import { DocketRecordSortInfo } from '@shared/business/utilities/sorting/docketEntrySorting'; import { state } from '@web-client/presenter/app.cerebral'; /** * get the pdf file and pdf blob url from the passed in htmlString @@ -13,15 +13,10 @@ export const generateDocketRecordPdfUrlAction = async ({ props, }: ActionProps) => { const caseDetail = get(state.caseDetail); - const docketRecordSort = get( - state.sessionMetadata.docketRecordSort[caseDetail.docketNumber], - ); - - const docketRecordSortField = get( - state[STATE_KEYS.DOCKET_RECORD_TABLE_SORT].sortField, - ); - const docketRecordSortOrder = get( - state[STATE_KEYS.DOCKET_RECORD_TABLE_SORT].sortOrder, + const docketRecordSortInfo: DocketRecordSortInfo = get( + state.sessionMetadata.docketRecordSortInfoByDocketNumber[ + caseDetail.docketNumber + ], ); const { isAssociated } = props; @@ -37,11 +32,7 @@ export const generateDocketRecordPdfUrlAction = async ({ .getUseCases() .generateDocketRecordPdfInteractor(applicationContext, { docketNumber: caseDetail.docketNumber, - docketRecordSort, - docketRecordTableSort: { - sortField: docketRecordSortField, - sortOrder: docketRecordSortOrder, - }, + docketRecordSortInfo, includePartyDetail, isIndirectlyAssociated, }); diff --git a/web-client/src/presenter/actions/setCaseAction.ts b/web-client/src/presenter/actions/setCaseAction.ts index 1d2a31ebe5f..a9e5a38eef4 100644 --- a/web-client/src/presenter/actions/setCaseAction.ts +++ b/web-client/src/presenter/actions/setCaseAction.ts @@ -1,4 +1,4 @@ -import { compareCasesByDocketNumber } from '@shared/business/utilities/trialSession/getFormattedTrialSessionDetails'; +import { sortByDocketNumberAndGroupConsolidatedCases } from '@shared/business/utilities/sorting/caseSorting'; import { state } from '@web-client/presenter/app.cerebral'; export const setCaseAction = ({ @@ -6,9 +6,8 @@ export const setCaseAction = ({ store, }: ActionProps<{ caseDetail: RawCase }>) => { const unsortedConsolidatedCases = props.caseDetail.consolidatedCases || []; - props.caseDetail.consolidatedCases = unsortedConsolidatedCases.sort( - compareCasesByDocketNumber, - ); + props.caseDetail.consolidatedCases = + sortByDocketNumberAndGroupConsolidatedCases(unsortedConsolidatedCases); store.set(state.caseDetail, props.caseDetail); }; diff --git a/web-client/src/presenter/computeds/Public/publicCaseDetailHelper.ts b/web-client/src/presenter/computeds/Public/publicCaseDetailHelper.ts index c0206acb487..15b63025511 100644 --- a/web-client/src/presenter/computeds/Public/publicCaseDetailHelper.ts +++ b/web-client/src/presenter/computeds/Public/publicCaseDetailHelper.ts @@ -4,16 +4,18 @@ import { PUBLIC_DOCKET_RECORD_FILTER, PUBLIC_DOCKET_RECORD_FILTER_OPTIONS, ROLES, - STATE_KEYS, } from '../../../../../shared/src/business/entities/EntityConstants'; import { ClientApplicationContext } from '@web-client/applicationContext'; import { DocketEntry } from '../../../../../shared/src/business/entities/DocketEntry'; +import { + DocketRecordSortInfo, + sortDocketEntries, +} from '@shared/business/utilities/sorting/docketEntrySorting'; import { Get } from 'cerebral'; import { computeIsNotServedDocument, getFilingsAndProceedings, } from '../../../../../shared/src/business/utilities/getFormattedCaseDetail'; -import { sortDocketEntryTable } from '@web-client/presenter/computeds/formattedDocketEntries'; import { state } from '@web-client/presenter/app-public.cerebral'; export const formatDocketEntryOnDocketRecord = ( @@ -151,6 +153,7 @@ export type PublicFormattedDocketEntryInfo = { sealedToTooltip: string; numberOfPages?: number; filedBy?: string; + filingDate: string; // DEVEX TODO? action?: string; showServed: boolean; showNotServed: boolean; @@ -180,8 +183,10 @@ export const publicCaseDetailHelper = ( const { canAllowPrintableDocketRecord, docketEntries, isSealed } = rawCase; const isTerminalUser = get(state.isTerminalUser); - const { sortField, sortOrder } = get( - state[STATE_KEYS.DOCKET_RECORD_TABLE_SORT], + const docketRecordSortInfo: DocketRecordSortInfo = get( + state.sessionMetadata.docketRecordSortInfoByDocketNumber[ + rawCase.docketNumber + ], ); const { docketRecordFilter } = get(state.sessionMetadata); @@ -207,11 +212,11 @@ export const publicCaseDetailHelper = ( ); const sortedAndFilteredFormattedDocketEntriesOnDocketRecord = - sortDocketEntryTable( - filteredFormattedDocketEntriesOnDocketRecord, - sortField, - sortOrder, - ); + sortDocketEntries({ + ascending: docketRecordSortInfo.ascending, + docketEntries: filteredFormattedDocketEntriesOnDocketRecord, + sortByField: docketRecordSortInfo.sortByField, + }); return { formattedDocketEntriesOnDocketRecord: diff --git a/web-client/src/presenter/computeds/Public/publicTrialSessionDetailsHelper.ts b/web-client/src/presenter/computeds/Public/publicTrialSessionDetailsHelper.ts index 1339a93a217..ea12b4c8af7 100644 --- a/web-client/src/presenter/computeds/Public/publicTrialSessionDetailsHelper.ts +++ b/web-client/src/presenter/computeds/Public/publicTrialSessionDetailsHelper.ts @@ -7,6 +7,7 @@ import { ClientPublicApplicationContext } from '@web-client/applicationContextPu import { FORMATS, formatNow } from '@shared/business/utilities/DateHandler'; import { Get } from 'cerebral'; import { compact, some } from 'lodash'; +import { sortByDocketNumberAndGroupConsolidatedCases } from '@shared/business/utilities/sorting/caseSorting'; import { state } from '@web-client/presenter/app-public.cerebral'; export type FormattedPublicTrialSession = { @@ -69,7 +70,7 @@ export const publicTrialSessionDetailsHelper = ( formattedCityStateZip, ]); - const formattedCases = Case.sortByDocketNumberAndGroupConsolidatedCases( + const formattedCases = sortByDocketNumberAndGroupConsolidatedCases( trialSession.calendaredCases, ).map(c => formatPublicCase(c)); diff --git a/web-client/src/presenter/computeds/blockedCasesReportHelper.ts b/web-client/src/presenter/computeds/blockedCasesReportHelper.ts index 411d41b2c53..5ad6f516f89 100644 --- a/web-client/src/presenter/computeds/blockedCasesReportHelper.ts +++ b/web-client/src/presenter/computeds/blockedCasesReportHelper.ts @@ -1,6 +1,7 @@ import { CaseStatus } from '@shared/business/entities/EntityConstants'; import { ClientApplicationContext } from '@web-client/applicationContext'; import { Get } from 'cerebral'; +import { sortByDocketNumberAndGroupConsolidatedCases } from '@shared/business/utilities/sorting/caseSorting'; import { state } from '@web-client/presenter/app.cerebral'; export const blockedCasesReportHelper = ( @@ -15,40 +16,40 @@ export const blockedCasesReportHelper = ( state.blockedCaseReportFilter, ); - const blockedCasesFormatted: BlockedFormattedCase[] = blockedCases - .sort(applicationContext.getUtilities().compareCasesByDocketNumber) - .map(blockedCase => { - const blockedCaseWithConsolidatedProperties = applicationContext - .getUtilities() - .setConsolidationFlagsForDisplay(blockedCase); + const blockedCasesFormatted: BlockedFormattedCase[] = + sortByDocketNumberAndGroupConsolidatedCases(blockedCases) + .map(blockedCase => { + const blockedCaseWithConsolidatedProperties = applicationContext + .getUtilities() + .setConsolidationFlagsForDisplay(blockedCase); - const updatedCase = { - ...setFormattedBlockDates( - blockedCaseWithConsolidatedProperties, - applicationContext, - ), - caseTitle: applicationContext.getCaseTitle( - blockedCase.caseCaption || '', - ), - docketNumberWithSuffix: blockedCase.docketNumberWithSuffix, - }; + const updatedCase = { + ...setFormattedBlockDates( + blockedCaseWithConsolidatedProperties, + applicationContext, + ), + caseTitle: applicationContext.getCaseTitle( + blockedCase.caseCaption || '', + ), + docketNumberWithSuffix: blockedCase.docketNumberWithSuffix, + }; - return updatedCase; - }) - .filter(blockedCase => { - return procedureTypeFilter && procedureTypeFilter !== 'All' - ? blockedCase.procedureType === procedureTypeFilter - : true; - }) - .filter(blockedCase => { - if (caseStatusFilter === 'All') return true; - return blockedCase.status === caseStatusFilter; - }) - .filter(blockedCase => { - if (reasonFilter === 'All') return true; - if (reasonFilter === 'Manual Block') return !!blockedCase.blockedReason; - return blockedCase.automaticBlockedReason === reasonFilter; - }); + return updatedCase; + }) + .filter(blockedCase => { + return procedureTypeFilter && procedureTypeFilter !== 'All' + ? blockedCase.procedureType === procedureTypeFilter + : true; + }) + .filter(blockedCase => { + if (caseStatusFilter === 'All') return true; + return blockedCase.status === caseStatusFilter; + }) + .filter(blockedCase => { + if (reasonFilter === 'All') return true; + if (reasonFilter === 'Manual Block') return !!blockedCase.blockedReason; + return blockedCase.automaticBlockedReason === reasonFilter; + }); return { blockedCasesCount: blockedCasesFormatted.length, diff --git a/web-client/src/presenter/computeds/caseInventoryReportHelper.ts b/web-client/src/presenter/computeds/caseInventoryReportHelper.ts index 7021e37fe0e..ed66e22c247 100644 --- a/web-client/src/presenter/computeds/caseInventoryReportHelper.ts +++ b/web-client/src/presenter/computeds/caseInventoryReportHelper.ts @@ -3,6 +3,7 @@ import { without } from 'lodash'; import { ClientApplicationContext } from '@web-client/applicationContext'; import { Get } from 'cerebral'; +import { sortByDocketNumberAndGroupConsolidatedCases } from '@shared/business/utilities/sorting/caseSorting'; export const caseInventoryReportHelper = ( get: Get, applicationContext: ClientApplicationContext, @@ -37,9 +38,9 @@ export const caseInventoryReportHelper = ( const reportData = get(state.caseInventoryReportData.foundCases) || []; const user = get(state.user); - const formattedReportData = reportData - .sort(applicationContext.getUtilities().compareCasesByDocketNumber) - .map(item => formatCase(applicationContext, item, user)); + const formattedReportData = sortByDocketNumberAndGroupConsolidatedCases( + reportData, + ).map(item => formatCase(applicationContext, item, user)); let displayedCount = resultCount < CASE_INVENTORY_PAGE_SIZE diff --git a/web-client/src/presenter/computeds/docketRecordHelper.ts b/web-client/src/presenter/computeds/docketRecordHelper.ts index 5bfa18e95dc..9db5cafe490 100644 --- a/web-client/src/presenter/computeds/docketRecordHelper.ts +++ b/web-client/src/presenter/computeds/docketRecordHelper.ts @@ -13,7 +13,10 @@ export const docketRecordHelper = ( sortLabelTextMobile: string; } => { const permissions = get(state.permissions); - const { docketRecordFilter, docketRecordSort } = get(state.sessionMetadata); + const { + docketRecordFilter, + docketRecordSortInfoByDocketNumber: docketRecordSort, + } = get(state.sessionMetadata); const { canAllowPrintableDocketRecord: showPrintableDocketRecord, docketEntries, diff --git a/web-client/src/presenter/computeds/formattedDocketEntries.ts b/web-client/src/presenter/computeds/formattedDocketEntries.ts index 11032a155cd..971734a7c70 100644 --- a/web-client/src/presenter/computeds/formattedDocketEntries.ts +++ b/web-client/src/presenter/computeds/formattedDocketEntries.ts @@ -1,10 +1,12 @@ /* eslint-disable complexity */ import { ClientApplicationContext } from '@web-client/applicationContext'; +import { + DOCKET_ENTRY_SORT_FIELDS, + DocketRecordSortInfo, +} from '@shared/business/utilities/sorting/docketEntrySorting'; import { DocketEntry } from '@shared/business/entities/DocketEntry'; import { Get } from 'cerebral'; -import { STATE_KEYS } from '@shared/business/entities/EntityConstants'; import { computeIsNotServedDocument } from '@shared/business/utilities/getFormattedCaseDetail'; -import { sortBy } from 'lodash'; import { state } from '@web-client/presenter/app.cerebral'; export const isSelectableForDownload = (entry: RawDocketEntry) => { @@ -206,21 +208,17 @@ export const formattedDocketEntries = ( const { ALLOWLIST_FEATURE_FLAGS } = applicationContext.getConstants(); const caseDetail = get(state.caseDetail); const { docketNumber } = caseDetail; - let docketRecordSort; + let docketRecordSortInfo: DocketRecordSortInfo = { + ascending: true, + sortByField: DOCKET_ENTRY_SORT_FIELDS.index, + }; const { formatCase } = applicationContext.getUtilities(); if (docketNumber) { - docketRecordSort = get( - state.sessionMetadata.docketRecordSort[docketNumber], + docketRecordSortInfo = get( + state.sessionMetadata.docketRecordSortInfoByDocketNumber[docketNumber], ); } - const docketRecordSortField = get( - state[STATE_KEYS.DOCKET_RECORD_TABLE_SORT].sortField, - ); - const docketRecordSortOrder = get( - state[STATE_KEYS.DOCKET_RECORD_TABLE_SORT].sortOrder, - ); - const DOCUMENT_VISIBILITY_POLICY_CHANGE_DATE = get( state.featureFlags[ ALLOWLIST_FEATURE_FLAGS.DOCUMENT_VISIBILITY_POLICY_CHANGE_DATE.key @@ -262,12 +260,6 @@ export const formattedDocketEntries = ( }; }); - docketEntriesFormatted = sortDocketEntryTable( - docketEntriesFormatted, - docketRecordSortField, - docketRecordSortOrder, - ); - const selectableDocumentsCount = docketEntriesFormatted.filter(entry => isSelectableForDownload(entry), ).length; @@ -312,36 +304,6 @@ export const formattedDocketEntries = ( }; }); - result.docketRecordSort = docketRecordSort; + result.docketRecordSort = docketRecordSortInfo; return result; }; - -export function sortDocketEntryTable( - docketEntries: (T & { sortingFilingDate: string | undefined })[] = [], - docketRecordSortField: string | undefined, - docketRecordSortOrder: 'asc' | 'desc' | undefined, -): T[] { - if (!docketRecordSortField || !docketRecordSortOrder) { - return sortBy(docketEntries, ['sortingFilingDate', 'index']); - } - - const sortedDocketEntries = sortBy(docketEntries, [ - docketRecordSortField, - 'index', - ]); - - if (docketRecordSortOrder === 'desc') { - return sortedDocketEntries.reverse().sort(sortUndefined); - } - - return sortedDocketEntries.sort(sortUndefined); -} - -function sortUndefined( - a: { sortingFilingDate: string | undefined }, - b: { sortingFilingDate: string | undefined }, -): number { - if (a.sortingFilingDate && !b.sortingFilingDate) return -1; - if (!a.sortingFilingDate && b.sortingFilingDate) return 1; - return 0; -} diff --git a/web-client/src/presenter/computeds/trialSessionWorkingCopyHelper.ts b/web-client/src/presenter/computeds/trialSessionWorkingCopyHelper.ts index 24a18508d4b..4d1b791dae7 100644 --- a/web-client/src/presenter/computeds/trialSessionWorkingCopyHelper.ts +++ b/web-client/src/presenter/computeds/trialSessionWorkingCopyHelper.ts @@ -1,14 +1,12 @@ import { ClientApplicationContext } from '@web-client/applicationContext'; -import { - FormattedTrialSessionCase, - compareCasesByDocketNumber, -} from '@shared/business/utilities/trialSession/getFormattedTrialSessionDetails'; +import { FormattedTrialSessionCase } from '@shared/business/utilities/trialSession/getFormattedTrialSessionDetails'; import { Get } from 'cerebral'; import { TRIAL_STATUS_TYPES } from '@shared/business/entities/EntityConstants'; import { TrialSessionState } from '@web-client/presenter/state/trialSessionState'; import { UserCaseNote } from '@shared/business/entities/notes/UserCaseNote'; import { isClosed, isLeadCase } from '@shared/business/entities/cases/Case'; import { partition, pickBy } from 'lodash'; +import { sortByDocketNumber } from '@shared/business/utilities/sorting/caseSorting'; import { state } from '@web-client/presenter/app.cerebral'; export type TrialSessionWorkingCopyCase = FormattedTrialSessionCase & { @@ -38,25 +36,26 @@ export const trialSessionWorkingCopyHelper = ( //get an array of strings of the trial statuses that are set to true const enabledTrialStatusFilters = Object.keys(pickBy(filters)); - const formattedCases = (trialSession.calendaredCases || []) - .slice() - .filter(isOpenCaseInATrial) - .filter(calendaredCase => - isCaseTrialStatusEnabledInFilters( - calendaredCase, - caseMetadata, - enabledTrialStatusFilters, + const formattedCases = sortByDocketNumber( + (trialSession.calendaredCases || []) + .slice() + .filter(isOpenCaseInATrial) + .filter(calendaredCase => + isCaseTrialStatusEnabledInFilters( + calendaredCase, + caseMetadata, + enabledTrialStatusFilters, + ), + ) + .map(caseItem => + applicationContext.getUtilities().formatCaseForTrialSession({ + applicationContext, + caseItem, + eligibleCases: trialSession.calendaredCases, + setFilingPartiesCode: true, + }), ), - ) - .map(caseItem => - applicationContext.getUtilities().formatCaseForTrialSession({ - applicationContext, - caseItem, - eligibleCases: trialSession.calendaredCases, - setFilingPartiesCode: true, - }), - ) - .sort(compareCasesByDocketNumber) + ) .map(aCase => appendUserNotes(aCase, userNotes)) .map(aCase => appendCalendarNotes(aCase, trialSession)) .map(aCase => { diff --git a/web-client/src/presenter/state-public.ts b/web-client/src/presenter/state-public.ts index a9c2a55eb3b..efb4ae79643 100644 --- a/web-client/src/presenter/state-public.ts +++ b/web-client/src/presenter/state-public.ts @@ -1,3 +1,4 @@ +import { DocketRecordSortInfo } from '@shared/business/utilities/sorting/docketEntrySorting'; import { PUBLIC_DOCKET_RECORD_FILTER_OPTIONS, PUBLIC_TRIAL_SESSIONS_DATA_KEY, @@ -95,7 +96,10 @@ export const baseState = { }, sessionMetadata: { docketRecordFilter: PUBLIC_DOCKET_RECORD_FILTER_OPTIONS.allDocuments, - docketRecordSort: {}, + docketRecordSortInfoByDocketNumber: [] as Record< + string, + DocketRecordSortInfo + >[], todaysOrdersSort: '', }, showPassword: false, diff --git a/web-client/src/presenter/state.ts b/web-client/src/presenter/state.ts index b3689889613..af4f4e231df 100644 --- a/web-client/src/presenter/state.ts +++ b/web-client/src/presenter/state.ts @@ -1,5 +1,6 @@ /* eslint-disable max-lines */ import { Contact } from '@shared/business/useCases/generatePetitionPdfInteractor'; +import { DocketRecordSortInfo } from '@shared/business/utilities/sorting/docketEntrySorting'; import { FormattedPendingMotionWithWorksheet } from '@web-api/business/useCases/pendingMotion/getPendingMotionDocketEntriesForCurrentJudgeInteractor'; import { GetCasesByStatusAndByJudgeResponse } from '@web-api/business/useCases/judgeActivityReport/getCaseWorksheetsByJudgeInteractor'; import { @@ -824,7 +825,10 @@ export const baseState = { selectedWorkItems: [], sessionMetadata: { docketRecordFilter: DOCKET_RECORD_FILTER_OPTIONS.allDocuments, - docketRecordSort: [], + docketRecordSortInfoByDocketNumber: [] as Record< + string, + DocketRecordSortInfo + >[], todaysOrdersSort: [], }, setSelectedConsolidatedCasesToMultiDocketOn: false, diff --git a/web-client/src/test/createClientTestApplicationContext.ts b/web-client/src/test/createClientTestApplicationContext.ts index 2cce4d6bf16..d6accf9465f 100644 --- a/web-client/src/test/createClientTestApplicationContext.ts +++ b/web-client/src/test/createClientTestApplicationContext.ts @@ -33,11 +33,6 @@ import { bulkIndexRecords } from '@web-api/persistence/elasticsearch/bulkIndexRe import { calculateDaysElapsedSinceLastStatusChange } from '@shared/business/utilities/calculateDaysElapsedSinceLastStatusChange'; import { calculateDifferenceInDays } from '@shared/business/utilities/DateHandler'; import { combineTwoPdfs } from '@shared/business/utilities/documentGenerators/combineTwoPdfs'; -import { - compareCasesByDocketNumber, - formatCaseForTrialSession, - getFormattedTrialSessionDetails, -} from '@shared/business/utilities/trialSession/getFormattedTrialSessionDetails'; import { compareISODateStrings, compareStrings, @@ -53,8 +48,11 @@ import { formatCase, formatDocketEntry, getFormattedCaseDetail, - sortDocketEntries, } from '@shared/business/utilities/getFormattedCaseDetail'; +import { + formatCaseForTrialSession, + getFormattedTrialSessionDetails, +} from '@shared/business/utilities/trialSession/getFormattedTrialSessionDetails'; import { formatDollars } from '@shared/business/utilities/formatDollars'; import { formatJudgeName, @@ -102,6 +100,7 @@ import { setItem } from '@web-client/persistence/localStorage/setItem'; import { setNoticesForCalendaredTrialSessionInteractor } from '@shared/proxies/trialSessions/setNoticesForCalendaredTrialSessionProxy'; import { setServiceIndicatorsForCase } from '@shared/business/utilities/setServiceIndicatorsForCase'; import { setupPdfDocument } from '@shared/business/utilities/setupPdfDocument'; +import { sortDocketEntries } from '@shared/business/utilities/sorting/docketEntrySorting'; import { unsealDocketEntryInteractor } from '@shared/proxies/editDocketEntry/unsealDocketEntryProxy'; import { updateCase } from '@web-api/persistence/dynamo/cases/updateCase'; import { updateCaseCorrespondence } from '@web-api/persistence/dynamo/correspondence/updateCaseCorrespondence'; @@ -187,9 +186,6 @@ const createTestApplicationContext = () => { caseHasServedPetition: jest.fn().mockImplementation(caseHasServedPetition), checkDate: jest.fn().mockImplementation(DateHandler.checkDate), combineTwoPdfs: jest.fn().mockImplementation(combineTwoPdfs), - compareCasesByDocketNumber: jest - .fn() - .mockImplementation(compareCasesByDocketNumber), compareISODateStrings: jest.fn().mockImplementation(compareISODateStrings), compareStrings: jest.fn().mockImplementation(compareStrings), computeDate: jest.fn().mockImplementation(DateHandler.computeDate),