From 0ef9be49c49709bfb2846d9ddd9c42f59e45db46 Mon Sep 17 00:00:00 2001 From: James Brooks <12275865+jamesobrooks@users.noreply.github.com> Date: Fri, 13 Sep 2024 18:18:26 -0500 Subject: [PATCH 1/2] Add an ADR about the Auditable type and helper fn Jira ticket: CAMS-282 --- .../decision-records/Auditable.md | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 docs/architecture/decision-records/Auditable.md diff --git a/docs/architecture/decision-records/Auditable.md b/docs/architecture/decision-records/Auditable.md new file mode 100644 index 000000000..731434b55 --- /dev/null +++ b/docs/architecture/decision-records/Auditable.md @@ -0,0 +1,43 @@ +# Auditable + +## Context + +A historical record needs to be kept related to certain data points in CAMS. This historical record involves tracking when a change was made, and by whom. We were previously managing this for orders and assignments in an ad hoc manner. To ensure consistency and to reduce boilerplate code, we need a way to provide this functionality through code common to all data types that require historical records. + +## Decision + +We have created the `Auditable` type which contains the common properties for history. We have also created the `createAuditRecord` function which provides default values for the properties and the ability to override if needed. Consider the example type—`Foo`—as follows: + +```typescript +type Foo = Auditable & { + prop1: string; + prop2: number; +} +``` + +To create a historical record for an action initiated by a user, call the `createAuditRecord` as follows: + +```typescript +createAuditRecord(someFoo, userSession); +``` + +To create a historical record for an action initiated by the system, call the `createAuditRecord` as follows: + +```typescript +createAuditRecord(someFoo); +``` + +To create a historical record with an override, call the `createAuditRecord` as follows: + +```typescript +const override = { updatedOn: someDate, updatedBy: someUser }; +createAuditRecord(someFoo, override); +``` + +## Status + +Approved + +## Consequences + +Developers need to remember to make use of this type and the function, but it should reduce the amount of boilerplate/duplicate code we have to write to track history. From 2e83763c8dc2fa9c806ebeaa24aebbdcf0f3a868 Mon Sep 17 00:00:00 2001 From: James Brooks <12275865+jamesobrooks@users.noreply.github.com> Date: Mon, 16 Sep 2024 09:48:34 -0500 Subject: [PATCH 2/2] Accept a single argument for createAuditRecord We want to be able to use zero, one, or both of two optional arguments. We modify the function signature to accept a single object containing the required `record` as well as the two situationally required `session` and `override` arguments. We also document the appropriate use with JSDocs. Jira ticket: CAMS-282 Co-authored-by: Arthur Morrow <133667008+amorrow-flexion@users.noreply.github.com> --- .../lib/use-cases/case-assignment.ts | 20 ++++++------ .../functions/lib/use-cases/orders/orders.ts | 32 +++++++++---------- common/src/cams/auditable.ts | 22 ++++++++----- .../decision-records/Auditable.md | 6 ++-- 4 files changed, 43 insertions(+), 37 deletions(-) diff --git a/backend/functions/lib/use-cases/case-assignment.ts b/backend/functions/lib/use-cases/case-assignment.ts index 09b6d7c66..b23250afd 100644 --- a/backend/functions/lib/use-cases/case-assignment.ts +++ b/backend/functions/lib/use-cases/case-assignment.ts @@ -70,8 +70,8 @@ export class CaseAssignmentUseCase { const attorneys = [...new Set(newAssignments)]; const currentDate = new Date().toISOString(); attorneys.forEach((attorney) => { - const assignment = createAuditRecord( - { + const assignment = createAuditRecord({ + record: { documentType: 'ASSIGNMENT', caseId: caseId, userId: attorney.id, @@ -79,9 +79,9 @@ export class CaseAssignmentUseCase { role: CamsRole[role], assignedOn: currentDate, }, - context.session, - { updatedOn: currentDate }, - ); + session: context.session, + override: { updatedOn: currentDate }, + }); listOfAssignments.push(assignment); }); const listOfAssignmentIdsCreated: string[] = []; @@ -115,16 +115,16 @@ export class CaseAssignmentUseCase { } const newAssignmentRecords = await this.assignmentRepository.findAssignmentsByCaseId(caseId); - const history = createAuditRecord( - { + const history = createAuditRecord({ + record: { caseId, documentType: 'AUDIT_ASSIGNMENT', before: existingAssignmentRecords, after: newAssignmentRecords, }, - context.session, - { updatedOn: currentDate }, - ); + session: context.session, + override: { updatedOn: currentDate }, + }); await this.casesRepository.createCaseHistory(context, history); context.logger.info( diff --git a/backend/functions/lib/use-cases/orders/orders.ts b/backend/functions/lib/use-cases/orders/orders.ts index 05dc9b201..b6769ac27 100644 --- a/backend/functions/lib/use-cases/orders/orders.ts +++ b/backend/functions/lib/use-cases/orders/orders.ts @@ -138,15 +138,15 @@ export class OrdersUseCase { await this.casesRepo.createTransferFrom(context, transferFrom); await this.casesRepo.createTransferTo(context, transferTo); } - const caseHistory = createAuditRecord( - { + const caseHistory = createAuditRecord({ + record: { caseId: order.caseId, documentType: 'AUDIT_TRANSFER', before: initialOrder as TransferOrder, after: order, }, - context.session, - ); + session: context.session, + }); await this.casesRepo.createCaseHistory(context, caseHistory); } } @@ -205,15 +205,15 @@ export class OrdersUseCase { for (const order of writtenTransfers) { if (isTransferOrder(order)) { - const caseHistory = createAuditRecord( - { + const caseHistory = createAuditRecord({ + record: { caseId: order.caseId, documentType: 'AUDIT_TRANSFER', before: null, after: order, }, - context.session, - ); + session: context.session, + }); await this.casesRepo.createCaseHistory(context, caseHistory); } } @@ -231,15 +231,15 @@ export class OrdersUseCase { status: 'pending', childCases: [], }; - const caseHistory = createAuditRecord( - { + const caseHistory = createAuditRecord({ + record: { caseId: order.caseId, documentType: 'AUDIT_CONSOLIDATION', before: null, after: history, }, - context.session, - ); + session: context.session, + }); await this.casesRepo.createCaseHistory(context, caseHistory); } @@ -307,15 +307,15 @@ export class OrdersUseCase { if (isConsolidationHistory(before) && before.childCases.length > 0) { after.childCases.push(...before.childCases); } - return createAuditRecord( - { + return createAuditRecord({ + record: { caseId: bCase.caseId, documentType: 'AUDIT_CONSOLIDATION', before: isConsolidationHistory(before) ? before : null, after, }, - context.session, - ); + session: context.session, + }); } private async handleConsolidation( diff --git a/common/src/cams/auditable.ts b/common/src/cams/auditable.ts index e2dfb056e..74003502f 100644 --- a/common/src/cams/auditable.ts +++ b/common/src/cams/auditable.ts @@ -8,15 +8,21 @@ export type Auditable = { export const SYSTEM_USER_REFERENCE: CamsUserReference = { id: 'SYSTEM', name: 'SYSTEM' }; -export function createAuditRecord( - record: Omit, - session?: CamsSession, - override?: Partial, -): T { +/** + * Decorate a record (T) with the updatedOn and updatedBy properties. + * @param args {record: T; session?: CamsSession; override?: Partial} `session` must be provided for a + * user-initiated action and not provided for a system-initiated action. `override` may be used to explicitly set + * either or both of the Auditable properties based on business logic for a particular use case. + */ +export function createAuditRecord(args: { + record: Omit; + session?: CamsSession; + override?: Partial; +}): T { return { updatedOn: new Date().toISOString(), - updatedBy: session ? getCamsUserReference(session.user) : SYSTEM_USER_REFERENCE, - ...record, - ...override, + updatedBy: args.session ? getCamsUserReference(args.session.user) : SYSTEM_USER_REFERENCE, + ...args.record, + ...args.override, } as T; } diff --git a/docs/architecture/decision-records/Auditable.md b/docs/architecture/decision-records/Auditable.md index 731434b55..13cb78c94 100644 --- a/docs/architecture/decision-records/Auditable.md +++ b/docs/architecture/decision-records/Auditable.md @@ -18,20 +18,20 @@ type Foo = Auditable & { To create a historical record for an action initiated by a user, call the `createAuditRecord` as follows: ```typescript -createAuditRecord(someFoo, userSession); +createAuditRecord({ record: someFoo, session: userSession }); ``` To create a historical record for an action initiated by the system, call the `createAuditRecord` as follows: ```typescript -createAuditRecord(someFoo); +createAuditRecord({ record: someFoo }); ``` To create a historical record with an override, call the `createAuditRecord` as follows: ```typescript const override = { updatedOn: someDate, updatedBy: someUser }; -createAuditRecord(someFoo, override); +createAuditRecord({ record: someFoo, override }); ``` ## Status