From 6d3719439eee21d7e90467e5581ec2d9d12c8bc6 Mon Sep 17 00:00:00 2001 From: Brian Posey <15091170+btposey@users.noreply.github.com> Date: Tue, 12 Nov 2024 12:13:57 -0500 Subject: [PATCH 01/15] WIP - Decorate divisions with tags for filters * Added lists of invalid and legacy division codes. * Decordated the division model with tags. * Added INVALID and LEGACY tags to divisions in the lists. Jira ticket: CAMS-473 --- .../gateways/storage/local-storage-gateway.ts | 38 +++++++++++++++++-- .../functions/lib/adapters/types/storage.ts | 3 +- .../lib/use-cases/offices/offices.ts | 19 +++++++++- common/src/cams/courts.ts | 4 +- common/src/cams/offices.ts | 3 ++ 5 files changed, 60 insertions(+), 7 deletions(-) diff --git a/backend/functions/lib/adapters/gateways/storage/local-storage-gateway.ts b/backend/functions/lib/adapters/gateways/storage/local-storage-gateway.ts index 0b57bee98d..aa145d64e1 100644 --- a/backend/functions/lib/adapters/gateways/storage/local-storage-gateway.ts +++ b/backend/functions/lib/adapters/gateways/storage/local-storage-gateway.ts @@ -1,9 +1,12 @@ import { CamsRole } from '../../../../../../common/src/cams/roles'; import { StorageGateway } from '../../types/storage'; -import { USTP_OFFICES_ARRAY, UstpOfficeDetails } from '../../../../../../common/src/cams/offices'; - -let roleMapping; +import { + USTP_OFFICES_ARRAY, + UstpDivisionTag, + UstpOfficeDetails, +} from '../../../../../../common/src/cams/offices'; +let roleMapping: Map; export const ROLE_MAPPING_PATH = '/rolemapping.csv'; const ROLE_MAPPING = 'ad_group_name,idp_group_name,cams_role\n' + @@ -55,10 +58,39 @@ function getRoleMapping(): Map { return roleMapping; } +const INVALID_DIVISION_CODES = ['990', '991', '992', '993', '994', '995', '996', '999']; +const LEGACY_DIVISION_CODES = ['070']; + +let tagMapping: Map; + +function addUstpDivisionTagsToMap( + map: Map, + tag: UstpDivisionTag, + divisionCodes: string[], +) { + divisionCodes.forEach((divisionCode) => { + if (map.has(divisionCode)) { + map.get(divisionCode).push(tag); + } else { + map.set(divisionCode, [tag]); + } + }); +} + +function getUstpDivisionTags(): Map { + if (!tagMapping) { + tagMapping = new Map(); + addUstpDivisionTagsToMap(tagMapping, 'INVALID', INVALID_DIVISION_CODES); + addUstpDivisionTagsToMap(tagMapping, 'LEGACY', LEGACY_DIVISION_CODES); + } + return tagMapping; +} + export const LocalStorageGateway: StorageGateway = { get, getUstpOffices, getRoleMapping, + getUstpDivisionTags, }; export default LocalStorageGateway; diff --git a/backend/functions/lib/adapters/types/storage.ts b/backend/functions/lib/adapters/types/storage.ts index e380f16718..d7df8c4a6f 100644 --- a/backend/functions/lib/adapters/types/storage.ts +++ b/backend/functions/lib/adapters/types/storage.ts @@ -1,8 +1,9 @@ import { CamsRole } from '../../../../../common/src/cams/roles'; -import { UstpOfficeDetails } from '../../../../../common/src/cams/offices'; +import { UstpDivisionTag, UstpOfficeDetails } from '../../../../../common/src/cams/offices'; export type StorageGateway = { get(key: string): string | null; getUstpOffices(): UstpOfficeDetails[]; getRoleMapping(): Map; + getUstpDivisionTags(): Map; }; diff --git a/backend/functions/lib/use-cases/offices/offices.ts b/backend/functions/lib/use-cases/offices/offices.ts index 06ca6059d0..99a688e23a 100644 --- a/backend/functions/lib/use-cases/offices/offices.ts +++ b/backend/functions/lib/use-cases/offices/offices.ts @@ -17,8 +17,23 @@ const MODULE_NAME = 'OFFICES_USE_CASE'; export class OfficesUseCase { public async getOffices(context: ApplicationContext): Promise { - const gateway = getOfficesGateway(context); - return gateway.getOffices(context); + const officesGateway = getOfficesGateway(context); + const offices = await officesGateway.getOffices(context); + + const storageGateway = getStorageGateway(context); + const tags = storageGateway.getUstpDivisionTags(); + + offices.forEach((ustpOffice) => { + ustpOffice.groups.forEach((group) => { + group.divisions.forEach((division) => { + if (tags.has(division.divisionCode)) { + division.tags = tags.get(division.divisionCode); + } + }); + }); + }); + + return offices; } public async getOfficeAttorneys( diff --git a/common/src/cams/courts.ts b/common/src/cams/courts.ts index d78c6f3351..a6299868ed 100644 --- a/common/src/cams/courts.ts +++ b/common/src/cams/courts.ts @@ -1,4 +1,4 @@ -import { UstpOfficeDetails } from './offices'; +import { UstpDivisionTag, UstpOfficeDetails } from './offices'; import { CamsUserReference } from './users'; export function filterCourtByDivision(divisionCode: string, officeList: CourtDivisionDetails[]) { @@ -24,6 +24,7 @@ export function ustpOfficeToCourtDivision(ustp: UstpOfficeDetails): CourtDivisio groupDesignator: group.groupDesignator, regionId: ustp.regionId, regionName: ustp.regionName, + tags: division.tags, }); }); return acc; @@ -43,4 +44,5 @@ export type CourtDivisionDetails = { regionName: string; state?: string; staff?: CamsUserReference[]; + tags?: UstpDivisionTag[]; }; diff --git a/common/src/cams/offices.ts b/common/src/cams/offices.ts index 176f53523a..6b06bc21ca 100644 --- a/common/src/cams/offices.ts +++ b/common/src/cams/offices.ts @@ -17,10 +17,13 @@ export type UstpGroup = { divisions: UstpDivision[]; }; +export type UstpDivisionTag = 'LEGACY' | 'INVALID'; + export type UstpDivision = { divisionCode: string; // ACMS Div Code Office_Regions_and_Divisions.pdf court: Court; courtOffice: CourtOffice; + tags?: UstpDivisionTag[]; }; export type Court = { From ba4e459f8460b56e8754e15d8986b24aff37a130 Mon Sep 17 00:00:00 2001 From: Brian Posey <15091170+btposey@users.noreply.github.com> Date: Tue, 12 Nov 2024 12:52:48 -0500 Subject: [PATCH 02/15] Replace tags with isInvalid, isLegacy booleans Jira ticket: CAMS-473 --- backend/functions/lib/use-cases/offices/offices.ts | 8 +++++++- common/src/cams/courts.ts | 8 +++++--- common/src/cams/offices.ts | 3 ++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/backend/functions/lib/use-cases/offices/offices.ts b/backend/functions/lib/use-cases/offices/offices.ts index 99a688e23a..0353adfe3a 100644 --- a/backend/functions/lib/use-cases/offices/offices.ts +++ b/backend/functions/lib/use-cases/offices/offices.ts @@ -27,7 +27,13 @@ export class OfficesUseCase { ustpOffice.groups.forEach((group) => { group.divisions.forEach((division) => { if (tags.has(division.divisionCode)) { - division.tags = tags.get(division.divisionCode); + const divisionTags = tags.get(division.divisionCode); + if (divisionTags.includes('LEGACY')) { + division.isLegacy = true; + } + if (divisionTags.includes('INVALID')) { + division.isLegacy = true; + } } }); }); diff --git a/common/src/cams/courts.ts b/common/src/cams/courts.ts index a6299868ed..437fc1348c 100644 --- a/common/src/cams/courts.ts +++ b/common/src/cams/courts.ts @@ -1,4 +1,4 @@ -import { UstpDivisionTag, UstpOfficeDetails } from './offices'; +import { UstpOfficeDetails } from './offices'; import { CamsUserReference } from './users'; export function filterCourtByDivision(divisionCode: string, officeList: CourtDivisionDetails[]) { @@ -24,7 +24,8 @@ export function ustpOfficeToCourtDivision(ustp: UstpOfficeDetails): CourtDivisio groupDesignator: group.groupDesignator, regionId: ustp.regionId, regionName: ustp.regionName, - tags: division.tags, + isLegacy: division.isLegacy, + isInvalid: division.isInvalid, }); }); return acc; @@ -44,5 +45,6 @@ export type CourtDivisionDetails = { regionName: string; state?: string; staff?: CamsUserReference[]; - tags?: UstpDivisionTag[]; + isLegacy?: true; + isInvalid?: true; }; diff --git a/common/src/cams/offices.ts b/common/src/cams/offices.ts index 6b06bc21ca..bd7f880d5f 100644 --- a/common/src/cams/offices.ts +++ b/common/src/cams/offices.ts @@ -23,7 +23,8 @@ export type UstpDivision = { divisionCode: string; // ACMS Div Code Office_Regions_and_Divisions.pdf court: Court; courtOffice: CourtOffice; - tags?: UstpDivisionTag[]; + isLegacy?: true; + isInvalid?: true; }; export type Court = { From d6d7510188dc674f6f95f2e4e8fe481e0a99dc30 Mon Sep 17 00:00:00 2001 From: Brian Posey <15091170+btposey@users.noreply.github.com> Date: Tue, 12 Nov 2024 13:00:35 -0500 Subject: [PATCH 03/15] Add notes for the division tag meanings Jira ticket: CAMS-473 --- common/src/cams/offices.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/src/cams/offices.ts b/common/src/cams/offices.ts index bd7f880d5f..1110896e49 100644 --- a/common/src/cams/offices.ts +++ b/common/src/cams/offices.ts @@ -17,6 +17,10 @@ export type UstpGroup = { divisions: UstpDivision[]; }; +/** + * LEGACY - Show the division for historical purposes, but do not use it going forward. + * INVALID - Do not show the division in CAMS. Used to filter divisions from upstream systems. + */ export type UstpDivisionTag = 'LEGACY' | 'INVALID'; export type UstpDivision = { From 40a0f125b8927d57d37124dfc48f21448baf753c Mon Sep 17 00:00:00 2001 From: Brian Posey <15091170+btposey@users.noreply.github.com> Date: Tue, 12 Nov 2024 13:10:47 -0500 Subject: [PATCH 04/15] Simplify division meta data modeling Jira ticket: CAMS-473 --- .../gateways/storage/local-storage-gateway.ts | 28 +++++++++---------- .../functions/lib/adapters/types/storage.ts | 4 +-- .../lib/use-cases/offices/offices.ts | 14 ++++------ common/src/cams/offices.ts | 13 ++++----- 4 files changed, 26 insertions(+), 33 deletions(-) diff --git a/backend/functions/lib/adapters/gateways/storage/local-storage-gateway.ts b/backend/functions/lib/adapters/gateways/storage/local-storage-gateway.ts index aa145d64e1..93b7debbe4 100644 --- a/backend/functions/lib/adapters/gateways/storage/local-storage-gateway.ts +++ b/backend/functions/lib/adapters/gateways/storage/local-storage-gateway.ts @@ -2,7 +2,7 @@ import { CamsRole } from '../../../../../../common/src/cams/roles'; import { StorageGateway } from '../../types/storage'; import { USTP_OFFICES_ARRAY, - UstpDivisionTag, + UstpDivisionMeta, UstpOfficeDetails, } from '../../../../../../common/src/cams/offices'; @@ -61,36 +61,36 @@ function getRoleMapping(): Map { const INVALID_DIVISION_CODES = ['990', '991', '992', '993', '994', '995', '996', '999']; const LEGACY_DIVISION_CODES = ['070']; -let tagMapping: Map; +let metaMapping: Map; -function addUstpDivisionTagsToMap( - map: Map, - tag: UstpDivisionTag, +function addUstpDivisionMetaToMap( + map: Map, + meta: UstpDivisionMeta, divisionCodes: string[], ) { divisionCodes.forEach((divisionCode) => { if (map.has(divisionCode)) { - map.get(divisionCode).push(tag); + map.set(divisionCode, { ...map.get(divisionCode), ...meta }); } else { - map.set(divisionCode, [tag]); + map.set(divisionCode, meta); } }); } -function getUstpDivisionTags(): Map { - if (!tagMapping) { - tagMapping = new Map(); - addUstpDivisionTagsToMap(tagMapping, 'INVALID', INVALID_DIVISION_CODES); - addUstpDivisionTagsToMap(tagMapping, 'LEGACY', LEGACY_DIVISION_CODES); +function getUstpDivisionMeta(): Map { + if (!metaMapping) { + metaMapping = new Map(); + addUstpDivisionMetaToMap(metaMapping, { isInvalid: true }, INVALID_DIVISION_CODES); + addUstpDivisionMetaToMap(metaMapping, { isLegacy: true }, LEGACY_DIVISION_CODES); } - return tagMapping; + return metaMapping; } export const LocalStorageGateway: StorageGateway = { get, getUstpOffices, getRoleMapping, - getUstpDivisionTags, + getUstpDivisionMeta, }; export default LocalStorageGateway; diff --git a/backend/functions/lib/adapters/types/storage.ts b/backend/functions/lib/adapters/types/storage.ts index d7df8c4a6f..9636c96b17 100644 --- a/backend/functions/lib/adapters/types/storage.ts +++ b/backend/functions/lib/adapters/types/storage.ts @@ -1,9 +1,9 @@ import { CamsRole } from '../../../../../common/src/cams/roles'; -import { UstpDivisionTag, UstpOfficeDetails } from '../../../../../common/src/cams/offices'; +import { UstpDivisionMeta, UstpOfficeDetails } from '../../../../../common/src/cams/offices'; export type StorageGateway = { get(key: string): string | null; getUstpOffices(): UstpOfficeDetails[]; getRoleMapping(): Map; - getUstpDivisionTags(): Map; + getUstpDivisionMeta(): Map; }; diff --git a/backend/functions/lib/use-cases/offices/offices.ts b/backend/functions/lib/use-cases/offices/offices.ts index 0353adfe3a..9190fa58ad 100644 --- a/backend/functions/lib/use-cases/offices/offices.ts +++ b/backend/functions/lib/use-cases/offices/offices.ts @@ -21,19 +21,15 @@ export class OfficesUseCase { const offices = await officesGateway.getOffices(context); const storageGateway = getStorageGateway(context); - const tags = storageGateway.getUstpDivisionTags(); + const metas = storageGateway.getUstpDivisionMeta(); offices.forEach((ustpOffice) => { ustpOffice.groups.forEach((group) => { group.divisions.forEach((division) => { - if (tags.has(division.divisionCode)) { - const divisionTags = tags.get(division.divisionCode); - if (divisionTags.includes('LEGACY')) { - division.isLegacy = true; - } - if (divisionTags.includes('INVALID')) { - division.isLegacy = true; - } + if (metas.has(division.divisionCode)) { + const meta = metas.get(division.divisionCode); + division.isInvalid = meta.isInvalid; + division.isLegacy = meta.isLegacy; } }); }); diff --git a/common/src/cams/offices.ts b/common/src/cams/offices.ts index 1110896e49..e3c194d087 100644 --- a/common/src/cams/offices.ts +++ b/common/src/cams/offices.ts @@ -17,18 +17,15 @@ export type UstpGroup = { divisions: UstpDivision[]; }; -/** - * LEGACY - Show the division for historical purposes, but do not use it going forward. - * INVALID - Do not show the division in CAMS. Used to filter divisions from upstream systems. - */ -export type UstpDivisionTag = 'LEGACY' | 'INVALID'; +export type UstpDivisionMeta = { + isLegacy?: true; + isInvalid?: true; +}; -export type UstpDivision = { +export type UstpDivision = UstpDivisionMeta & { divisionCode: string; // ACMS Div Code Office_Regions_and_Divisions.pdf court: Court; courtOffice: CourtOffice; - isLegacy?: true; - isInvalid?: true; }; export type Court = { From 803dd28aaccc019adec1643d8b0e775e467d11c9 Mon Sep 17 00:00:00 2001 From: Brian Posey <15091170+btposey@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:03:13 -0500 Subject: [PATCH 05/15] Have CourtDivisionDetails extend UstpDivisionMeta Jira ticket: CAMS-473 --- common/src/cams/courts.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/common/src/cams/courts.ts b/common/src/cams/courts.ts index 437fc1348c..145c6c67b3 100644 --- a/common/src/cams/courts.ts +++ b/common/src/cams/courts.ts @@ -1,4 +1,4 @@ -import { UstpOfficeDetails } from './offices'; +import { UstpDivisionMeta, UstpOfficeDetails } from './offices'; import { CamsUserReference } from './users'; export function filterCourtByDivision(divisionCode: string, officeList: CourtDivisionDetails[]) { @@ -33,7 +33,7 @@ export function ustpOfficeToCourtDivision(ustp: UstpOfficeDetails): CourtDivisio return courtDivisions; } -export type CourtDivisionDetails = { +export type CourtDivisionDetails = UstpDivisionMeta & { officeName: string; officeCode: string; courtId: string; @@ -45,6 +45,4 @@ export type CourtDivisionDetails = { regionName: string; state?: string; staff?: CamsUserReference[]; - isLegacy?: true; - isInvalid?: true; }; From 6c9ba765e07da8e3a6fae94cc843293752bc43fe Mon Sep 17 00:00:00 2001 From: Brian Posey <15091170+btposey@users.noreply.github.com> Date: Tue, 12 Nov 2024 15:50:01 -0500 Subject: [PATCH 06/15] Apply invalid division filter to DXTR gateway Jira ticket: CAMS-473 --- .../lib/adapters/gateways/dxtr/offices.dxtr.gateway.ts | 7 ++++++- .../lib/adapters/gateways/storage/local-storage-gateway.ts | 2 -- backend/functions/lib/use-cases/offices/offices.ts | 4 +--- common/src/cams/courts.ts | 1 - common/src/cams/offices.ts | 1 - 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/backend/functions/lib/adapters/gateways/dxtr/offices.dxtr.gateway.ts b/backend/functions/lib/adapters/gateways/dxtr/offices.dxtr.gateway.ts index e3a395d1c9..777716791c 100644 --- a/backend/functions/lib/adapters/gateways/dxtr/offices.dxtr.gateway.ts +++ b/backend/functions/lib/adapters/gateways/dxtr/offices.dxtr.gateway.ts @@ -14,6 +14,11 @@ import { buildOfficeCode, getOfficeName } from '../../../use-cases/offices/offic const MODULE_NAME = 'OFFICES-GATEWAY'; +// Remove invalid divisions at the gateway rather than forcing the +// more important use case code to include logic to removing them. +const INVALID_DIVISION_CODES = ['990', '991', '992', '993', '994', '995', '996', '999']; +const INVALID_DIVISION_CODES_SQL = INVALID_DIVISION_CODES.map((code) => "'" + code + "'"); + type DxtrFlatOfficeDetails = { officeName: string; officeCode: string; @@ -82,7 +87,6 @@ export default class OfficesDxtrGateway implements OfficesGateway { async getOffices(context: ApplicationContext): Promise { const input: DbTableFieldSpec[] = []; - const query = ` SELECT a.[CS_DIV] AS courtDivisionCode ,a.[GRP_DES] AS groupDesignator @@ -98,6 +102,7 @@ export default class OfficesDxtrGateway implements OfficesGateway { JOIN [dbo].[AO_COURT] c on a.COURT_ID = c.COURT_ID JOIN [dbo].[AO_GRP_DES] d on a.GRP_DES = d.GRP_DES JOIN [dbo].[AO_REGION] r on d.REGION_ID = r.REGION_ID + WHERE a.[CS_DIV] not in (${INVALID_DIVISION_CODES_SQL}) ORDER BY a.GRP_DES, a.OFFICE_CODE`; const queryResult: QueryResults = await executeQuery( diff --git a/backend/functions/lib/adapters/gateways/storage/local-storage-gateway.ts b/backend/functions/lib/adapters/gateways/storage/local-storage-gateway.ts index 93b7debbe4..2e2d9efb7c 100644 --- a/backend/functions/lib/adapters/gateways/storage/local-storage-gateway.ts +++ b/backend/functions/lib/adapters/gateways/storage/local-storage-gateway.ts @@ -58,7 +58,6 @@ function getRoleMapping(): Map { return roleMapping; } -const INVALID_DIVISION_CODES = ['990', '991', '992', '993', '994', '995', '996', '999']; const LEGACY_DIVISION_CODES = ['070']; let metaMapping: Map; @@ -80,7 +79,6 @@ function addUstpDivisionMetaToMap( function getUstpDivisionMeta(): Map { if (!metaMapping) { metaMapping = new Map(); - addUstpDivisionMetaToMap(metaMapping, { isInvalid: true }, INVALID_DIVISION_CODES); addUstpDivisionMetaToMap(metaMapping, { isLegacy: true }, LEGACY_DIVISION_CODES); } return metaMapping; diff --git a/backend/functions/lib/use-cases/offices/offices.ts b/backend/functions/lib/use-cases/offices/offices.ts index 9190fa58ad..66ab95a3bd 100644 --- a/backend/functions/lib/use-cases/offices/offices.ts +++ b/backend/functions/lib/use-cases/offices/offices.ts @@ -27,9 +27,7 @@ export class OfficesUseCase { ustpOffice.groups.forEach((group) => { group.divisions.forEach((division) => { if (metas.has(division.divisionCode)) { - const meta = metas.get(division.divisionCode); - division.isInvalid = meta.isInvalid; - division.isLegacy = meta.isLegacy; + division.isLegacy = metas.get(division.divisionCode).isLegacy; } }); }); diff --git a/common/src/cams/courts.ts b/common/src/cams/courts.ts index 145c6c67b3..5e0a107229 100644 --- a/common/src/cams/courts.ts +++ b/common/src/cams/courts.ts @@ -25,7 +25,6 @@ export function ustpOfficeToCourtDivision(ustp: UstpOfficeDetails): CourtDivisio regionId: ustp.regionId, regionName: ustp.regionName, isLegacy: division.isLegacy, - isInvalid: division.isInvalid, }); }); return acc; diff --git a/common/src/cams/offices.ts b/common/src/cams/offices.ts index e3c194d087..5890405456 100644 --- a/common/src/cams/offices.ts +++ b/common/src/cams/offices.ts @@ -19,7 +19,6 @@ export type UstpGroup = { export type UstpDivisionMeta = { isLegacy?: true; - isInvalid?: true; }; export type UstpDivision = UstpDivisionMeta & { From d6c0e4bf42d54496719fbd79bed92246322b2f0d Mon Sep 17 00:00:00 2001 From: Brian Posey <15091170+btposey@users.noreply.github.com> Date: Tue, 12 Nov 2024 15:53:48 -0500 Subject: [PATCH 07/15] Explicitly delimit invalid division codes with comma Jira ticket: CAMS-473 --- .../lib/adapters/gateways/dxtr/offices.dxtr.gateway.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/functions/lib/adapters/gateways/dxtr/offices.dxtr.gateway.ts b/backend/functions/lib/adapters/gateways/dxtr/offices.dxtr.gateway.ts index 777716791c..e92bb4cf20 100644 --- a/backend/functions/lib/adapters/gateways/dxtr/offices.dxtr.gateway.ts +++ b/backend/functions/lib/adapters/gateways/dxtr/offices.dxtr.gateway.ts @@ -17,7 +17,7 @@ const MODULE_NAME = 'OFFICES-GATEWAY'; // Remove invalid divisions at the gateway rather than forcing the // more important use case code to include logic to removing them. const INVALID_DIVISION_CODES = ['990', '991', '992', '993', '994', '995', '996', '999']; -const INVALID_DIVISION_CODES_SQL = INVALID_DIVISION_CODES.map((code) => "'" + code + "'"); +const INVALID_DIVISION_CODES_SQL = INVALID_DIVISION_CODES.map((code) => "'" + code + "'").join(','); type DxtrFlatOfficeDetails = { officeName: string; From ba43daf4fc514945092c921153f17d73662f0001 Mon Sep 17 00:00:00 2001 From: Brian Posey <15091170+btposey@users.noreply.github.com> Date: Tue, 12 Nov 2024 16:19:32 -0500 Subject: [PATCH 08/15] Move division 070 to invalid division codes Jira ticket: CAMS-473 --- .../lib/adapters/gateways/dxtr/offices.dxtr.gateway.ts | 2 +- .../lib/adapters/gateways/storage/local-storage-gateway.ts | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/backend/functions/lib/adapters/gateways/dxtr/offices.dxtr.gateway.ts b/backend/functions/lib/adapters/gateways/dxtr/offices.dxtr.gateway.ts index e92bb4cf20..47372c2392 100644 --- a/backend/functions/lib/adapters/gateways/dxtr/offices.dxtr.gateway.ts +++ b/backend/functions/lib/adapters/gateways/dxtr/offices.dxtr.gateway.ts @@ -16,7 +16,7 @@ const MODULE_NAME = 'OFFICES-GATEWAY'; // Remove invalid divisions at the gateway rather than forcing the // more important use case code to include logic to removing them. -const INVALID_DIVISION_CODES = ['990', '991', '992', '993', '994', '995', '996', '999']; +const INVALID_DIVISION_CODES = ['070', '990', '991', '992', '993', '994', '995', '996', '999']; const INVALID_DIVISION_CODES_SQL = INVALID_DIVISION_CODES.map((code) => "'" + code + "'").join(','); type DxtrFlatOfficeDetails = { diff --git a/backend/functions/lib/adapters/gateways/storage/local-storage-gateway.ts b/backend/functions/lib/adapters/gateways/storage/local-storage-gateway.ts index 2e2d9efb7c..038994f52f 100644 --- a/backend/functions/lib/adapters/gateways/storage/local-storage-gateway.ts +++ b/backend/functions/lib/adapters/gateways/storage/local-storage-gateway.ts @@ -27,6 +27,9 @@ const OFFICE_MAPPING = 'USTP_CAMS_Region_2_Office_New_Haven,USTP CAMS Region 2 Office New Haven,NH\n' + 'USTP_CAMS_Region_18_Office_Seattle,USTP CAMS Region 18 Office Seattle,SE|AK\n'; +let metaMapping: Map; +const LEGACY_DIVISION_CODES = []; + const storage = new Map(); storage.set(ROLE_MAPPING_PATH, ROLE_MAPPING); storage.set(OFFICE_MAPPING_PATH, OFFICE_MAPPING); @@ -58,10 +61,6 @@ function getRoleMapping(): Map { return roleMapping; } -const LEGACY_DIVISION_CODES = ['070']; - -let metaMapping: Map; - function addUstpDivisionMetaToMap( map: Map, meta: UstpDivisionMeta, From 1ec7ec89d6649c705182c3d4ad38aedbc4b94968 Mon Sep 17 00:00:00 2001 From: Brian Posey <15091170+btposey@users.noreply.github.com> Date: Tue, 12 Nov 2024 16:21:57 -0500 Subject: [PATCH 09/15] Remove legacy divisions from data verification Jira ticket: CAMS-473 --- user-interface/src/data-verification/DataVerificationScreen.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user-interface/src/data-verification/DataVerificationScreen.tsx b/user-interface/src/data-verification/DataVerificationScreen.tsx index d506b56456..9ff3eff6c1 100644 --- a/user-interface/src/data-verification/DataVerificationScreen.tsx +++ b/user-interface/src/data-verification/DataVerificationScreen.tsx @@ -80,7 +80,7 @@ export default function DataVerificationScreen() { .getCourts() .then((response) => { const courts = (response as ResponseBody).data; - setCourts(courts.sort(courtSorter)); + setCourts(courts.filter((division) => !division.isLegacy).sort(courtSorter)); setRegionsMap( courts.reduce((regionsMap, court) => { if (!regionsMap.has(court.regionId)) { From d63b076cadd61bc414dd48b90f39808bfec80bc9 Mon Sep 17 00:00:00 2001 From: Brian Posey <15091170+btposey@users.noreply.github.com> Date: Tue, 12 Nov 2024 16:33:41 -0500 Subject: [PATCH 10/15] Fix broken import causing test failure locally Jira ticket: CAMS-473 --- .../functions/lib/controllers/orders/orders.controller.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/functions/lib/controllers/orders/orders.controller.test.ts b/backend/functions/lib/controllers/orders/orders.controller.test.ts index 59a06eadc2..dfb20501a5 100644 --- a/backend/functions/lib/controllers/orders/orders.controller.test.ts +++ b/backend/functions/lib/controllers/orders/orders.controller.test.ts @@ -21,6 +21,7 @@ import { mockCamsHttpRequest } from '../../testing/mock-data/cams-http-request-h import { ResponseBody } from '../../../../../common/src/api/response'; import { NotFoundError } from '../../common-errors/not-found-error'; import { BadRequestError } from '../../common-errors/bad-request'; +import * as crypto from 'crypto'; const syncResponse: SyncOrdersStatus = { options: { From f6b6cb4fd74e6e702d38259e50aa0509b6c5bae6 Mon Sep 17 00:00:00 2001 From: Brian Posey <15091170+btposey@users.noreply.github.com> Date: Tue, 12 Nov 2024 16:34:36 -0500 Subject: [PATCH 11/15] Fix grammar in comment Jira ticket: CAMS-473 --- .../lib/adapters/gateways/dxtr/offices.dxtr.gateway.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/functions/lib/adapters/gateways/dxtr/offices.dxtr.gateway.ts b/backend/functions/lib/adapters/gateways/dxtr/offices.dxtr.gateway.ts index 47372c2392..0eba548cc0 100644 --- a/backend/functions/lib/adapters/gateways/dxtr/offices.dxtr.gateway.ts +++ b/backend/functions/lib/adapters/gateways/dxtr/offices.dxtr.gateway.ts @@ -15,7 +15,7 @@ import { buildOfficeCode, getOfficeName } from '../../../use-cases/offices/offic const MODULE_NAME = 'OFFICES-GATEWAY'; // Remove invalid divisions at the gateway rather than forcing the -// more important use case code to include logic to removing them. +// more important use case code to include logic to remove them. const INVALID_DIVISION_CODES = ['070', '990', '991', '992', '993', '994', '995', '996', '999']; const INVALID_DIVISION_CODES_SQL = INVALID_DIVISION_CODES.map((code) => "'" + code + "'").join(','); From 8f1e49629c6f1d8f70ac64600272a5ffce36b9e1 Mon Sep 17 00:00:00 2001 From: Brian Posey <15091170+btposey@users.noreply.github.com> Date: Tue, 12 Nov 2024 16:56:48 -0500 Subject: [PATCH 12/15] Test coverage for division isLegacy boolean Jira ticket: CAMS-473 --- .../lib/use-cases/offices/offices.test.ts | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/backend/functions/lib/use-cases/offices/offices.test.ts b/backend/functions/lib/use-cases/offices/offices.test.ts index bd731ee747..e83467242f 100644 --- a/backend/functions/lib/use-cases/offices/offices.test.ts +++ b/backend/functions/lib/use-cases/offices/offices.test.ts @@ -6,7 +6,7 @@ import OktaUserGroupGateway from '../../adapters/gateways/okta/okta-user-group-g import { UserGroupGatewayConfig } from '../../adapters/types/authorization'; import { CamsUserGroup, Staff } from '../../../../../common/src/cams/users'; import MockData from '../../../../../common/src/cams/test-utilities/mock-data'; -import { USTP_OFFICES_ARRAY } from '../../../../../common/src/cams/offices'; +import { USTP_OFFICES_ARRAY, UstpDivisionMeta } from '../../../../../common/src/cams/offices'; import { TRIAL_ATTORNEYS } from '../../../../../common/src/cams/test-utilities/attorneys.mock'; import AttorneysList from '../attorneys'; import { MockMongoRepository } from '../../testing/mock-gateways/mock-mongo.repository'; @@ -39,6 +39,44 @@ describe('offices use case tests', () => { expect(offices).toEqual(USTP_OFFICES_ARRAY); }); + test('should flag legacy offices', async () => { + const useCase = new OfficesUseCase(); + const manhattanOffice = USTP_OFFICES_ARRAY.find( + (office) => office.officeCode === 'USTP_CAMS_Region_2_Office_Manhattan', + ); + + const legacyDivisionCode = '087'; + const officeWithLegacyFlag = { ...manhattanOffice }; + officeWithLegacyFlag.groups[0].divisions.find( + (d) => d.divisionCode === legacyDivisionCode, + ).isLegacy = true; + const expectedOffices = [officeWithLegacyFlag]; + + jest.spyOn(factory, 'getOfficesGateway').mockImplementation(() => { + return { + getOfficeName: jest.fn(), + getOffices: jest.fn().mockResolvedValue([manhattanOffice]), + }; + }); + + jest.spyOn(factory, 'getStorageGateway').mockImplementation(() => { + return { + get: jest.fn(), + getRoleMapping: jest.fn(), + getUstpOffices: jest.fn(), + getUstpDivisionMeta: jest + .fn() + .mockReturnValue( + new Map([[legacyDivisionCode, { isLegacy: true }]]), + ), + }; + }); + + const offices = await useCase.getOffices(applicationContext); + + expect(offices).toEqual(expectedOffices); + }); + test('should return default attorneys with feature flag off', async () => { const localContext = { ...applicationContext, From 9b34f705a74dfcb96bd887743285769b54ca0249 Mon Sep 17 00:00:00 2001 From: Brian Posey <15091170+btposey@users.noreply.github.com> Date: Wed, 13 Nov 2024 10:16:50 -0500 Subject: [PATCH 13/15] Identify legacy divisions in search options Jira ticket: CAMS-473 --- .../DataVerificationScreen.test.tsx | 108 +------------- .../DataVerificationScreen.tsx | 8 +- .../dataVerificationHelper.test.ts | 132 +++++++++++++++++- .../dataVerificationHelper.ts | 13 +- user-interface/src/search/SearchScreen.tsx | 3 +- 5 files changed, 146 insertions(+), 118 deletions(-) diff --git a/user-interface/src/data-verification/DataVerificationScreen.test.tsx b/user-interface/src/data-verification/DataVerificationScreen.test.tsx index 6982887d97..6be9deb020 100644 --- a/user-interface/src/data-verification/DataVerificationScreen.test.tsx +++ b/user-interface/src/data-verification/DataVerificationScreen.test.tsx @@ -1,5 +1,5 @@ import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; -import DataVerificationScreen, { courtSorter } from './DataVerificationScreen'; +import DataVerificationScreen from './DataVerificationScreen'; import { BrowserRouter } from 'react-router-dom'; import { formatDate } from '@/lib/utils/datetime'; import { @@ -8,7 +8,6 @@ import { ConsolidationOrder, isConsolidationOrder, } from '@common/cams/orders'; -import { CourtDivisionDetails } from '@common/cams/courts'; import * as FeatureFlagHook from '@/lib/hooks/UseFeatureFlags'; import Api2 from '@/lib/models/api2'; import MockData from '@common/cams/test-utilities/mock-data'; @@ -29,111 +28,6 @@ describe('Review Orders screen', () => { vi.clearAllMocks(); }); - test('should sort offices', () => { - const testOffices: CourtDivisionDetails[] = [ - { - courtDivisionCode: '001', - groupDesignator: 'AA', - courtId: '0101', - officeCode: '1', - officeName: 'A1', - state: 'NY', - courtName: 'A', - courtDivisionName: 'New York 1', - regionId: '02', - regionName: 'NEW YORK', - }, - { - courtDivisionCode: '003', - groupDesignator: 'AC', - courtId: '0103', - officeCode: '3', - officeName: 'C1', - state: 'NY', - courtName: 'C', - courtDivisionName: 'New York 1', - regionId: '02', - regionName: 'NEW YORK', - }, - { - courtDivisionCode: '003', - groupDesignator: 'AC', - courtId: '0103', - officeCode: '3', - officeName: 'C1', - state: 'NY', - courtName: 'C', - courtDivisionName: 'New York 1', - regionId: '02', - regionName: 'NEW YORK', - }, - { - courtDivisionCode: '002', - groupDesignator: 'AB', - courtId: '0102', - officeCode: '2', - officeName: 'B1', - state: 'NY', - courtName: 'B', - courtDivisionName: 'New York 1', - regionId: '02', - regionName: 'NEW YORK', - }, - ]; - const expectedOffices: CourtDivisionDetails[] = [ - { - courtDivisionCode: '001', - groupDesignator: 'AA', - courtId: '0101', - officeCode: '1', - officeName: 'A1', - state: 'NY', - courtName: 'A', - courtDivisionName: 'New York 1', - regionId: '02', - regionName: 'NEW YORK', - }, - { - courtDivisionCode: '002', - groupDesignator: 'AB', - courtId: '0102', - officeCode: '2', - officeName: 'B1', - state: 'NY', - courtName: 'B', - courtDivisionName: 'New York 1', - regionId: '02', - regionName: 'NEW YORK', - }, - { - courtDivisionCode: '003', - groupDesignator: 'AC', - courtId: '0103', - officeCode: '3', - officeName: 'C1', - state: 'NY', - courtName: 'C', - courtDivisionName: 'New York 1', - regionId: '02', - regionName: 'NEW YORK', - }, - { - courtDivisionCode: '003', - groupDesignator: 'AC', - courtId: '0103', - officeCode: '3', - officeName: 'C1', - state: 'NY', - courtName: 'C', - courtDivisionName: 'New York 1', - regionId: '02', - regionName: 'NEW YORK', - }, - ]; - const actualOffices = testOffices.sort(courtSorter); - expect(actualOffices).toEqual(expectedOffices); - }); - test('should toggle filter button', async () => { render( diff --git a/user-interface/src/data-verification/DataVerificationScreen.tsx b/user-interface/src/data-verification/DataVerificationScreen.tsx index 9ff3eff6c1..1e230dcde9 100644 --- a/user-interface/src/data-verification/DataVerificationScreen.tsx +++ b/user-interface/src/data-verification/DataVerificationScreen.tsx @@ -26,13 +26,7 @@ import { ResponseBody } from '@common/api/response'; import { CamsRole } from '@common/cams/roles'; import LocalStorage from '@/lib/utils/local-storage'; import { useGlobalAlert } from '@/lib/hooks/UseGlobalAlert'; - -export function courtSorter(a: CourtDivisionDetails, b: CourtDivisionDetails) { - const aKey = a.courtName + '-' + a.courtDivisionName; - const bKey = b.courtName + '-' + b.courtDivisionName; - if (aKey === bKey) return 0; - return aKey > bKey ? 1 : -1; -} +import { courtSorter } from './dataVerificationHelper'; export default function DataVerificationScreen() { const featureFlags = useFeatureFlags(); diff --git a/user-interface/src/data-verification/dataVerificationHelper.test.ts b/user-interface/src/data-verification/dataVerificationHelper.test.ts index 5b44f8fd3b..fa134e48a3 100644 --- a/user-interface/src/data-verification/dataVerificationHelper.test.ts +++ b/user-interface/src/data-verification/dataVerificationHelper.test.ts @@ -1,6 +1,6 @@ import { describe } from 'vitest'; import { CourtDivisionDetails } from '@common/cams/courts'; -import { getOfficeList } from './dataVerificationHelper'; +import { courtSorter, getOfficeList } from './dataVerificationHelper'; describe('data verification helper tests', () => { test('should properly map court information for selection', () => { @@ -95,4 +95,134 @@ describe('data verification helper tests', () => { const actualOptions = getOfficeList(sortedTestOffices); expect(actualOptions).toStrictEqual(expectedOptions); }); + + test('should label legacy offices in the options', () => { + const testOffices: CourtDivisionDetails[] = [ + { + courtDivisionCode: '002', + groupDesignator: 'AB', + courtId: '0102', + officeCode: '2', + officeName: 'B1', + state: 'NY', + courtName: 'B', + courtDivisionName: 'New York 1', + regionId: '02', + regionName: 'NEW YORK', + isLegacy: true, + }, + ]; + + const expectedOptions: Array> = [ + { value: '002', label: 'B (New York 1) Legacy' }, + ]; + + const actualOptions = getOfficeList(testOffices); + expect(actualOptions).toStrictEqual(expectedOptions); + }); + + test('should sort offices', () => { + const testOffices: CourtDivisionDetails[] = [ + { + courtDivisionCode: '001', + groupDesignator: 'AA', + courtId: '0101', + officeCode: '1', + officeName: 'A1', + state: 'NY', + courtName: 'A', + courtDivisionName: 'New York 1', + regionId: '02', + regionName: 'NEW YORK', + }, + { + courtDivisionCode: '003', + groupDesignator: 'AC', + courtId: '0103', + officeCode: '3', + officeName: 'C1', + state: 'NY', + courtName: 'C', + courtDivisionName: 'New York 1', + regionId: '02', + regionName: 'NEW YORK', + }, + { + courtDivisionCode: '003', + groupDesignator: 'AC', + courtId: '0103', + officeCode: '3', + officeName: 'C1', + state: 'NY', + courtName: 'C', + courtDivisionName: 'New York 1', + regionId: '02', + regionName: 'NEW YORK', + }, + { + courtDivisionCode: '002', + groupDesignator: 'AB', + courtId: '0102', + officeCode: '2', + officeName: 'B1', + state: 'NY', + courtName: 'B', + courtDivisionName: 'New York 1', + regionId: '02', + regionName: 'NEW YORK', + }, + ]; + const expectedOffices: CourtDivisionDetails[] = [ + { + courtDivisionCode: '001', + groupDesignator: 'AA', + courtId: '0101', + officeCode: '1', + officeName: 'A1', + state: 'NY', + courtName: 'A', + courtDivisionName: 'New York 1', + regionId: '02', + regionName: 'NEW YORK', + }, + { + courtDivisionCode: '002', + groupDesignator: 'AB', + courtId: '0102', + officeCode: '2', + officeName: 'B1', + state: 'NY', + courtName: 'B', + courtDivisionName: 'New York 1', + regionId: '02', + regionName: 'NEW YORK', + }, + { + courtDivisionCode: '003', + groupDesignator: 'AC', + courtId: '0103', + officeCode: '3', + officeName: 'C1', + state: 'NY', + courtName: 'C', + courtDivisionName: 'New York 1', + regionId: '02', + regionName: 'NEW YORK', + }, + { + courtDivisionCode: '003', + groupDesignator: 'AC', + courtId: '0103', + officeCode: '3', + officeName: 'C1', + state: 'NY', + courtName: 'C', + courtDivisionName: 'New York 1', + regionId: '02', + regionName: 'NEW YORK', + }, + ]; + const actualOffices = testOffices.sort(courtSorter); + expect(actualOffices).toEqual(expectedOffices); + }); }); diff --git a/user-interface/src/data-verification/dataVerificationHelper.ts b/user-interface/src/data-verification/dataVerificationHelper.ts index 75069d3967..c27c1e5ad6 100644 --- a/user-interface/src/data-verification/dataVerificationHelper.ts +++ b/user-interface/src/data-verification/dataVerificationHelper.ts @@ -2,10 +2,21 @@ import { CourtDivisionDetails } from '@common/cams/courts'; export function getOfficeList(officesList: CourtDivisionDetails[]) { const mapOutput = officesList.map((court) => { + let label = `${court.courtName} (${court.courtDivisionName})`; + if (court.isLegacy) { + label = label + ' Legacy'; + } return { value: court.courtDivisionCode, - label: `${court.courtName} (${court.courtDivisionName})`, + label, }; }); return mapOutput; } + +export function courtSorter(a: CourtDivisionDetails, b: CourtDivisionDetails) { + const aKey = a.courtName + '-' + a.courtDivisionName; + const bKey = b.courtName + '-' + b.courtDivisionName; + if (aKey === bKey) return 0; + return aKey > bKey ? 1 : -1; +} diff --git a/user-interface/src/search/SearchScreen.tsx b/user-interface/src/search/SearchScreen.tsx index 5635d7f966..898e3ed2b9 100644 --- a/user-interface/src/search/SearchScreen.tsx +++ b/user-interface/src/search/SearchScreen.tsx @@ -8,8 +8,7 @@ import { CourtDivisionDetails } from '@common/cams/courts'; import CaseNumberInput from '@/lib/components/CaseNumberInput'; import { useApi2 } from '@/lib/hooks/UseApi2'; import { ComboBoxRef, InputRef } from '@/lib/type-declarations/input-fields'; -import { getOfficeList } from '@/data-verification/dataVerificationHelper'; -import { courtSorter } from '@/data-verification/DataVerificationScreen'; +import { courtSorter, getOfficeList } from '@/data-verification/dataVerificationHelper'; import Alert, { UswdsAlertStyle } from '@/lib/components/uswds/Alert'; import './SearchScreen.scss'; import ComboBox, { ComboOption } from '@/lib/components/combobox/ComboBox'; From ddf83f0928934b3a4a8e1bdb9abbdbcf96dc2ea5 Mon Sep 17 00:00:00 2001 From: James Brooks <12275865+jamesobrooks@users.noreply.github.com> Date: Wed, 13 Nov 2024 10:35:58 -0600 Subject: [PATCH 14/15] Clean up test Jira ticket: CAMS-473 Co-authored-by: Brian Posey <15091170+btposey@users.noreply.github.com>, --- .../orders/orders.controller.test.ts | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/backend/functions/lib/controllers/orders/orders.controller.test.ts b/backend/functions/lib/controllers/orders/orders.controller.test.ts index dfb20501a5..875b54e4ca 100644 --- a/backend/functions/lib/controllers/orders/orders.controller.test.ts +++ b/backend/functions/lib/controllers/orders/orders.controller.test.ts @@ -121,7 +121,7 @@ describe('orders controller tests', () => { const failTransfer = { ...orderTransfer }; failTransfer.id = crypto.randomUUID().toString(); const controller = new OrdersController(applicationContext); - expect( + await expect( async () => await controller.updateOrder(applicationContext, id, failTransfer), ).rejects.toThrow(expectedError); }); @@ -161,23 +161,6 @@ describe('orders controller tests', () => { await expect(controller.getSuggestedCases(applicationContext)).rejects.toThrow(camsError); }); - test('should throw UnknownError if any other error is encountered', async () => { - const originalError = new Error('Test'); - const unknownError = new UnknownError('TEST', { originalError }); - jest.spyOn(OrdersUseCase.prototype, 'getOrders').mockRejectedValue(originalError); - jest.spyOn(OrdersUseCase.prototype, 'updateTransferOrder').mockRejectedValue(originalError); - jest.spyOn(OrdersUseCase.prototype, 'syncOrders').mockRejectedValue(originalError); - jest.spyOn(OrdersUseCase.prototype, 'getSuggestedCases').mockRejectedValue(originalError); - - const controller = new OrdersController(applicationContext); - await expect(controller.getOrders(applicationContext)).rejects.toThrow(unknownError); - await expect(controller.updateOrder(applicationContext, id, orderTransfer)).rejects.toThrow( - unknownError, - ); - await expect(controller.syncOrders(applicationContext)).rejects.toThrow(unknownError); - applicationContext.request = mockCamsHttpRequest({ params: { caseId: 'mockId' } }); - await expect(controller.getSuggestedCases(applicationContext)).rejects.toThrow(unknownError); - }); test('should throw UnknownError if any other error is encountered', async () => { const originalError = new Error('Test'); const unknownError = new UnknownError('TEST', { originalError }); @@ -290,7 +273,7 @@ describe('orders controller exception tests', () => { applicationContext.request = request; const controller = new OrdersController(applicationContext); - expect(async () => await controller.handleRequest(applicationContext)).rejects.toThrow( + await expect(async () => await controller.handleRequest(applicationContext)).rejects.toThrow( expectedError, ); }); From dbf1de08b97e0abc897adfc19bed9aa8d0f56f6a Mon Sep 17 00:00:00 2001 From: Brian Posey <15091170+btposey@users.noreply.github.com> Date: Wed, 13 Nov 2024 12:27:15 -0500 Subject: [PATCH 15/15] Add legacy division guard to transfer order actions Jira ticket: CAMS-473 Co-authored-by: Fritz Madden <96319835+fmaddenflx@users.noreply.github.com> Co-authored-by: James Brooks <12275865+jamesobrooks@users.noreply.github.com> Co-authored-by: Brian Posey <15091170+btposey@users.noreply.github.com>, --- .../controllers/orders/orders.controller.ts | 2 + .../orders-consolidation-approval.test.ts | 2 + .../orders/orders-local-gateway.test.ts | 5 ++ .../lib/use-cases/orders/orders.test.ts | 58 +++++++++++++++++++ .../functions/lib/use-cases/orders/orders.ts | 16 +++++ 5 files changed, 83 insertions(+) diff --git a/backend/functions/lib/controllers/orders/orders.controller.ts b/backend/functions/lib/controllers/orders/orders.controller.ts index 96a378cb95..6150fb767d 100644 --- a/backend/functions/lib/controllers/orders/orders.controller.ts +++ b/backend/functions/lib/controllers/orders/orders.controller.ts @@ -8,6 +8,7 @@ import { getOrdersGateway, getOrdersRepository, getRuntimeStateRepository, + getStorageGateway, } from '../../factory'; import { OrdersUseCase, SyncOrdersOptions, SyncOrdersStatus } from '../../use-cases/orders/orders'; import { @@ -46,6 +47,7 @@ export class OrdersController implements CamsController, CamsTimerController { getOrdersGateway(context), getRuntimeStateRepository(context), getConsolidationOrdersRepository(context), + getStorageGateway(context), ); } diff --git a/backend/functions/lib/use-cases/orders/orders-consolidation-approval.test.ts b/backend/functions/lib/use-cases/orders/orders-consolidation-approval.test.ts index d0a4873020..5807268ba2 100644 --- a/backend/functions/lib/use-cases/orders/orders-consolidation-approval.test.ts +++ b/backend/functions/lib/use-cases/orders/orders-consolidation-approval.test.ts @@ -10,6 +10,7 @@ import { getCasesRepository, getCasesGateway, getConsolidationOrdersRepository, + getStorageGateway, } from '../../factory'; import { ConsolidationOrderActionApproval, @@ -56,6 +57,7 @@ describe('Orders use case', () => { ordersGateway, runtimeStateRepo, consolidationRepo, + getStorageGateway(mockContext), ); }); diff --git a/backend/functions/lib/use-cases/orders/orders-local-gateway.test.ts b/backend/functions/lib/use-cases/orders/orders-local-gateway.test.ts index b8704500c2..36b819ce8e 100644 --- a/backend/functions/lib/use-cases/orders/orders-local-gateway.test.ts +++ b/backend/functions/lib/use-cases/orders/orders-local-gateway.test.ts @@ -13,6 +13,7 @@ import { getOrdersGateway, getOrdersRepository, getRuntimeStateRepository, + getStorageGateway, } from '../../factory'; import { CasesRepository, ConsolidationOrdersRepository } from '../gateways.types'; import { ApplicationContext } from '../../adapters/types/basic'; @@ -125,6 +126,8 @@ describe('orders use case tests', () => { let ordersRepo; let runtimeStateRepo; let casesGateway; + let storageGateway; + const authorizedUser = MockData.getCamsUser({ roles: [CamsRole.DataVerifier], offices: [REGION_02_GROUP_NY], @@ -137,6 +140,7 @@ describe('orders use case tests', () => { runtimeStateRepo = getRuntimeStateRepository(mockContext); ordersRepo = getOrdersRepository(mockContext); casesGateway = getCasesGateway(mockContext); + storageGateway = getStorageGateway(mockContext); }); test('should not create a second lead case for an existing consolidation', async () => { @@ -159,6 +163,7 @@ describe('orders use case tests', () => { ordersGateway, runtimeStateRepo, localConsolidationsRepo, + storageGateway, ); // attempt to set up a consolidation with a different lead case diff --git a/backend/functions/lib/use-cases/orders/orders.test.ts b/backend/functions/lib/use-cases/orders/orders.test.ts index 6841eb5c0c..de9e6b9a97 100644 --- a/backend/functions/lib/use-cases/orders/orders.test.ts +++ b/backend/functions/lib/use-cases/orders/orders.test.ts @@ -10,7 +10,9 @@ import { getCasesRepository, getCasesGateway, getConsolidationOrdersRepository, + getStorageGateway, } from '../../factory'; +import * as factory from '../../factory'; import { OrderSyncState } from '../gateways.types'; import { ConsolidationOrder, @@ -41,6 +43,7 @@ import { CaseAssignmentUseCase } from '../case-assignment'; import { REGION_02_GROUP_NY } from '../../../../../common/src/cams/test-utilities/mock-user'; import { getCourtDivisionCodes } from '../../../../../common/src/cams/users'; import { MockMongoRepository } from '../../testing/mock-gateways/mock-mongo.repository'; +import { UstpDivisionMeta } from '../../../../../common/src/cams/offices'; describe('Orders use case', () => { const CASE_ID = '000-11-22222'; @@ -51,6 +54,8 @@ describe('Orders use case', () => { let runtimeStateRepo; let casesGateway; let consolidationRepo; + let storageGateway; + let useCase: OrdersUseCase; const authorizedUser = MockData.getCamsUser({ roles: [CamsRole.DataVerifier], @@ -67,6 +72,7 @@ describe('Orders use case', () => { casesRepo = getCasesRepository(mockContext); casesGateway = getCasesGateway(mockContext); consolidationRepo = getConsolidationOrdersRepository(mockContext); + storageGateway = getStorageGateway(mockContext); useCase = new OrdersUseCase( casesRepo, casesGateway, @@ -74,6 +80,7 @@ describe('Orders use case', () => { ordersGateway, runtimeStateRepo, consolidationRepo, + storageGateway, ); }); @@ -639,4 +646,55 @@ describe('Orders use case', () => { expect.objectContaining({ updatedBy: getCamsUserReference(authorizedUser) }), ); }); + + test('should fail to update to a legacy office', async () => { + const courtDivisionCode = '000'; + jest.spyOn(factory, 'getStorageGateway').mockImplementation(() => { + return { + get: jest.fn(), + getRoleMapping: jest.fn(), + getUstpOffices: jest.fn(), + getUstpDivisionMeta: jest.fn().mockImplementation(() => { + return new Map([[courtDivisionCode, { isLegacy: true }]]); + }), + }; + }); + + const localUseCase = new OrdersUseCase( + casesRepo, + casesGateway, + ordersRepo, + ordersGateway, + runtimeStateRepo, + consolidationRepo, + factory.getStorageGateway(mockContext), + ); + + const newCase = MockData.getCaseSummary({ override: { courtDivisionCode } }); + const order: TransferOrder = MockData.getTransferOrder({ + override: { status: 'approved', newCase }, + }); + + const action: TransferOrderAction = { + id: order.id, + orderType: 'transfer', + caseId: order.caseId, + newCase: order.newCase, + status: 'approved', + }; + + const updateOrderFn = jest.spyOn(ordersRepo, 'update').mockResolvedValue({ id: 'mock-guid' }); + const getOrderFn = jest.spyOn(ordersRepo, 'read').mockResolvedValue(order); + const transferToFn = jest.spyOn(casesRepo, 'createTransferTo'); + const transferFromFn = jest.spyOn(casesRepo, 'createTransferFrom'); + const auditFn = jest.spyOn(casesRepo, 'createCaseHistory'); + mockContext.session = await createMockApplicationContextSession({ user: authorizedUser }); + + await expect(localUseCase.updateTransferOrder(mockContext, order.id, action)).rejects.toThrow(); + expect(updateOrderFn).not.toHaveBeenCalled(); + expect(getOrderFn).not.toHaveBeenCalled(); + expect(transferToFn).not.toHaveBeenCalled(); + expect(transferFromFn).not.toHaveBeenCalled(); + expect(auditFn).not.toHaveBeenCalled(); + }); }); diff --git a/backend/functions/lib/use-cases/orders/orders.ts b/backend/functions/lib/use-cases/orders/orders.ts index d07d429bf0..5cd3a92956 100644 --- a/backend/functions/lib/use-cases/orders/orders.ts +++ b/backend/functions/lib/use-cases/orders/orders.ts @@ -47,6 +47,7 @@ import { UnauthorizedError } from '../../common-errors/unauthorized-error'; import { createAuditRecord } from '../../../../../common/src/cams/auditable'; import { OrdersSearchPredicate } from '../../../../../common/src/api/search'; import { isNotFoundError } from '../../common-errors/not-found-error'; +import { StorageGateway } from '../../adapters/types/storage'; const MODULE_NAME = 'ORDERS_USE_CASE'; @@ -70,6 +71,7 @@ export class OrdersUseCase { private readonly ordersRepo: OrdersRepository; private readonly consolidationsRepo: ConsolidationOrdersRepository; private readonly runtimeStateRepo: RuntimeStateRepository; + private readonly storageGateway: StorageGateway; constructor( casesRepo: CasesRepository, @@ -78,6 +80,7 @@ export class OrdersUseCase { ordersGateway: OrdersGateway, runtimeRepo: RuntimeStateRepository, consolidationRepo: ConsolidationOrdersRepository, + storageGateway: StorageGateway, ) { this.casesRepo = casesRepo; this.casesGateway = casesGateway; @@ -85,6 +88,7 @@ export class OrdersUseCase { this.ordersGateway = ordersGateway; this.runtimeStateRepo = runtimeRepo; this.consolidationsRepo = consolidationRepo; + this.storageGateway = storageGateway; } public async getOrders(context: ApplicationContext): Promise> { @@ -114,6 +118,18 @@ export class OrdersUseCase { throw new UnauthorizedError(MODULE_NAME); } + const divisionMeta = this.storageGateway.getUstpDivisionMeta(); + const divisionCodeMaybe = data['newCase'] ? data['newCase'].courtDivisionCode : null; + if ( + divisionCodeMaybe && + divisionMeta.has(divisionCodeMaybe) && + divisionMeta.get(divisionCodeMaybe).isLegacy + ) { + throw new BadRequestError(MODULE_NAME, { + message: 'Cannot transfer to legacy division.', + }); + } + context.logger.info(MODULE_NAME, 'Updating transfer order:', data); const initialOrder = await this.ordersRepo.read(id, data.caseId); let order: Order;