Merged PR 2394: adjusting reps domain logic

Related work items: #23754
This commit is contained in:
Robert Bond
2026-06-17 10:55:32 +00:00
parent ca244e5de1
commit 0b66a0e1d4
4 changed files with 421 additions and 1 deletions
@@ -0,0 +1,145 @@
# Representation Type Policy Boundary
## Purpose
This directory contains a narrow **representation type policy boundary**.
It currently owns only the behaviour-preserving rule engine that converts an
already prepared policy context into the current ordered array of available
representation type strings.
It is **not** a full representation domain model.
This boundary should be treated as a small, compatibility-preserving extraction
seam for type-availability decisions, not as a place to redesign the
representation journey.
---
## Current ownership
### `getAvailableRepresentationTypes(context)`
Purpose:
```text
prepared context
ordered representation-type string array
```
The helper currently returns the live consumer contract as an ordered array of
strings, including values such as:
- `Questionnaire`
- `Statement`
- `Final comments`
- `Consultation Response`
- `Local Impact Report`
- `Marine Impact Report`
---
## Explicit contract
Current behaviour intentionally preserves:
- exact string values
- exact ordering
- deduplication behaviour
- current DNS behaviour
- current SIPS behaviour
- current Householder behaviour
- current Advert behaviour
- current CPO behaviour
- current specialist-process behaviour
- current LPA / non-LPA behaviour
- current NRW behaviour
This is a **consumer-facing compatibility contract**.
Changing any of the following would currently be a contract change, not just an
internal refactor:
- returned string values
- returned ordering
- returned deduplication semantics
---
## Compatibility requirements
The following date fields are compatibility-sensitive and must be preserved
carefully:
- `pinswg_startdate`
- `pinswg_startdates`
- `pinswg_statementduedate`
- `pinswg_statementsduedate`
- `pinswg_applicationacceptedasvalid`
These fallbacks are characterized behaviour and must not be casually removed.
---
## Explicit non-goals
This boundary does **not** currently own:
- capacity ownership
- capacity selection UX
- `buildRepresentationContext(...)`
- translation mapping
- Welsh labels
- file naming
- upload logic
- submission logic
- finalisation logic
- questionnaire routing
- Redux state
- API routes
- CRM queries
- dashboard behaviour
---
## Adapter boundary
`buildRepresentationContext(...)` intentionally remains outside this domain
boundary.
It should currently be treated as an **adapter layer**:
```text
CRM / UI state
policy context
```
It is not currently a domain helper.
---
## Future evolution
Any future migration from English string values to:
- stable IDs
- enums
- keys
would be a **contract change**, not merely refactoring.
That kind of migration must be explicitly scoped and characterized before any
implementation.
---
## Testing expectations
Current minimum safety net for this boundary:
```bash
node tests/phase22/representation-build-reps-arr-rules.test.cjs
npm run lint
```
@@ -0,0 +1,147 @@
const APPEAL_TYPES = {
SIPS: 846040002,
DNS: 846040011,
HOUSEHOLDER: 846040004,
ADVERT: 846040018,
EXCLUDE_QUESTIONNAIRE_1: 846040019,
EXCLUDE_QUESTIONNAIRE_2: 846040015,
EXCLUDE_QUESTIONNAIRE_3: 846040016
};
const SPECIALIST_PROCESS = {
WRITTEN_REPS: 846040000,
HEARING: 846040001
};
const REPRESENTATION_OPTIONS = {
QUESTIONNAIRE: "Questionnaire",
STATEMENT: "Statement",
FINAL_COMMENTS: "Final comments",
CONSULTATION_RESPONSE: "Consultation Response",
LOCAL_IMPACT_REPORT: "Local Impact Report",
MARINE_IMPACT_REPORT: "Marine Impact Report"
};
const isValidDate = (date) =>
date instanceof Date && !Number.isNaN(date.getTime());
const isWithinWindow = (now, start, end) => {
if (!isValidDate(now) || !isValidDate(start) || !isValidDate(end)) {
return false;
}
return now >= start && now <= end;
};
const addQuestionnaire = (options, ctx) => {
const excludedTypes = [
APPEAL_TYPES.EXCLUDE_QUESTIONNAIRE_1,
APPEAL_TYPES.DNS,
APPEAL_TYPES.EXCLUDE_QUESTIONNAIRE_2,
APPEAL_TYPES.EXCLUDE_QUESTIONNAIRE_3
];
const canAdd =
ctx.isLPA &&
!excludedTypes.includes(ctx.appealType) &&
isWithinWindow(ctx.now, ctx.startDate, ctx.finalCommentsDueDate);
if (canAdd) {
options.add(REPRESENTATION_OPTIONS.QUESTIONNAIRE);
}
};
const addStatements = (options, ctx) => {
const isLpaAdvertPart3NoStatement =
ctx.isLPA &&
ctx.appealType === APPEAL_TYPES.ADVERT &&
ctx.specialistProcess === SPECIALIST_PROCESS.WRITTEN_REPS;
const canAddDnsLpaDocs =
ctx.isLPA &&
ctx.isDNS &&
ctx.appealType !== APPEAL_TYPES.EXCLUDE_QUESTIONNAIRE_1 &&
!ctx.isCaseOwner &&
isWithinWindow(ctx.now, ctx.startDate, ctx.finalCommentsDueDate);
if (canAddDnsLpaDocs) {
options.add(REPRESENTATION_OPTIONS.LOCAL_IMPACT_REPORT);
options.add(REPRESENTATION_OPTIONS.STATEMENT);
}
const canAddStatementForNonDns =
!ctx.isDNS &&
ctx.appealType !== APPEAL_TYPES.HOUSEHOLDER &&
!isLpaAdvertPart3NoStatement &&
!ctx.isCaseOwner &&
isWithinWindow(ctx.now, ctx.startDate, ctx.statementDueDate);
if (canAddStatementForNonDns) {
options.add(REPRESENTATION_OPTIONS.STATEMENT);
}
const canAddStatementForDnsNonLpa =
ctx.isDNS &&
!ctx.isLPA &&
!ctx.isCaseOwner &&
isWithinWindow(ctx.now, ctx.startDate, ctx.statementDueDate);
if (canAddStatementForDnsNonLpa) {
options.add(REPRESENTATION_OPTIONS.STATEMENT);
}
};
const addFinalComments = (options, ctx) => {
const canSubmitFinalCommentsDefault =
ctx.isSelectedAppellant ||
ctx.isSelectedAgent ||
ctx.isSelectedInterestedParty ||
ctx.isLPA;
const canSubmitFinalComments = ctx.isDNS
? ctx.isLPA
: canSubmitFinalCommentsDefault;
const isSpecialistNonHearing =
ctx.specialistProcess !== SPECIALIST_PROCESS.HEARING;
const finalCommentsWindowStart = ctx.isDNS
? ctx.startDate
: ctx.statementDueDate;
const canAdd =
canSubmitFinalComments &&
isWithinWindow(
ctx.now,
finalCommentsWindowStart,
ctx.finalCommentsDueDate
) &&
(ctx.appealType !== APPEAL_TYPES.HOUSEHOLDER || isSpecialistNonHearing);
if (canAdd) {
options.add(REPRESENTATION_OPTIONS.FINAL_COMMENTS);
}
};
const addConsultation = (options, ctx) => {
if (!ctx.isSIPS) return;
options.add(REPRESENTATION_OPTIONS.CONSULTATION_RESPONSE);
if (!ctx.isSelectedAppellant) {
options.add(REPRESENTATION_OPTIONS.LOCAL_IMPACT_REPORT);
}
if (ctx.isNRW) {
options.add(REPRESENTATION_OPTIONS.MARINE_IMPACT_REPORT);
}
};
export const getAvailableRepresentationTypes = (ctx) => {
const options = new Set();
addQuestionnaire(options, ctx);
addStatements(options, ctx);
addFinalComments(options, ctx);
addConsultation(options, ctx);
return [...options];
};
@@ -0,0 +1 @@
export { getAvailableRepresentationTypes } from "./getAvailableRepresentationTypes";