From 6d39430df2c5f6db9bad5142f9737ec7521a6037 Mon Sep 17 00:00:00 2001 From: David Wass Date: Tue, 7 Apr 2026 15:47:17 +0100 Subject: [PATCH 1/9] CCM-16444 Populate config table --- .../dispatch_internal_repo_workflow.sh | 13 ++++- .github/workflows/stage-3-build.yaml | 28 +++++++++ .../letter-variant/digitrials-aspiring.json | 38 ++++++++++++ .../letter-variant/digitrials-dmapp.json | 38 ++++++++++++ .../digitrials-globalminds.json | 38 ++++++++++++ .../letter-variant/digitrials-mymelanoma.json | 38 ++++++++++++ .../letter-variant/digitrials-ofh.json | 38 ++++++++++++ .../digitrials-prostateprogress.json | 38 ++++++++++++ .../letter-variant/digitrials-protectc.json | 38 ++++++++++++ .../letter-variant/digitrials-restore.json | 38 ++++++++++++ .../letter-variant/gpreg-admail.json | 35 +++++++++++ .../nces-abnormal-results-braille.json | 31 ++++++++++ .../letter-variant/nces-abnormal-results.json | 31 ++++++++++ .../letter-variant/nces-invites-braille.json | 31 ++++++++++ .../letter-variant/nces-invites.json | 31 ++++++++++ .../letter-variant/nces-standard-braille.json | 31 ++++++++++ .../letter-variant/nces-standard.json | 31 ++++++++++ .../letter-variant/notify-audio.json | 30 ++++++++++ .../letter-variant/notify-braille.json | 30 ++++++++++ .../notify-digital-letters-standard.json | 31 ++++++++++ .../letter-variant/notify-first.json | 30 ++++++++++ .../notify-standard-colour.json | 34 +++++++++++ .../letter-variant/notify-standard.json | 30 ++++++++++ .../digitrials-aspiring.json | 58 +++++++++++++++++++ .../digitrials-globalminds.json | 56 ++++++++++++++++++ .../digitrials-mymelanoma.json | 55 ++++++++++++++++++ .../pack-specification/digitrials-ofh.json | 55 ++++++++++++++++++ .../digitrials-prostateprogress.json | 55 ++++++++++++++++++ .../digitrials-restore.json | 58 +++++++++++++++++++ .../nces-abnormal-results-braille.json | 53 +++++++++++++++++ .../nces-abnormal-results.json | 53 +++++++++++++++++ .../nces-invites-braille.json | 51 ++++++++++++++++ .../pack-specification/nces-invites.json | 50 ++++++++++++++++ .../notify-admail-whitemail.json | 51 ++++++++++++++++ .../pack-specification/notify-admail.json | 54 +++++++++++++++++ .../pack-specification/notify-audio.json | 48 +++++++++++++++ .../notify-braille-whitemail.json | 49 ++++++++++++++++ .../pack-specification/notify-braille.json | 48 +++++++++++++++ .../pack-specification/notify-c4.json | 48 +++++++++++++++ .../pack-specification/notify-c5-colour.json | 52 +++++++++++++++++ .../notify-c5-whitemail.json | 48 +++++++++++++++ .../pack-specification/notify-c5.json | 48 +++++++++++++++ .../pack-specification/notify-first.json | 48 +++++++++++++++ .../pack-specification/notify-sameday.json | 51 ++++++++++++++++ .../datagraphic-datagraphic.json | 7 +++ .../synertec-synertec.json | 7 +++ .../datagraphic-digitrials-aspiring.json | 7 +++ .../datagraphic-digitrials-globalminds.json | 7 +++ .../datagraphic-digitrials-mymelanoma.json | 7 +++ .../datagraphic-digitrials-ofh.json | 7 +++ ...tagraphic-digitrials-prostateprogress.json | 7 +++ .../datagraphic-digitrials-restore.json | 7 +++ .../datagraphic-notify-admail.json | 7 +++ .../datagraphic-notify-audio.json | 7 +++ .../datagraphic-notify-braille-whitemail.json | 7 +++ .../datagraphic-notify-braille.json | 7 +++ .../supplier-pack/datagraphic-notify-c4.json | 7 +++ .../datagraphic-notify-c5-colour.json | 7 +++ .../datagraphic-notify-c5-whitemail.json | 7 +++ .../supplier-pack/datagraphic-notify-c5.json | 7 +++ .../datagraphic-notify-first.json | 7 +++ .../datagraphic-notify-sameday.json | 7 +++ .../supplier-pack/mba-notify-audio.json | 7 +++ .../mba-notify-braille-whitemail.json | 7 +++ .../supplier-pack/mba-notify-braille.json | 7 +++ .../supplier-pack/mba-notify-c4.json | 7 +++ .../mba-notify-c5-whitemail.json | 7 +++ .../supplier-pack/mba-notify-c5.json | 7 +++ .../supplier-pack/mba-notify-first.json | 7 +++ .../supplier-pack/mba-notify-sameday.json | 7 +++ ...ynertec-nces-abnormal-results-braille.json | 7 +++ .../synertec-nces-abnormal-results.json | 7 +++ .../synertec-nces-invites-braille.json | 7 +++ .../supplier-pack/synertec-nces-invites.json | 7 +++ .../synertec-notify-admail-whitemail.json | 7 +++ .../supplier-pack/synertec-notify-admail.json | 7 +++ .../supplier-pack/synertec-notify-audio.json | 7 +++ .../synertec-notify-braille-whitemail.json | 7 +++ .../synertec-notify-braille.json | 7 +++ .../supplier-pack/synertec-notify-c4.json | 7 +++ .../synertec-notify-c5-colour.json | 7 +++ .../synertec-notify-c5-whitemail.json | 7 +++ .../supplier-pack/synertec-notify-c5.json | 7 +++ .../supplier-pack/synertec-notify-first.json | 7 +++ .../synertec-notify-sameday.json | 7 +++ config/suppliers/supplier/datagraphic.json | 7 +++ config/suppliers/supplier/mba.json | 7 +++ config/suppliers/supplier/supplier1.json | 7 +++ config/suppliers/supplier/supplier2.json | 7 +++ config/suppliers/supplier/synertec.json | 7 +++ config/suppliers/volume-group/accessible.json | 8 +++ config/suppliers/volume-group/bau.json | 8 +++ .../suppliers/volume-group/datagraphic.json | 7 +++ config/suppliers/volume-group/sameday.json | 8 +++ config/suppliers/volume-group/synertec.json | 7 +++ .../api/ddb_table_supplier_configuration.tf | 12 ++-- 96 files changed, 2205 insertions(+), 7 deletions(-) create mode 100644 config/suppliers/letter-variant/digitrials-aspiring.json create mode 100644 config/suppliers/letter-variant/digitrials-dmapp.json create mode 100644 config/suppliers/letter-variant/digitrials-globalminds.json create mode 100644 config/suppliers/letter-variant/digitrials-mymelanoma.json create mode 100644 config/suppliers/letter-variant/digitrials-ofh.json create mode 100644 config/suppliers/letter-variant/digitrials-prostateprogress.json create mode 100644 config/suppliers/letter-variant/digitrials-protectc.json create mode 100644 config/suppliers/letter-variant/digitrials-restore.json create mode 100644 config/suppliers/letter-variant/gpreg-admail.json create mode 100644 config/suppliers/letter-variant/nces-abnormal-results-braille.json create mode 100644 config/suppliers/letter-variant/nces-abnormal-results.json create mode 100644 config/suppliers/letter-variant/nces-invites-braille.json create mode 100644 config/suppliers/letter-variant/nces-invites.json create mode 100644 config/suppliers/letter-variant/nces-standard-braille.json create mode 100644 config/suppliers/letter-variant/nces-standard.json create mode 100644 config/suppliers/letter-variant/notify-audio.json create mode 100644 config/suppliers/letter-variant/notify-braille.json create mode 100644 config/suppliers/letter-variant/notify-digital-letters-standard.json create mode 100644 config/suppliers/letter-variant/notify-first.json create mode 100644 config/suppliers/letter-variant/notify-standard-colour.json create mode 100644 config/suppliers/letter-variant/notify-standard.json create mode 100644 config/suppliers/pack-specification/digitrials-aspiring.json create mode 100644 config/suppliers/pack-specification/digitrials-globalminds.json create mode 100644 config/suppliers/pack-specification/digitrials-mymelanoma.json create mode 100644 config/suppliers/pack-specification/digitrials-ofh.json create mode 100644 config/suppliers/pack-specification/digitrials-prostateprogress.json create mode 100644 config/suppliers/pack-specification/digitrials-restore.json create mode 100644 config/suppliers/pack-specification/nces-abnormal-results-braille.json create mode 100644 config/suppliers/pack-specification/nces-abnormal-results.json create mode 100644 config/suppliers/pack-specification/nces-invites-braille.json create mode 100644 config/suppliers/pack-specification/nces-invites.json create mode 100644 config/suppliers/pack-specification/notify-admail-whitemail.json create mode 100644 config/suppliers/pack-specification/notify-admail.json create mode 100644 config/suppliers/pack-specification/notify-audio.json create mode 100644 config/suppliers/pack-specification/notify-braille-whitemail.json create mode 100644 config/suppliers/pack-specification/notify-braille.json create mode 100644 config/suppliers/pack-specification/notify-c4.json create mode 100644 config/suppliers/pack-specification/notify-c5-colour.json create mode 100644 config/suppliers/pack-specification/notify-c5-whitemail.json create mode 100644 config/suppliers/pack-specification/notify-c5.json create mode 100644 config/suppliers/pack-specification/notify-first.json create mode 100644 config/suppliers/pack-specification/notify-sameday.json create mode 100644 config/suppliers/supplier-allocation/datagraphic-datagraphic.json create mode 100644 config/suppliers/supplier-allocation/synertec-synertec.json create mode 100644 config/suppliers/supplier-pack/datagraphic-digitrials-aspiring.json create mode 100644 config/suppliers/supplier-pack/datagraphic-digitrials-globalminds.json create mode 100644 config/suppliers/supplier-pack/datagraphic-digitrials-mymelanoma.json create mode 100644 config/suppliers/supplier-pack/datagraphic-digitrials-ofh.json create mode 100644 config/suppliers/supplier-pack/datagraphic-digitrials-prostateprogress.json create mode 100644 config/suppliers/supplier-pack/datagraphic-digitrials-restore.json create mode 100644 config/suppliers/supplier-pack/datagraphic-notify-admail.json create mode 100644 config/suppliers/supplier-pack/datagraphic-notify-audio.json create mode 100644 config/suppliers/supplier-pack/datagraphic-notify-braille-whitemail.json create mode 100644 config/suppliers/supplier-pack/datagraphic-notify-braille.json create mode 100644 config/suppliers/supplier-pack/datagraphic-notify-c4.json create mode 100644 config/suppliers/supplier-pack/datagraphic-notify-c5-colour.json create mode 100644 config/suppliers/supplier-pack/datagraphic-notify-c5-whitemail.json create mode 100644 config/suppliers/supplier-pack/datagraphic-notify-c5.json create mode 100644 config/suppliers/supplier-pack/datagraphic-notify-first.json create mode 100644 config/suppliers/supplier-pack/datagraphic-notify-sameday.json create mode 100644 config/suppliers/supplier-pack/mba-notify-audio.json create mode 100644 config/suppliers/supplier-pack/mba-notify-braille-whitemail.json create mode 100644 config/suppliers/supplier-pack/mba-notify-braille.json create mode 100644 config/suppliers/supplier-pack/mba-notify-c4.json create mode 100644 config/suppliers/supplier-pack/mba-notify-c5-whitemail.json create mode 100644 config/suppliers/supplier-pack/mba-notify-c5.json create mode 100644 config/suppliers/supplier-pack/mba-notify-first.json create mode 100644 config/suppliers/supplier-pack/mba-notify-sameday.json create mode 100644 config/suppliers/supplier-pack/synertec-nces-abnormal-results-braille.json create mode 100644 config/suppliers/supplier-pack/synertec-nces-abnormal-results.json create mode 100644 config/suppliers/supplier-pack/synertec-nces-invites-braille.json create mode 100644 config/suppliers/supplier-pack/synertec-nces-invites.json create mode 100644 config/suppliers/supplier-pack/synertec-notify-admail-whitemail.json create mode 100644 config/suppliers/supplier-pack/synertec-notify-admail.json create mode 100644 config/suppliers/supplier-pack/synertec-notify-audio.json create mode 100644 config/suppliers/supplier-pack/synertec-notify-braille-whitemail.json create mode 100644 config/suppliers/supplier-pack/synertec-notify-braille.json create mode 100644 config/suppliers/supplier-pack/synertec-notify-c4.json create mode 100644 config/suppliers/supplier-pack/synertec-notify-c5-colour.json create mode 100644 config/suppliers/supplier-pack/synertec-notify-c5-whitemail.json create mode 100644 config/suppliers/supplier-pack/synertec-notify-c5.json create mode 100644 config/suppliers/supplier-pack/synertec-notify-first.json create mode 100644 config/suppliers/supplier-pack/synertec-notify-sameday.json create mode 100644 config/suppliers/supplier/datagraphic.json create mode 100644 config/suppliers/supplier/mba.json create mode 100644 config/suppliers/supplier/supplier1.json create mode 100644 config/suppliers/supplier/supplier2.json create mode 100644 config/suppliers/supplier/synertec.json create mode 100644 config/suppliers/volume-group/accessible.json create mode 100644 config/suppliers/volume-group/bau.json create mode 100644 config/suppliers/volume-group/datagraphic.json create mode 100644 config/suppliers/volume-group/sameday.json create mode 100644 config/suppliers/volume-group/synertec.json diff --git a/.github/scripts/dispatch_internal_repo_workflow.sh b/.github/scripts/dispatch_internal_repo_workflow.sh index 714a3fed5..599292976 100755 --- a/.github/scripts/dispatch_internal_repo_workflow.sh +++ b/.github/scripts/dispatch_internal_repo_workflow.sh @@ -104,6 +104,10 @@ while [[ $# -gt 0 ]]; do version="$2" shift 2 ;; + --tableName) # Table name (optional) + tableName="$2" + shift 2 + ;; *) echo "[ERROR] Unknown argument: $1" exit 1 @@ -202,6 +206,10 @@ if [[ -z "$version" ]]; then version="" fi +if [{ -z "$tableName" }]; then + tableName="" +fi + echo "==================== Workflow Dispatch Parameters ====================" echo " infraRepoName: $infraRepoName" echo " releaseVersion: $releaseVersion" @@ -221,6 +229,7 @@ echo " apimEnvironment: $apimEnvironment" echo " boundedContext: $boundedContext" echo " targetDomain: $targetDomain" echo " version: $version" +echo " tableName: $tableName" DISPATCH_EVENT=$(jq -ncM \ --arg infraRepoName "$infraRepoName" \ @@ -240,6 +249,7 @@ DISPATCH_EVENT=$(jq -ncM \ --arg boundedContext "$boundedContext" \ --arg targetDomain "$targetDomain" \ --arg version "$version" \ + --arg tableName "$tableName" \ '{ "ref": "'"$internalRef"'", "inputs": ( @@ -255,6 +265,7 @@ DISPATCH_EVENT=$(jq -ncM \ (if $boundedContext != "" then { "boundedContext": $boundedContext } else {} end) + (if $targetDomain != "" then { "targetDomain": $targetDomain } else {} end) + (if $version != "" then { "version": $version } else {} end) + + (if $tableName != "" then { "tableName": $tableName } else {} end) + (if $targetAccountGroup != "" then { "targetAccountGroup": $targetAccountGroup } else {} end) + { "releaseVersion": $releaseVersion, @@ -269,7 +280,7 @@ echo "[INFO] Triggering workflow '$targetWorkflow' in nhs-notify-internal..." echo "[DEBUG] Dispatch event payload: $DISPATCH_EVENT" trigger_response=$(curl -s -L \ - --fail \ + --fail-with-body \ -X POST \ -H "Accept: application/vnd.github+json" \ -H "Authorization: Bearer ${PR_TRIGGER_PAT}" \ diff --git a/.github/workflows/stage-3-build.yaml b/.github/workflows/stage-3-build.yaml index 3d476c2d6..516bc9473 100644 --- a/.github/workflows/stage-3-build.yaml +++ b/.github/workflows/stage-3-build.yaml @@ -184,3 +184,31 @@ jobs: runId: "${{ github.run_id }}" buildSandbox: true releaseVersion: ${{ github.head_ref || github.ref_name }} + populate-config: + name: "Populate Supplier Config" + runs-on: ubuntu-latest + needs: [pr-create-dynamic-environment] + timeout-minutes: 10 + + steps: + - name: "Checkout code" + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + - name: Set Environment Name + id: set-environment + run: echo "environment_name=${{ inputs.pr_number != '' && format('pr{0}', inputs.pr_number) || 'main' }}" >> $GITHUB_OUTPUT + - name: "Trigger populate config workflow in internal repo" + env: + APP_CLIENT_ID: ${{ secrets.APP_CLIENT_ID }} + APP_PEM_FILE: ${{ secrets.APP_PEM_FILE }} + PR_NUMBER: ${{ inputs.pr_number }} + shell: bash + run: | + .github/scripts/dispatch_internal_repo_workflow.sh \ + --infraRepoName "$(echo ${{ github.repository }} | cut -d'/' -f2)" \ + --releaseVersion ${{ github.head_ref || github.ref_name }} \ + --targetWorkflow "publish-supplier-config.yaml" \ + --targetEnvironment "${{ steps.set-environment.outputs.environment_name }}" \ + --targetComponent "config" \ + --targetAccountGroup "nhs-notify-suppliers-dev" \ + --tableName "supplier-config" \ + --internalRef "feature/CCM-12444-supplier-config-publishing-workflow" diff --git a/config/suppliers/letter-variant/digitrials-aspiring.json b/config/suppliers/letter-variant/digitrials-aspiring.json new file mode 100644 index 000000000..35a395c46 --- /dev/null +++ b/config/suppliers/letter-variant/digitrials-aspiring.json @@ -0,0 +1,38 @@ +{ + "campaignIds": [ + "digitrials-aspiring" + ], + "clientId": "digitrials", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "colourCoveragePercentage": { + "operator": "LESS_THAN", + "value": 10 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 3 + }, + "sides": { + "operator": "LESS_THAN", + "value": 6 + } + }, + "description": "Colour printing, ASPIRING envelope, Attachment", + "id": "digitrials-aspiring", + "name": "Digitrials - ASPIRING", + "packSpecificationIds": [ + "digitrials-aspiring" + ], + "status": "INT", + "supplierId": "datagraphic", + "type": "STANDARD", + "volumeGroupId": "datagraphic" +} diff --git a/config/suppliers/letter-variant/digitrials-dmapp.json b/config/suppliers/letter-variant/digitrials-dmapp.json new file mode 100644 index 000000000..aa7ba5188 --- /dev/null +++ b/config/suppliers/letter-variant/digitrials-dmapp.json @@ -0,0 +1,38 @@ +{ + "campaignIds": [ + "digitrials-dmapp" + ], + "clientId": "digitrials", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "colourCoveragePercentage": { + "operator": "LESS_THAN", + "value": 10 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "description": "Admail, colour printing", + "id": "digitrials-dmapp", + "name": "Digitrials - DMAPP", + "packSpecificationIds": [ + "notify-admail" + ], + "status": "INT", + "supplierId": "datagraphic", + "type": "STANDARD", + "volumeGroupId": "datagraphic" +} diff --git a/config/suppliers/letter-variant/digitrials-globalminds.json b/config/suppliers/letter-variant/digitrials-globalminds.json new file mode 100644 index 000000000..717c4d0a3 --- /dev/null +++ b/config/suppliers/letter-variant/digitrials-globalminds.json @@ -0,0 +1,38 @@ +{ + "campaignIds": [ + "digitrials-globalminds" + ], + "clientId": "digitrials", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "colourCoveragePercentage": { + "operator": "LESS_THAN", + "value": 10 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "description": "Admail?, colour printing, booklet", + "id": "digitrials-globalminds", + "name": "Digitrials - Global Minds", + "packSpecificationIds": [ + "digitrials-globalminds" + ], + "status": "INT", + "supplierId": "datagraphic", + "type": "STANDARD", + "volumeGroupId": "datagraphic" +} diff --git a/config/suppliers/letter-variant/digitrials-mymelanoma.json b/config/suppliers/letter-variant/digitrials-mymelanoma.json new file mode 100644 index 000000000..75414128a --- /dev/null +++ b/config/suppliers/letter-variant/digitrials-mymelanoma.json @@ -0,0 +1,38 @@ +{ + "campaignIds": [ + "digitrials-mymelanoma" + ], + "clientId": "digitrials", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "colourCoveragePercentage": { + "operator": "LESS_THAN", + "value": 10 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "description": "Admail, colour printing, MyMelanoma envelope", + "id": "digitrials-mymelanoma", + "name": "Digitrials - MyMelanoma", + "packSpecificationIds": [ + "digitrials-mymelanoma" + ], + "status": "INT", + "supplierId": "datagraphic", + "type": "STANDARD", + "volumeGroupId": "datagraphic" +} diff --git a/config/suppliers/letter-variant/digitrials-ofh.json b/config/suppliers/letter-variant/digitrials-ofh.json new file mode 100644 index 000000000..7fd4b6f58 --- /dev/null +++ b/config/suppliers/letter-variant/digitrials-ofh.json @@ -0,0 +1,38 @@ +{ + "campaignIds": [ + "digitrials-ofh" + ], + "clientId": "digitrials", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "colourCoveragePercentage": { + "operator": "LESS_THAN", + "value": 10 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "description": "Admail, colour printing, OFH envelope", + "id": "digitrials-ofh", + "name": "Digitrials - Our Future Health", + "packSpecificationIds": [ + "digitrials-ofh" + ], + "status": "PROD", + "supplierId": "datagraphic", + "type": "STANDARD", + "volumeGroupId": "datagraphic" +} diff --git a/config/suppliers/letter-variant/digitrials-prostateprogress.json b/config/suppliers/letter-variant/digitrials-prostateprogress.json new file mode 100644 index 000000000..712599250 --- /dev/null +++ b/config/suppliers/letter-variant/digitrials-prostateprogress.json @@ -0,0 +1,38 @@ +{ + "campaignIds": [ + "digitrials-prostateprogress" + ], + "clientId": "digitrials", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "colourCoveragePercentage": { + "operator": "LESS_THAN", + "value": 10 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 4 + }, + "sides": { + "operator": "LESS_THAN", + "value": 8 + } + }, + "description": "Colour printing, PP envelope, Attachment", + "id": "digitrials-prostateprogress", + "name": "Digitrials - Prostate Progress", + "packSpecificationIds": [ + "digitrials-prostateprogress" + ], + "status": "INT", + "supplierId": "datagraphic", + "type": "STANDARD", + "volumeGroupId": "datagraphic" +} diff --git a/config/suppliers/letter-variant/digitrials-protectc.json b/config/suppliers/letter-variant/digitrials-protectc.json new file mode 100644 index 000000000..156ff14b3 --- /dev/null +++ b/config/suppliers/letter-variant/digitrials-protectc.json @@ -0,0 +1,38 @@ +{ + "campaignIds": [ + "digitrials-protectc" + ], + "clientId": "digitrials", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "colourCoveragePercentage": { + "operator": "LESS_THAN", + "value": 10 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "description": "Economy, colour printing", + "id": "digitrials-protectc", + "name": "Digitrials - PROTECT-C", + "packSpecificationIds": [ + "notify-c5-colour" + ], + "status": "INT", + "supplierId": "datagraphic", + "type": "STANDARD", + "volumeGroupId": "datagraphic" +} diff --git a/config/suppliers/letter-variant/digitrials-restore.json b/config/suppliers/letter-variant/digitrials-restore.json new file mode 100644 index 000000000..de6be6f3a --- /dev/null +++ b/config/suppliers/letter-variant/digitrials-restore.json @@ -0,0 +1,38 @@ +{ + "campaignIds": [ + "digitrials-restore" + ], + "clientId": "digitrials", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "colourCoveragePercentage": { + "operator": "LESS_THAN", + "value": 10 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "description": "Admail?, colour printing, booklet", + "id": "digitrials-restore", + "name": "Digitrials - Restore", + "packSpecificationIds": [ + "digitrials-restore" + ], + "status": "INT", + "supplierId": "datagraphic", + "type": "STANDARD", + "volumeGroupId": "datagraphic" +} diff --git a/config/suppliers/letter-variant/gpreg-admail.json b/config/suppliers/letter-variant/gpreg-admail.json new file mode 100644 index 000000000..8679e96e9 --- /dev/null +++ b/config/suppliers/letter-variant/gpreg-admail.json @@ -0,0 +1,35 @@ +{ + "clientId": "gp-reg", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "colourCoveragePercentage": { + "operator": "LESS_THAN", + "value": 10 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "description": "Letter with admail postage tariff", + "id": "gpreg-admail", + "name": "Admail letter", + "packSpecificationIds": [ + "notify-admail" + ], + "status": "INT", + "supplierId": "synertec", + "type": "STANDARD", + "volumeGroupId": "synertec" +} diff --git a/config/suppliers/letter-variant/nces-abnormal-results-braille.json b/config/suppliers/letter-variant/nces-abnormal-results-braille.json new file mode 100644 index 000000000..0a42c9415 --- /dev/null +++ b/config/suppliers/letter-variant/nces-abnormal-results-braille.json @@ -0,0 +1,31 @@ +{ + "clientId": "nces", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 1 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 4 + }, + "sides": { + "operator": "LESS_THAN", + "value": 8 + } + }, + "description": "Same Day, Braille, Whitemail, Booklet", + "id": "nces-abnormal-results-braille", + "name": "Cervical Screening Braille Abnormal Results", + "packSpecificationIds": [ + "nces-abnormal-results-braille" + ], + "status": "INT", + "supplierId": "synertec", + "type": "BRAILLE", + "volumeGroupId": "accessible" +} diff --git a/config/suppliers/letter-variant/nces-abnormal-results.json b/config/suppliers/letter-variant/nces-abnormal-results.json new file mode 100644 index 000000000..0fd4b659f --- /dev/null +++ b/config/suppliers/letter-variant/nces-abnormal-results.json @@ -0,0 +1,31 @@ +{ + "clientId": "nces", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 1 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 4 + }, + "sides": { + "operator": "LESS_THAN", + "value": 8 + } + }, + "description": "Same Day, Whitemail, Booklet", + "id": "nces-abnormal-results", + "name": "Cervical Screening Abnormal Results", + "packSpecificationIds": [ + "nces-abnormal-results" + ], + "status": "INT", + "supplierId": "synertec", + "type": "STANDARD", + "volumeGroupId": "sameday" +} diff --git a/config/suppliers/letter-variant/nces-invites-braille.json b/config/suppliers/letter-variant/nces-invites-braille.json new file mode 100644 index 000000000..3d98c879a --- /dev/null +++ b/config/suppliers/letter-variant/nces-invites-braille.json @@ -0,0 +1,31 @@ +{ + "clientId": "nces", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 4 + }, + "sides": { + "operator": "LESS_THAN", + "value": 8 + } + }, + "description": "Braille, Whitemail, Booklet", + "id": "nces-invites-braille", + "name": "Cervical Screening Braille Invites", + "packSpecificationIds": [ + "nces-invites-braille" + ], + "status": "INT", + "supplierId": "synertec", + "type": "BRAILLE", + "volumeGroupId": "accessible" +} diff --git a/config/suppliers/letter-variant/nces-invites.json b/config/suppliers/letter-variant/nces-invites.json new file mode 100644 index 000000000..95dcbd804 --- /dev/null +++ b/config/suppliers/letter-variant/nces-invites.json @@ -0,0 +1,31 @@ +{ + "clientId": "nces", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 4 + }, + "sides": { + "operator": "LESS_THAN", + "value": 8 + } + }, + "description": "Business Economy, Whitemail, Booklet", + "id": "nces-invites", + "name": "Cervical Screening Invites", + "packSpecificationIds": [ + "nces-invites" + ], + "status": "INT", + "supplierId": "synertec", + "type": "STANDARD", + "volumeGroupId": "bau" +} diff --git a/config/suppliers/letter-variant/nces-standard-braille.json b/config/suppliers/letter-variant/nces-standard-braille.json new file mode 100644 index 000000000..191eebbe1 --- /dev/null +++ b/config/suppliers/letter-variant/nces-standard-braille.json @@ -0,0 +1,31 @@ +{ + "clientId": "nces", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "description": "Braille, Whitemail", + "id": "nces-standard-braille", + "name": "Cervical Screening Standard Braille Letters", + "packSpecificationIds": [ + "notify-braille-whitemail" + ], + "status": "INT", + "supplierId": "synertec", + "type": "BRAILLE", + "volumeGroupId": "accessible" +} diff --git a/config/suppliers/letter-variant/nces-standard.json b/config/suppliers/letter-variant/nces-standard.json new file mode 100644 index 000000000..d512df617 --- /dev/null +++ b/config/suppliers/letter-variant/nces-standard.json @@ -0,0 +1,31 @@ +{ + "clientId": "nces", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "description": "Business Economy, Whitemail", + "id": "nces-standard", + "name": "Cervical Screening Standard Letters", + "packSpecificationIds": [ + "notify-c5-whitemail" + ], + "status": "PROD", + "supplierId": "synertec", + "type": "STANDARD", + "volumeGroupId": "synertec" +} diff --git a/config/suppliers/letter-variant/notify-audio.json b/config/suppliers/letter-variant/notify-audio.json new file mode 100644 index 000000000..bb80b3e4b --- /dev/null +++ b/config/suppliers/letter-variant/notify-audio.json @@ -0,0 +1,30 @@ +{ + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "description": "Audio CD with standard letter", + "id": "notify-audio", + "name": "Audio CD Letter", + "packSpecificationIds": [ + "notify-audio" + ], + "status": "DRAFT", + "supplierId": "synertec", + "type": "AUDIO", + "volumeGroupId": "accessible" +} diff --git a/config/suppliers/letter-variant/notify-braille.json b/config/suppliers/letter-variant/notify-braille.json new file mode 100644 index 000000000..90fd88153 --- /dev/null +++ b/config/suppliers/letter-variant/notify-braille.json @@ -0,0 +1,30 @@ +{ + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "description": "Braille letter with standard letter", + "id": "notify-braille", + "name": "Braille Letter", + "packSpecificationIds": [ + "notify-braille" + ], + "status": "INT", + "supplierId": "synertec", + "type": "BRAILLE", + "volumeGroupId": "accessible" +} diff --git a/config/suppliers/letter-variant/notify-digital-letters-standard.json b/config/suppliers/letter-variant/notify-digital-letters-standard.json new file mode 100644 index 000000000..1cb96e32c --- /dev/null +++ b/config/suppliers/letter-variant/notify-digital-letters-standard.json @@ -0,0 +1,31 @@ +{ + "clientId": "digital-letters", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "description": "Black printing, economy postage tariff", + "id": "notify-digital-letters-standard", + "name": "Standard Letter Variant for Digital Letters fallback", + "packSpecificationIds": [ + "notify-c5" + ], + "status": "INT", + "supplierId": "synertec", + "type": "STANDARD", + "volumeGroupId": "synertec" +} diff --git a/config/suppliers/letter-variant/notify-first.json b/config/suppliers/letter-variant/notify-first.json new file mode 100644 index 000000000..a3741e885 --- /dev/null +++ b/config/suppliers/letter-variant/notify-first.json @@ -0,0 +1,30 @@ +{ + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 2 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "description": "Black printing, first class postage tariff", + "id": "notify-first", + "name": "First class letter", + "packSpecificationIds": [ + "notify-first" + ], + "status": "DRAFT", + "supplierId": "synertec", + "type": "STANDARD", + "volumeGroupId": "bau" +} diff --git a/config/suppliers/letter-variant/notify-standard-colour.json b/config/suppliers/letter-variant/notify-standard-colour.json new file mode 100644 index 000000000..d21617cb5 --- /dev/null +++ b/config/suppliers/letter-variant/notify-standard-colour.json @@ -0,0 +1,34 @@ +{ + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "colourCoveragePercentage": { + "operator": "LESS_THAN", + "value": 10 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "description": "Colour printing, economy postage tariff", + "id": "notify-standard-colour", + "name": "Standard Letter (colour)", + "packSpecificationIds": [ + "notify-c5-colour" + ], + "status": "INT", + "supplierId": "synertec", + "type": "STANDARD", + "volumeGroupId": "datagraphic" +} diff --git a/config/suppliers/letter-variant/notify-standard.json b/config/suppliers/letter-variant/notify-standard.json new file mode 100644 index 000000000..5b0a4d9b8 --- /dev/null +++ b/config/suppliers/letter-variant/notify-standard.json @@ -0,0 +1,30 @@ +{ + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "description": "Black printing, economy postage tariff", + "id": "notify-standard", + "name": "Standard Letter", + "packSpecificationIds": [ + "notify-c5" + ], + "status": "PROD", + "supplierId": "synertec", + "type": "STANDARD", + "volumeGroupId": "synertec" +} diff --git a/config/suppliers/pack-specification/digitrials-aspiring.json b/config/suppliers/pack-specification/digitrials-aspiring.json new file mode 100644 index 000000000..d9a478388 --- /dev/null +++ b/config/suppliers/pack-specification/digitrials-aspiring.json @@ -0,0 +1,58 @@ +{ + "assembly": { + "duplex": true, + "envelopeId": "digitrials-aspiring", + "features": [ + "ADMAIL" + ], + "insertIds": [ + "digitrials-aspiring-fast-facts-attachment" + ], + "paper": { + "colour": "WHITE", + "id": "paper-std-white-100", + "name": "Standard White 80gsm", + "recycled": true, + "size": "A4", + "weightGSM": 80 + }, + "printColour": "COLOUR" + }, + "billingId": "insert-economy", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "colourCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 4 + }, + "sides": { + "operator": "LESS_THAN", + "value": 8 + } + }, + "createdAt": "2026-01-12T00:00:00.000Z", + "description": "Envelope and attachment for ASPIRING", + "id": "digitrials-aspiring", + "name": "Digitrials - ASPIRING", + "postage": { + "deliveryDays": 3, + "id": "economy", + "maxThicknessMm": 5, + "maxWeightGrams": 100, + "size": "STANDARD" + }, + "status": "PROD", + "updatedAt": "2026-02-16T17:00:00.000Z", + "version": 2 +} diff --git a/config/suppliers/pack-specification/digitrials-globalminds.json b/config/suppliers/pack-specification/digitrials-globalminds.json new file mode 100644 index 000000000..e7aa11130 --- /dev/null +++ b/config/suppliers/pack-specification/digitrials-globalminds.json @@ -0,0 +1,56 @@ +{ + "assembly": { + "duplex": true, + "envelopeId": "economy-c5", + "features": [ + "ADMAIL" + ], + "insertIds": [ + "digitrials-globalminds-booklet" + ], + "paper": { + "colour": "WHITE", + "id": "paper-std-white-80", + "name": "Standard White 80gsm", + "recycled": true, + "size": "A4", + "weightGSM": 80 + }, + "printColour": "COLOUR" + }, + "billingId": "insert-admail-economy", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "colourCoveragePercentage": { + "operator": "LESS_THAN", + "value": 10 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 4 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "createdAt": "2026-01-12T00:00:00.000Z", + "description": "Envelope and booklet for Global Minds", + "id": "digitrials-globalminds", + "name": "Digitrials - Global Minds", + "postage": { + "deliveryDays": 4, + "id": "admail-economy", + "size": "STANDARD" + }, + "status": "PROD", + "updatedAt": "2026-01-12T00:00:00.000Z", + "version": 1 +} diff --git a/config/suppliers/pack-specification/digitrials-mymelanoma.json b/config/suppliers/pack-specification/digitrials-mymelanoma.json new file mode 100644 index 000000000..1061f995a --- /dev/null +++ b/config/suppliers/pack-specification/digitrials-mymelanoma.json @@ -0,0 +1,55 @@ +{ + "assembly": { + "duplex": true, + "envelopeId": "digitrials-mymelanoma", + "features": [ + "ADMAIL" + ], + "paper": { + "colour": "WHITE", + "id": "paper-std-white-80", + "name": "Standard White 80gsm", + "recycled": true, + "size": "A4", + "weightGSM": 80 + }, + "printColour": "COLOUR" + }, + "billingId": "admail-economy", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "colourCoveragePercentage": { + "operator": "LESS_THAN", + "value": 10 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 4 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "createdAt": "2026-01-12T00:00:00.000Z", + "description": "Envelope for My Melanoma", + "id": "digitrials-mymelanoma", + "name": "Digitrials - MyMelanoma", + "postage": { + "deliveryDays": 4, + "id": "admail-economy", + "maxThicknessMm": 5, + "maxWeightGrams": 100, + "size": "STANDARD" + }, + "status": "PROD", + "updatedAt": "2026-01-12T00:00:00.000Z", + "version": 1 +} diff --git a/config/suppliers/pack-specification/digitrials-ofh.json b/config/suppliers/pack-specification/digitrials-ofh.json new file mode 100644 index 000000000..fd143e8ba --- /dev/null +++ b/config/suppliers/pack-specification/digitrials-ofh.json @@ -0,0 +1,55 @@ +{ + "assembly": { + "duplex": true, + "envelopeId": "digitrials-ofh", + "features": [ + "ADMAIL" + ], + "paper": { + "colour": "WHITE", + "id": "paper-std-white-80", + "name": "Standard White 80gsm", + "recycled": true, + "size": "A4", + "weightGSM": 80 + }, + "printColour": "COLOUR" + }, + "billingId": "admail-economy", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "colourCoveragePercentage": { + "operator": "LESS_THAN", + "value": 10 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 4 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "createdAt": "2026-01-12T00:00:00.000Z", + "description": "Envelope for Our Future Health", + "id": "digitrials-ofh", + "name": "Digitrials - Our Future Health", + "postage": { + "deliveryDays": 4, + "id": "admail-economy", + "maxThicknessMm": 5, + "maxWeightGrams": 100, + "size": "STANDARD" + }, + "status": "PROD", + "updatedAt": "2026-01-12T00:00:00.000Z", + "version": 1 +} diff --git a/config/suppliers/pack-specification/digitrials-prostateprogress.json b/config/suppliers/pack-specification/digitrials-prostateprogress.json new file mode 100644 index 000000000..bb6cd9bbc --- /dev/null +++ b/config/suppliers/pack-specification/digitrials-prostateprogress.json @@ -0,0 +1,55 @@ +{ + "assembly": { + "duplex": true, + "envelopeId": "digitrials-prostateprogress", + "insertIds": [ + "digitrials-prostateprogress-leaflet" + ], + "paper": { + "colour": "WHITE", + "id": "paper-std-white-80", + "name": "Standard White 80gsm", + "recycled": true, + "size": "A4", + "weightGSM": 80 + }, + "printColour": "COLOUR" + }, + "billingId": "insert-admail-economy", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "colourCoveragePercentage": { + "operator": "LESS_THAN", + "value": 10 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 4 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "createdAt": "2026-02-04T00:00:00.000Z", + "description": "Envelope and insert for Prostate Progress", + "id": "digitrials-prostateprogress", + "name": "Digitrials - Prostate Progress", + "postage": { + "deliveryDays": 4, + "id": "admail-economy", + "maxThicknessMm": 5, + "maxWeightGrams": 100, + "size": "STANDARD" + }, + "status": "PROD", + "updatedAt": "2026-02-04T00:00:00.000Z", + "version": 1 +} diff --git a/config/suppliers/pack-specification/digitrials-restore.json b/config/suppliers/pack-specification/digitrials-restore.json new file mode 100644 index 000000000..d3a5b3c4b --- /dev/null +++ b/config/suppliers/pack-specification/digitrials-restore.json @@ -0,0 +1,58 @@ +{ + "assembly": { + "duplex": true, + "envelopeId": "economy-c5", + "features": [ + "ADMAIL" + ], + "insertIds": [ + "digitrials-restore-leaflet" + ], + "paper": { + "colour": "WHITE", + "id": "paper-std-white-80", + "name": "Standard White 80gsm", + "recycled": true, + "size": "A4", + "weightGSM": 80 + }, + "printColour": "COLOUR" + }, + "billingId": "insert-admail-economy", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "colourCoveragePercentage": { + "operator": "LESS_THAN", + "value": 10 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 4 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 4 + }, + "sides": { + "operator": "LESS_THAN", + "value": 8 + } + }, + "createdAt": "2026-01-12T00:00:00.000Z", + "description": "Insert for Restore", + "id": "digitrials-restore", + "name": "Digitrials - Restore", + "postage": { + "deliveryDays": 4, + "id": "admail-economy", + "maxThicknessMm": 5, + "maxWeightGrams": 100, + "size": "STANDARD" + }, + "status": "PROD", + "updatedAt": "2026-01-12T00:00:00.000Z", + "version": 1 +} diff --git a/config/suppliers/pack-specification/nces-abnormal-results-braille.json b/config/suppliers/pack-specification/nces-abnormal-results-braille.json new file mode 100644 index 000000000..97d3eaab7 --- /dev/null +++ b/config/suppliers/pack-specification/nces-abnormal-results-braille.json @@ -0,0 +1,53 @@ +{ + "assembly": { + "duplex": true, + "envelopeId": "braille-whitemail", + "features": [ + "BRAILLE", + "SAME_DAY" + ], + "insertIds": [ + "CSP15" + ], + "paper": { + "colour": "WHITE", + "id": "paper-braille", + "name": "Braille Paper", + "recycled": true, + "size": "A4", + "weightGSM": 120 + }, + "printColour": "BLACK" + }, + "billingId": "sameday-insert-braille", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 1 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 4 + }, + "sides": { + "operator": "LESS_THAN", + "value": 8 + } + }, + "createdAt": "2026-01-12T00:00:00.000Z", + "id": "nces-abnormal-results-braille", + "name": "NCES Abnormal Results Braille", + "postage": { + "deliveryDays": 1, + "id": "articles-blind", + "maxWeightGrams": 100, + "size": "STANDARD" + }, + "status": "INT", + "updatedAt": "2026-01-12T00:00:00.000Z", + "version": 1 +} diff --git a/config/suppliers/pack-specification/nces-abnormal-results.json b/config/suppliers/pack-specification/nces-abnormal-results.json new file mode 100644 index 000000000..600fd4aca --- /dev/null +++ b/config/suppliers/pack-specification/nces-abnormal-results.json @@ -0,0 +1,53 @@ +{ + "assembly": { + "duplex": true, + "envelopeId": "first-c5-whitemail", + "features": [ + "SAME_DAY" + ], + "insertIds": [ + "CSP15" + ], + "paper": { + "colour": "WHITE", + "id": "paper-std-white-80", + "name": "Standard White 80gsm", + "recycled": true, + "size": "A4", + "weightGSM": 80 + }, + "printColour": "BLACK" + }, + "billingId": "sameday-insert-c5", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 1 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 4 + }, + "sides": { + "operator": "LESS_THAN", + "value": 8 + } + }, + "createdAt": "2026-01-12T00:00:00.000Z", + "id": "nces-abnormal-results", + "name": "NCES Abnormal Results", + "postage": { + "deliveryDays": 1, + "id": "first-class", + "maxThicknessMm": 5, + "maxWeightGrams": 100, + "size": "STANDARD" + }, + "status": "INT", + "updatedAt": "2026-01-12T00:00:00.000Z", + "version": 1 +} diff --git a/config/suppliers/pack-specification/nces-invites-braille.json b/config/suppliers/pack-specification/nces-invites-braille.json new file mode 100644 index 000000000..f24712659 --- /dev/null +++ b/config/suppliers/pack-specification/nces-invites-braille.json @@ -0,0 +1,51 @@ +{ + "assembly": { + "duplex": true, + "envelopeId": "braille-whitemail", + "features": [ + "BRAILLE" + ], + "insertIds": [ + "CSP14" + ], + "paper": { + "colour": "WHITE", + "id": "paper-braille", + "name": "Braille Paper", + "recycled": true, + "size": "A4", + "weightGSM": 120 + }, + "printColour": "BLACK" + }, + "billingId": "insert-braille", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 4 + }, + "sides": { + "operator": "LESS_THAN", + "value": 8 + } + }, + "createdAt": "2026-01-12T00:00:00.000Z", + "id": "nces-invites-braille", + "name": "NCES Invites Braille", + "postage": { + "deliveryDays": 3, + "id": "articles-blind", + "size": "STANDARD" + }, + "status": "INT", + "updatedAt": "2026-01-12T00:00:00.000Z", + "version": 1 +} diff --git a/config/suppliers/pack-specification/nces-invites.json b/config/suppliers/pack-specification/nces-invites.json new file mode 100644 index 000000000..e057cc35d --- /dev/null +++ b/config/suppliers/pack-specification/nces-invites.json @@ -0,0 +1,50 @@ +{ + "assembly": { + "duplex": true, + "envelopeId": "economy-c5-whitemail", + "insertIds": [ + "CSP14" + ], + "paper": { + "colour": "WHITE", + "id": "paper-std-white-80", + "name": "Standard White 80gsm", + "recycled": true, + "size": "A4", + "weightGSM": 80 + }, + "printColour": "BLACK" + }, + "billingId": "insert-admail-economy", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 4 + }, + "sides": { + "operator": "LESS_THAN", + "value": 8 + } + }, + "createdAt": "2026-01-12T00:00:00.000Z", + "id": "nces-invites", + "name": "NCES Invites", + "postage": { + "deliveryDays": 3, + "id": "admail-economy", + "maxThicknessMm": 5, + "maxWeightGrams": 100, + "size": "STANDARD" + }, + "status": "INT", + "updatedAt": "2026-01-12T00:00:00.000Z", + "version": 1 +} diff --git a/config/suppliers/pack-specification/notify-admail-whitemail.json b/config/suppliers/pack-specification/notify-admail-whitemail.json new file mode 100644 index 000000000..0132b71f9 --- /dev/null +++ b/config/suppliers/pack-specification/notify-admail-whitemail.json @@ -0,0 +1,51 @@ +{ + "assembly": { + "duplex": true, + "envelopeId": "economy-c5", + "features": [ + "ADMAIL" + ], + "paper": { + "colour": "WHITE", + "id": "paper-std-white-80", + "name": "Standard White 80gsm", + "recycled": true, + "size": "A4", + "weightGSM": 80 + }, + "printColour": "BLACK" + }, + "billingId": "insert-admail-economy", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 2 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "createdAt": "2026-01-12T00:00:00.000Z", + "description": "Admail economy tariff with whitemail envelope", + "id": "notify-admail-whitemail", + "name": "Admail (whitemail)", + "postage": { + "deliveryDays": 4, + "id": "admail-economy", + "maxThicknessMm": 5, + "maxWeightGrams": 100, + "size": "STANDARD" + }, + "status": "PROD", + "updatedAt": "2026-01-12T00:00:00.000Z", + "version": 1 +} diff --git a/config/suppliers/pack-specification/notify-admail.json b/config/suppliers/pack-specification/notify-admail.json new file mode 100644 index 000000000..7344a939b --- /dev/null +++ b/config/suppliers/pack-specification/notify-admail.json @@ -0,0 +1,54 @@ +{ + "assembly": { + "duplex": true, + "envelopeId": "economy-c5", + "features": [ + "ADMAIL" + ], + "paper": { + "colour": "WHITE", + "id": "paper-std-white-80", + "name": "Standard White 80gsm", + "recycled": true, + "size": "A4", + "weightGSM": 80 + }, + "printColour": "BLACK" + }, + "billingId": "admail", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "colourCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "createdAt": "2026-01-12T00:00:00.000Z", + "description": "Admail economy tariff, B&W", + "id": "notify-admail", + "name": "Admail", + "postage": { + "id": "admail", + "maxThicknessMm": 5, + "maxWeightGrams": 100, + "size": "STANDARD" + }, + "status": "PROD", + "updatedAt": "2026-01-12T00:00:00.000Z", + "version": 2 +} diff --git a/config/suppliers/pack-specification/notify-audio.json b/config/suppliers/pack-specification/notify-audio.json new file mode 100644 index 000000000..e5fb5002e --- /dev/null +++ b/config/suppliers/pack-specification/notify-audio.json @@ -0,0 +1,48 @@ +{ + "assembly": { + "duplex": true, + "envelopeId": "audio", + "features": [ + "AUDIO" + ], + "paper": { + "colour": "WHITE", + "id": "paper-std-white-80", + "name": "Standard White 80gsm", + "recycled": true, + "size": "A4", + "weightGSM": 80 + }, + "printColour": "BLACK" + }, + "billingId": "notify-audio", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "createdAt": "2026-01-12T00:00:00.000Z", + "id": "notify-audio", + "name": "Audio CD", + "postage": { + "deliveryDays": 3, + "id": "articles-blind", + "size": "STANDARD" + }, + "status": "DRAFT", + "updatedAt": "2026-01-12T00:00:00.000Z", + "version": 1 +} diff --git a/config/suppliers/pack-specification/notify-braille-whitemail.json b/config/suppliers/pack-specification/notify-braille-whitemail.json new file mode 100644 index 000000000..2ef1f05f6 --- /dev/null +++ b/config/suppliers/pack-specification/notify-braille-whitemail.json @@ -0,0 +1,49 @@ +{ + "assembly": { + "duplex": true, + "envelopeId": "braille-whitemail", + "features": [ + "BRAILLE" + ], + "paper": { + "colour": "WHITE", + "id": "paper-braille", + "name": "Braille Paper", + "recycled": true, + "size": "A4", + "weightGSM": 120 + }, + "printColour": "BLACK" + }, + "billingId": "notify-braille", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "createdAt": "2026-01-12T00:00:00.000Z", + "description": "Braille pack with whitemail return address", + "id": "notify-braille-whitemail", + "name": "Braille letter (whitemail)", + "postage": { + "deliveryDays": 3, + "id": "articles-blind", + "size": "STANDARD" + }, + "status": "PROD", + "updatedAt": "2026-01-12T00:00:00.000Z", + "version": 1 +} diff --git a/config/suppliers/pack-specification/notify-braille.json b/config/suppliers/pack-specification/notify-braille.json new file mode 100644 index 000000000..4417ce98d --- /dev/null +++ b/config/suppliers/pack-specification/notify-braille.json @@ -0,0 +1,48 @@ +{ + "assembly": { + "duplex": true, + "envelopeId": "braille", + "features": [ + "BRAILLE" + ], + "paper": { + "colour": "WHITE", + "id": "paper-braille", + "name": "Braille Paper", + "recycled": true, + "size": "A4", + "weightGSM": 120 + }, + "printColour": "BLACK" + }, + "billingId": "notify-braille", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "createdAt": "2026-01-12T00:00:00.000Z", + "id": "notify-braille", + "name": "Braille Letter", + "postage": { + "deliveryDays": 3, + "id": "articles-blind", + "size": "STANDARD" + }, + "status": "PROD", + "updatedAt": "2026-01-12T00:00:00.000Z", + "version": 1 +} diff --git a/config/suppliers/pack-specification/notify-c4.json b/config/suppliers/pack-specification/notify-c4.json new file mode 100644 index 000000000..dc42c0f1b --- /dev/null +++ b/config/suppliers/pack-specification/notify-c4.json @@ -0,0 +1,48 @@ +{ + "assembly": { + "duplex": true, + "envelopeId": "economy-c5", + "paper": { + "colour": "WHITE", + "id": "paper-std-white-80", + "name": "Standard White 80gsm", + "recycled": true, + "size": "A4", + "weightGSM": 80 + }, + "printColour": "BLACK" + }, + "billingId": "notify-c4", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 20 + }, + "sides": { + "operator": "LESS_THAN", + "value": 40 + } + }, + "createdAt": "2026-01-12T00:00:00.000Z", + "description": "C4 pack (large letter)", + "id": "notify-c4", + "name": "Notify standard (C4)", + "postage": { + "deliveryDays": 3, + "id": "economy", + "maxThicknessMm": 25, + "maxWeightGrams": 500, + "size": "LARGE" + }, + "status": "DRAFT", + "updatedAt": "2026-01-12T00:00:00.000Z", + "version": 1 +} diff --git a/config/suppliers/pack-specification/notify-c5-colour.json b/config/suppliers/pack-specification/notify-c5-colour.json new file mode 100644 index 000000000..fdcee099d --- /dev/null +++ b/config/suppliers/pack-specification/notify-c5-colour.json @@ -0,0 +1,52 @@ +{ + "assembly": { + "duplex": true, + "envelopeId": "economy-c5", + "paper": { + "colour": "WHITE", + "id": "paper-std-white-80", + "name": "Standard White 80gsm", + "recycled": true, + "size": "A4", + "weightGSM": 80 + }, + "printColour": "COLOUR" + }, + "billingId": "notify-c5-colour", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "colourCoveragePercentage": { + "operator": "LESS_THAN", + "value": 10 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 2 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "createdAt": "2026-01-12T00:00:00.000Z", + "description": "C5 pack with colour printing", + "id": "notify-c5-colour", + "name": "Notify standard (colour)", + "postage": { + "deliveryDays": 3, + "id": "economy", + "maxThicknessMm": 5, + "maxWeightGrams": 100, + "size": "STANDARD" + }, + "status": "INT", + "updatedAt": "2026-01-12T00:00:00.000Z", + "version": 1 +} diff --git a/config/suppliers/pack-specification/notify-c5-whitemail.json b/config/suppliers/pack-specification/notify-c5-whitemail.json new file mode 100644 index 000000000..0341599ea --- /dev/null +++ b/config/suppliers/pack-specification/notify-c5-whitemail.json @@ -0,0 +1,48 @@ +{ + "assembly": { + "duplex": true, + "envelopeId": "economy-c5-whitemail", + "paper": { + "colour": "WHITE", + "id": "paper-std-white-80", + "name": "Standard White 80gsm", + "recycled": true, + "size": "A4", + "weightGSM": 80 + }, + "printColour": "BLACK" + }, + "billingId": "notify-c5", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "createdAt": "2026-01-12T00:00:00.000Z", + "description": "C5 pack with whitemail return address", + "id": "notify-c5-whitemail", + "name": "Notify standard (whitemail)", + "postage": { + "deliveryDays": 3, + "id": "economy", + "maxThicknessMm": 5, + "maxWeightGrams": 100, + "size": "STANDARD" + }, + "status": "PROD", + "updatedAt": "2026-01-12T00:00:00.000Z", + "version": 1 +} diff --git a/config/suppliers/pack-specification/notify-c5.json b/config/suppliers/pack-specification/notify-c5.json new file mode 100644 index 000000000..b656c17fa --- /dev/null +++ b/config/suppliers/pack-specification/notify-c5.json @@ -0,0 +1,48 @@ +{ + "assembly": { + "duplex": true, + "envelopeId": "economy-c4", + "paper": { + "colour": "WHITE", + "id": "paper-std-white-80", + "name": "Standard White 80gsm", + "recycled": true, + "size": "A4", + "weightGSM": 80 + }, + "printColour": "BLACK" + }, + "billingId": "notify-c5", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "createdAt": "2026-01-12T00:00:00.000Z", + "description": "C5 pack", + "id": "notify-c5", + "name": "Notify standard", + "postage": { + "deliveryDays": 3, + "id": "economy", + "maxThicknessMm": 5, + "maxWeightGrams": 100, + "size": "STANDARD" + }, + "status": "PROD", + "updatedAt": "2026-01-12T00:00:00.000Z", + "version": 1 +} diff --git a/config/suppliers/pack-specification/notify-first.json b/config/suppliers/pack-specification/notify-first.json new file mode 100644 index 000000000..55543a47e --- /dev/null +++ b/config/suppliers/pack-specification/notify-first.json @@ -0,0 +1,48 @@ +{ + "assembly": { + "duplex": true, + "envelopeId": "first-c5", + "paper": { + "colour": "WHITE", + "id": "paper-std-white-80", + "name": "Standard White 80gsm", + "recycled": true, + "size": "A4", + "weightGSM": 80 + }, + "printColour": "BLACK" + }, + "billingId": "notify-first", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 2 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "createdAt": "2026-01-12T00:00:00.000Z", + "description": "First class postage tariff", + "id": "notify-first", + "name": "First class", + "postage": { + "deliveryDays": 2, + "id": "first-class", + "maxThicknessMm": 5, + "maxWeightGrams": 100, + "size": "STANDARD" + }, + "status": "DRAFT", + "updatedAt": "2026-01-12T00:00:00.000Z", + "version": 1 +} diff --git a/config/suppliers/pack-specification/notify-sameday.json b/config/suppliers/pack-specification/notify-sameday.json new file mode 100644 index 000000000..66b350848 --- /dev/null +++ b/config/suppliers/pack-specification/notify-sameday.json @@ -0,0 +1,51 @@ +{ + "assembly": { + "duplex": true, + "envelopeId": "first-c5", + "features": [ + "SAME_DAY" + ], + "paper": { + "colour": "WHITE", + "id": "paper-std-white-80", + "name": "Standard White 80gsm", + "recycled": true, + "size": "A4", + "weightGSM": 80 + }, + "printColour": "BLACK" + }, + "billingId": "sameday", + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 2 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "createdAt": "2026-01-12T00:00:00.000Z", + "description": "Same day production and dispatch", + "id": "notify-sameday", + "name": "Same day dispatch", + "postage": { + "deliveryDays": 1, + "id": "first-class", + "maxThicknessMm": 5, + "maxWeightGrams": 100, + "size": "STANDARD" + }, + "status": "DRAFT", + "updatedAt": "2026-01-12T00:00:00.000Z", + "version": 1 +} diff --git a/config/suppliers/supplier-allocation/datagraphic-datagraphic.json b/config/suppliers/supplier-allocation/datagraphic-datagraphic.json new file mode 100644 index 000000000..984f34e83 --- /dev/null +++ b/config/suppliers/supplier-allocation/datagraphic-datagraphic.json @@ -0,0 +1,7 @@ +{ + "allocationPercentage": 100, + "id": "datagraphic-datagraphic", + "status": "PROD", + "supplier": "datagraphic", + "volumeGroup": "datagraphic" +} diff --git a/config/suppliers/supplier-allocation/synertec-synertec.json b/config/suppliers/supplier-allocation/synertec-synertec.json new file mode 100644 index 000000000..ddedeb824 --- /dev/null +++ b/config/suppliers/supplier-allocation/synertec-synertec.json @@ -0,0 +1,7 @@ +{ + "allocationPercentage": 100, + "id": "synertec-synertec", + "status": "PROD", + "supplier": "synertec", + "volumeGroup": "synertec" +} diff --git a/config/suppliers/supplier-pack/datagraphic-digitrials-aspiring.json b/config/suppliers/supplier-pack/datagraphic-digitrials-aspiring.json new file mode 100644 index 000000000..f301ac24e --- /dev/null +++ b/config/suppliers/supplier-pack/datagraphic-digitrials-aspiring.json @@ -0,0 +1,7 @@ +{ + "approval": "APPROVED", + "id": "datagraphic-digitrials-aspiring", + "packSpecificationId": "digitrials-aspiring", + "status": "PROD", + "supplierId": "datagraphic" +} diff --git a/config/suppliers/supplier-pack/datagraphic-digitrials-globalminds.json b/config/suppliers/supplier-pack/datagraphic-digitrials-globalminds.json new file mode 100644 index 000000000..3970a0775 --- /dev/null +++ b/config/suppliers/supplier-pack/datagraphic-digitrials-globalminds.json @@ -0,0 +1,7 @@ +{ + "approval": "APPROVED", + "id": "datagraphic-digitrials-globalminds", + "packSpecificationId": "digitrials-globalminds", + "status": "PROD", + "supplierId": "datagraphic" +} diff --git a/config/suppliers/supplier-pack/datagraphic-digitrials-mymelanoma.json b/config/suppliers/supplier-pack/datagraphic-digitrials-mymelanoma.json new file mode 100644 index 000000000..f39b6bdf3 --- /dev/null +++ b/config/suppliers/supplier-pack/datagraphic-digitrials-mymelanoma.json @@ -0,0 +1,7 @@ +{ + "approval": "APPROVED", + "id": "datagraphic-digitrials-mymelanoma", + "packSpecificationId": "digitrials-mymelanoma", + "status": "PROD", + "supplierId": "datagraphic" +} diff --git a/config/suppliers/supplier-pack/datagraphic-digitrials-ofh.json b/config/suppliers/supplier-pack/datagraphic-digitrials-ofh.json new file mode 100644 index 000000000..0dca38fa7 --- /dev/null +++ b/config/suppliers/supplier-pack/datagraphic-digitrials-ofh.json @@ -0,0 +1,7 @@ +{ + "approval": "APPROVED", + "id": "datagraphic-digitrials-ofh", + "packSpecificationId": "digitrials-ofh", + "status": "PROD", + "supplierId": "datagraphic" +} diff --git a/config/suppliers/supplier-pack/datagraphic-digitrials-prostateprogress.json b/config/suppliers/supplier-pack/datagraphic-digitrials-prostateprogress.json new file mode 100644 index 000000000..0926aa2c6 --- /dev/null +++ b/config/suppliers/supplier-pack/datagraphic-digitrials-prostateprogress.json @@ -0,0 +1,7 @@ +{ + "approval": "APPROVED", + "id": "datagraphic-digitrials-prostateprogress", + "packSpecificationId": "digitrials-prostateprogress", + "status": "PROD", + "supplierId": "datagraphic" +} diff --git a/config/suppliers/supplier-pack/datagraphic-digitrials-restore.json b/config/suppliers/supplier-pack/datagraphic-digitrials-restore.json new file mode 100644 index 000000000..72c7e1212 --- /dev/null +++ b/config/suppliers/supplier-pack/datagraphic-digitrials-restore.json @@ -0,0 +1,7 @@ +{ + "approval": "APPROVED", + "id": "datagraphic-digitrials-restore", + "packSpecificationId": "digitrials-restore", + "status": "PROD", + "supplierId": "datagraphic" +} diff --git a/config/suppliers/supplier-pack/datagraphic-notify-admail.json b/config/suppliers/supplier-pack/datagraphic-notify-admail.json new file mode 100644 index 000000000..ebf12c1f8 --- /dev/null +++ b/config/suppliers/supplier-pack/datagraphic-notify-admail.json @@ -0,0 +1,7 @@ +{ + "approval": "APPROVED", + "id": "datagraphic-notify-admail", + "packSpecificationId": "notify-admail", + "status": "PROD", + "supplierId": "datagraphic" +} diff --git a/config/suppliers/supplier-pack/datagraphic-notify-audio.json b/config/suppliers/supplier-pack/datagraphic-notify-audio.json new file mode 100644 index 000000000..20cbd2f80 --- /dev/null +++ b/config/suppliers/supplier-pack/datagraphic-notify-audio.json @@ -0,0 +1,7 @@ +{ + "approval": "DRAFT", + "id": "datagraphic-notify-audio", + "packSpecificationId": "notify-audio", + "status": "DRAFT", + "supplierId": "datagraphic" +} diff --git a/config/suppliers/supplier-pack/datagraphic-notify-braille-whitemail.json b/config/suppliers/supplier-pack/datagraphic-notify-braille-whitemail.json new file mode 100644 index 000000000..2c89088bd --- /dev/null +++ b/config/suppliers/supplier-pack/datagraphic-notify-braille-whitemail.json @@ -0,0 +1,7 @@ +{ + "approval": "DRAFT", + "id": "datagraphic-notify-braille-whitemail", + "packSpecificationId": "notify-braille-whitemail", + "status": "DRAFT", + "supplierId": "datagraphic" +} diff --git a/config/suppliers/supplier-pack/datagraphic-notify-braille.json b/config/suppliers/supplier-pack/datagraphic-notify-braille.json new file mode 100644 index 000000000..d46fcd8ef --- /dev/null +++ b/config/suppliers/supplier-pack/datagraphic-notify-braille.json @@ -0,0 +1,7 @@ +{ + "approval": "DRAFT", + "id": "datagraphic-notify-braille", + "packSpecificationId": "notify-braille", + "status": "DRAFT", + "supplierId": "datagraphic" +} diff --git a/config/suppliers/supplier-pack/datagraphic-notify-c4.json b/config/suppliers/supplier-pack/datagraphic-notify-c4.json new file mode 100644 index 000000000..e54cd61e9 --- /dev/null +++ b/config/suppliers/supplier-pack/datagraphic-notify-c4.json @@ -0,0 +1,7 @@ +{ + "approval": "DRAFT", + "id": "datagraphic-notify-c4", + "packSpecificationId": "notify-c4", + "status": "DRAFT", + "supplierId": "datagraphic" +} diff --git a/config/suppliers/supplier-pack/datagraphic-notify-c5-colour.json b/config/suppliers/supplier-pack/datagraphic-notify-c5-colour.json new file mode 100644 index 000000000..aca5790aa --- /dev/null +++ b/config/suppliers/supplier-pack/datagraphic-notify-c5-colour.json @@ -0,0 +1,7 @@ +{ + "approval": "APPROVED", + "id": "datagraphic-notify-c5-colour", + "packSpecificationId": "notify-c5-colour", + "status": "INT", + "supplierId": "datagraphic" +} diff --git a/config/suppliers/supplier-pack/datagraphic-notify-c5-whitemail.json b/config/suppliers/supplier-pack/datagraphic-notify-c5-whitemail.json new file mode 100644 index 000000000..98daba720 --- /dev/null +++ b/config/suppliers/supplier-pack/datagraphic-notify-c5-whitemail.json @@ -0,0 +1,7 @@ +{ + "approval": "APPROVED", + "id": "datagraphic-notify-c5-whitemail", + "packSpecificationId": "notify-c5-whitemail", + "status": "PROD", + "supplierId": "datagraphic" +} diff --git a/config/suppliers/supplier-pack/datagraphic-notify-c5.json b/config/suppliers/supplier-pack/datagraphic-notify-c5.json new file mode 100644 index 000000000..cea709c6e --- /dev/null +++ b/config/suppliers/supplier-pack/datagraphic-notify-c5.json @@ -0,0 +1,7 @@ +{ + "approval": "APPROVED", + "id": "datagraphic-notify-c5", + "packSpecificationId": "notify-c5", + "status": "PROD", + "supplierId": "datagraphic" +} diff --git a/config/suppliers/supplier-pack/datagraphic-notify-first.json b/config/suppliers/supplier-pack/datagraphic-notify-first.json new file mode 100644 index 000000000..a5caef81f --- /dev/null +++ b/config/suppliers/supplier-pack/datagraphic-notify-first.json @@ -0,0 +1,7 @@ +{ + "approval": "DRAFT", + "id": "datagraphic-notify-first", + "packSpecificationId": "notify-first", + "status": "DRAFT", + "supplierId": "datagraphic" +} diff --git a/config/suppliers/supplier-pack/datagraphic-notify-sameday.json b/config/suppliers/supplier-pack/datagraphic-notify-sameday.json new file mode 100644 index 000000000..309901e0b --- /dev/null +++ b/config/suppliers/supplier-pack/datagraphic-notify-sameday.json @@ -0,0 +1,7 @@ +{ + "approval": "DRAFT", + "id": "datagraphic-notify-sameday", + "packSpecificationId": "notify-sameday", + "status": "DRAFT", + "supplierId": "datagraphic" +} diff --git a/config/suppliers/supplier-pack/mba-notify-audio.json b/config/suppliers/supplier-pack/mba-notify-audio.json new file mode 100644 index 000000000..6a4f53398 --- /dev/null +++ b/config/suppliers/supplier-pack/mba-notify-audio.json @@ -0,0 +1,7 @@ +{ + "approval": "DRAFT", + "id": "mba-notify-audio", + "packSpecificationId": "notify-audio", + "status": "DRAFT", + "supplierId": "mba" +} diff --git a/config/suppliers/supplier-pack/mba-notify-braille-whitemail.json b/config/suppliers/supplier-pack/mba-notify-braille-whitemail.json new file mode 100644 index 000000000..00f97f80a --- /dev/null +++ b/config/suppliers/supplier-pack/mba-notify-braille-whitemail.json @@ -0,0 +1,7 @@ +{ + "approval": "DRAFT", + "id": "mba-notify-braille-whitemail", + "packSpecificationId": "notify-braille-whitemail", + "status": "DRAFT", + "supplierId": "mba" +} diff --git a/config/suppliers/supplier-pack/mba-notify-braille.json b/config/suppliers/supplier-pack/mba-notify-braille.json new file mode 100644 index 000000000..c1bedcabd --- /dev/null +++ b/config/suppliers/supplier-pack/mba-notify-braille.json @@ -0,0 +1,7 @@ +{ + "approval": "DRAFT", + "id": "mba-notify-braille", + "packSpecificationId": "notify-braille", + "status": "DRAFT", + "supplierId": "mba" +} diff --git a/config/suppliers/supplier-pack/mba-notify-c4.json b/config/suppliers/supplier-pack/mba-notify-c4.json new file mode 100644 index 000000000..2d5b12c0d --- /dev/null +++ b/config/suppliers/supplier-pack/mba-notify-c4.json @@ -0,0 +1,7 @@ +{ + "approval": "DRAFT", + "id": "mba-notify-c4", + "packSpecificationId": "notify-c4", + "status": "DRAFT", + "supplierId": "mba" +} diff --git a/config/suppliers/supplier-pack/mba-notify-c5-whitemail.json b/config/suppliers/supplier-pack/mba-notify-c5-whitemail.json new file mode 100644 index 000000000..d5daf8217 --- /dev/null +++ b/config/suppliers/supplier-pack/mba-notify-c5-whitemail.json @@ -0,0 +1,7 @@ +{ + "approval": "DRAFT", + "id": "mba-notify-c5-whitemail", + "packSpecificationId": "notify-c5-whitemail", + "status": "DRAFT", + "supplierId": "mba" +} diff --git a/config/suppliers/supplier-pack/mba-notify-c5.json b/config/suppliers/supplier-pack/mba-notify-c5.json new file mode 100644 index 000000000..5c9f7e67c --- /dev/null +++ b/config/suppliers/supplier-pack/mba-notify-c5.json @@ -0,0 +1,7 @@ +{ + "approval": "DRAFT", + "id": "mba-notify-c5", + "packSpecificationId": "notify-c5", + "status": "DRAFT", + "supplierId": "mba" +} diff --git a/config/suppliers/supplier-pack/mba-notify-first.json b/config/suppliers/supplier-pack/mba-notify-first.json new file mode 100644 index 000000000..8b2092d95 --- /dev/null +++ b/config/suppliers/supplier-pack/mba-notify-first.json @@ -0,0 +1,7 @@ +{ + "approval": "DRAFT", + "id": "mba-notify-first", + "packSpecificationId": "notify-first", + "status": "DRAFT", + "supplierId": "mba" +} diff --git a/config/suppliers/supplier-pack/mba-notify-sameday.json b/config/suppliers/supplier-pack/mba-notify-sameday.json new file mode 100644 index 000000000..208210918 --- /dev/null +++ b/config/suppliers/supplier-pack/mba-notify-sameday.json @@ -0,0 +1,7 @@ +{ + "approval": "DRAFT", + "id": "mba-notify-sameday", + "packSpecificationId": "notify-sameday", + "status": "DRAFT", + "supplierId": "mba" +} diff --git a/config/suppliers/supplier-pack/synertec-nces-abnormal-results-braille.json b/config/suppliers/supplier-pack/synertec-nces-abnormal-results-braille.json new file mode 100644 index 000000000..b27f23748 --- /dev/null +++ b/config/suppliers/supplier-pack/synertec-nces-abnormal-results-braille.json @@ -0,0 +1,7 @@ +{ + "approval": "SUBMITTED", + "id": "synertec-nces-abnormal-results-braille", + "packSpecificationId": "nces-abnormal-results-braille", + "status": "INT", + "supplierId": "synertec" +} diff --git a/config/suppliers/supplier-pack/synertec-nces-abnormal-results.json b/config/suppliers/supplier-pack/synertec-nces-abnormal-results.json new file mode 100644 index 000000000..52c28638d --- /dev/null +++ b/config/suppliers/supplier-pack/synertec-nces-abnormal-results.json @@ -0,0 +1,7 @@ +{ + "approval": "SUBMITTED", + "id": "synertec-nces-abnormal-results", + "packSpecificationId": "nces-abnormal-results", + "status": "INT", + "supplierId": "synertec" +} diff --git a/config/suppliers/supplier-pack/synertec-nces-invites-braille.json b/config/suppliers/supplier-pack/synertec-nces-invites-braille.json new file mode 100644 index 000000000..b22f26ef4 --- /dev/null +++ b/config/suppliers/supplier-pack/synertec-nces-invites-braille.json @@ -0,0 +1,7 @@ +{ + "approval": "SUBMITTED", + "id": "synertec-nces-invites-braille", + "packSpecificationId": "nces-invites-braille", + "status": "INT", + "supplierId": "synertec" +} diff --git a/config/suppliers/supplier-pack/synertec-nces-invites.json b/config/suppliers/supplier-pack/synertec-nces-invites.json new file mode 100644 index 000000000..e84eea5ae --- /dev/null +++ b/config/suppliers/supplier-pack/synertec-nces-invites.json @@ -0,0 +1,7 @@ +{ + "approval": "SUBMITTED", + "id": "synertec-nces-invites", + "packSpecificationId": "nces-invites", + "status": "INT", + "supplierId": "synertec" +} diff --git a/config/suppliers/supplier-pack/synertec-notify-admail-whitemail.json b/config/suppliers/supplier-pack/synertec-notify-admail-whitemail.json new file mode 100644 index 000000000..290abcd88 --- /dev/null +++ b/config/suppliers/supplier-pack/synertec-notify-admail-whitemail.json @@ -0,0 +1,7 @@ +{ + "approval": "APPROVED", + "id": "synertec-notify-admail-whitemail", + "packSpecificationId": "notify-admail-whitemail", + "status": "PROD", + "supplierId": "synertec" +} diff --git a/config/suppliers/supplier-pack/synertec-notify-admail.json b/config/suppliers/supplier-pack/synertec-notify-admail.json new file mode 100644 index 000000000..96bd162d3 --- /dev/null +++ b/config/suppliers/supplier-pack/synertec-notify-admail.json @@ -0,0 +1,7 @@ +{ + "approval": "APPROVED", + "id": "synertec-notify-admail", + "packSpecificationId": "notify-admail", + "status": "PROD", + "supplierId": "synertec" +} diff --git a/config/suppliers/supplier-pack/synertec-notify-audio.json b/config/suppliers/supplier-pack/synertec-notify-audio.json new file mode 100644 index 000000000..6b46cc572 --- /dev/null +++ b/config/suppliers/supplier-pack/synertec-notify-audio.json @@ -0,0 +1,7 @@ +{ + "approval": "DRAFT", + "id": "synertec-notify-audio", + "packSpecificationId": "notify-audio", + "status": "DRAFT", + "supplierId": "synertec" +} diff --git a/config/suppliers/supplier-pack/synertec-notify-braille-whitemail.json b/config/suppliers/supplier-pack/synertec-notify-braille-whitemail.json new file mode 100644 index 000000000..7743da115 --- /dev/null +++ b/config/suppliers/supplier-pack/synertec-notify-braille-whitemail.json @@ -0,0 +1,7 @@ +{ + "approval": "DRAFT", + "id": "synertec-notify-braille-whitemail", + "packSpecificationId": "notify-braille-whitemail", + "status": "INT", + "supplierId": "synertec" +} diff --git a/config/suppliers/supplier-pack/synertec-notify-braille.json b/config/suppliers/supplier-pack/synertec-notify-braille.json new file mode 100644 index 000000000..8ec9cf25f --- /dev/null +++ b/config/suppliers/supplier-pack/synertec-notify-braille.json @@ -0,0 +1,7 @@ +{ + "approval": "DRAFT", + "id": "synertec-notify-braille", + "packSpecificationId": "notify-braille", + "status": "INT", + "supplierId": "synertec" +} diff --git a/config/suppliers/supplier-pack/synertec-notify-c4.json b/config/suppliers/supplier-pack/synertec-notify-c4.json new file mode 100644 index 000000000..060591839 --- /dev/null +++ b/config/suppliers/supplier-pack/synertec-notify-c4.json @@ -0,0 +1,7 @@ +{ + "approval": "DRAFT", + "id": "synertec-notify-c4", + "packSpecificationId": "notify-c4", + "status": "DRAFT", + "supplierId": "synertec" +} diff --git a/config/suppliers/supplier-pack/synertec-notify-c5-colour.json b/config/suppliers/supplier-pack/synertec-notify-c5-colour.json new file mode 100644 index 000000000..16ca8e717 --- /dev/null +++ b/config/suppliers/supplier-pack/synertec-notify-c5-colour.json @@ -0,0 +1,7 @@ +{ + "approval": "APPROVED", + "id": "synertec-notify-c5-colour", + "packSpecificationId": "notify-c5-colour", + "status": "INT", + "supplierId": "synertec" +} diff --git a/config/suppliers/supplier-pack/synertec-notify-c5-whitemail.json b/config/suppliers/supplier-pack/synertec-notify-c5-whitemail.json new file mode 100644 index 000000000..301ec4806 --- /dev/null +++ b/config/suppliers/supplier-pack/synertec-notify-c5-whitemail.json @@ -0,0 +1,7 @@ +{ + "approval": "APPROVED", + "id": "synertec-notify-c5-whitemail", + "packSpecificationId": "notify-c5-whitemail", + "status": "PROD", + "supplierId": "synertec" +} diff --git a/config/suppliers/supplier-pack/synertec-notify-c5.json b/config/suppliers/supplier-pack/synertec-notify-c5.json new file mode 100644 index 000000000..80752c2f3 --- /dev/null +++ b/config/suppliers/supplier-pack/synertec-notify-c5.json @@ -0,0 +1,7 @@ +{ + "approval": "APPROVED", + "id": "synertec-notify-c5", + "packSpecificationId": "notify-c5", + "status": "PROD", + "supplierId": "synertec" +} diff --git a/config/suppliers/supplier-pack/synertec-notify-first.json b/config/suppliers/supplier-pack/synertec-notify-first.json new file mode 100644 index 000000000..9617adbd1 --- /dev/null +++ b/config/suppliers/supplier-pack/synertec-notify-first.json @@ -0,0 +1,7 @@ +{ + "approval": "DRAFT", + "id": "synertec-notify-first", + "packSpecificationId": "notify-first", + "status": "DRAFT", + "supplierId": "synertec" +} diff --git a/config/suppliers/supplier-pack/synertec-notify-sameday.json b/config/suppliers/supplier-pack/synertec-notify-sameday.json new file mode 100644 index 000000000..50f9ff9fd --- /dev/null +++ b/config/suppliers/supplier-pack/synertec-notify-sameday.json @@ -0,0 +1,7 @@ +{ + "approval": "DRAFT", + "id": "synertec-notify-sameday", + "packSpecificationId": "notify-sameday", + "status": "DRAFT", + "supplierId": "synertec" +} diff --git a/config/suppliers/supplier/datagraphic.json b/config/suppliers/supplier/datagraphic.json new file mode 100644 index 000000000..40d0b2ea3 --- /dev/null +++ b/config/suppliers/supplier/datagraphic.json @@ -0,0 +1,7 @@ +{ + "channelType": "LETTER", + "dailyCapacity": 500000, + "id": "datagraphic", + "name": "PSL/Datagraphic", + "status": "PROD" +} diff --git a/config/suppliers/supplier/mba.json b/config/suppliers/supplier/mba.json new file mode 100644 index 000000000..2016fb408 --- /dev/null +++ b/config/suppliers/supplier/mba.json @@ -0,0 +1,7 @@ +{ + "channelType": "LETTER", + "dailyCapacity": 500000, + "id": "mba", + "name": "MBA Group", + "status": "INT" +} diff --git a/config/suppliers/supplier/supplier1.json b/config/suppliers/supplier/supplier1.json new file mode 100644 index 000000000..5ef81166a --- /dev/null +++ b/config/suppliers/supplier/supplier1.json @@ -0,0 +1,7 @@ +{ + "channelType": "LETTER", + "dailyCapacity": 500000, + "id": "supplier1", + "name": "Supplier1", + "status": "PROD" +} diff --git a/config/suppliers/supplier/supplier2.json b/config/suppliers/supplier/supplier2.json new file mode 100644 index 000000000..883da65cc --- /dev/null +++ b/config/suppliers/supplier/supplier2.json @@ -0,0 +1,7 @@ +{ + "channelType": "LETTER", + "dailyCapacity": 500000, + "id": "supplier2", + "name": "Supplier2", + "status": "PROD" +} diff --git a/config/suppliers/supplier/synertec.json b/config/suppliers/supplier/synertec.json new file mode 100644 index 000000000..faa8ef45a --- /dev/null +++ b/config/suppliers/supplier/synertec.json @@ -0,0 +1,7 @@ +{ + "channelType": "LETTER", + "dailyCapacity": 500000, + "id": "synertec", + "name": "Synertec", + "status": "PROD" +} diff --git a/config/suppliers/volume-group/accessible.json b/config/suppliers/volume-group/accessible.json new file mode 100644 index 000000000..8fd399cdc --- /dev/null +++ b/config/suppliers/volume-group/accessible.json @@ -0,0 +1,8 @@ +{ + "description": "Accessible formats", + "endDate": "2028-03-31", + "id": "accessible", + "name": "Accessible", + "startDate": "2026-04-01", + "status": "DRAFT" +} diff --git a/config/suppliers/volume-group/bau.json b/config/suppliers/volume-group/bau.json new file mode 100644 index 000000000..238f3a4a9 --- /dev/null +++ b/config/suppliers/volume-group/bau.json @@ -0,0 +1,8 @@ +{ + "description": "Combined BAU volume", + "endDate": "2028-03-31", + "id": "bau", + "name": "BAU", + "startDate": "2026-04-01", + "status": "DRAFT" +} diff --git a/config/suppliers/volume-group/datagraphic.json b/config/suppliers/volume-group/datagraphic.json new file mode 100644 index 000000000..36951e2b9 --- /dev/null +++ b/config/suppliers/volume-group/datagraphic.json @@ -0,0 +1,7 @@ +{ + "description": "PSL/Datagraphic contract", + "id": "datagraphic", + "name": "Datagraphic", + "startDate": "2026-01-01", + "status": "PROD" +} diff --git a/config/suppliers/volume-group/sameday.json b/config/suppliers/volume-group/sameday.json new file mode 100644 index 000000000..d17c520a2 --- /dev/null +++ b/config/suppliers/volume-group/sameday.json @@ -0,0 +1,8 @@ +{ + "description": "Same day dispatch", + "endDate": "2028-03-31", + "id": "sameday", + "name": "Same Day", + "startDate": "2026-04-01", + "status": "DRAFT" +} diff --git a/config/suppliers/volume-group/synertec.json b/config/suppliers/volume-group/synertec.json new file mode 100644 index 000000000..cdb63ee8a --- /dev/null +++ b/config/suppliers/volume-group/synertec.json @@ -0,0 +1,7 @@ +{ + "description": "Synertec contract", + "id": "synertec", + "name": "Synertec", + "startDate": "2026-01-01", + "status": "PROD" +} diff --git a/infrastructure/terraform/components/api/ddb_table_supplier_configuration.tf b/infrastructure/terraform/components/api/ddb_table_supplier_configuration.tf index 8d089a19b..90b876a6f 100644 --- a/infrastructure/terraform/components/api/ddb_table_supplier_configuration.tf +++ b/infrastructure/terraform/components/api/ddb_table_supplier_configuration.tf @@ -2,8 +2,8 @@ resource "aws_dynamodb_table" "supplier-configuration" { name = "${local.csi}-supplier-config" billing_mode = "PAY_PER_REQUEST" - hash_key = "PK" - range_key = "SK" + hash_key = "pk" + range_key = "sk" ttl { attribute_name = "ttl" @@ -11,12 +11,12 @@ resource "aws_dynamodb_table" "supplier-configuration" { } attribute { - name = "PK" + name = "pk" type = "S" } attribute { - name = "SK" + name = "sk" type = "S" } @@ -34,13 +34,13 @@ resource "aws_dynamodb_table" "supplier-configuration" { global_secondary_index { name = "EntityTypeIndex" hash_key = "entityType" - range_key = "SK" + range_key = "sk" projection_type = "ALL" } global_secondary_index { name = "volumeGroup-index" - hash_key = "PK" + hash_key = "pk" range_key = "volumeGroup" projection_type = "ALL" } From 4451eee8fe07359aba2479f608aee3cdfe147155 Mon Sep 17 00:00:00 2001 From: David Wass Date: Thu, 9 Apr 2026 14:17:42 +0100 Subject: [PATCH 2/9] Fix existing database access --- internal/datastore/src/__test__/db.ts | 10 +++++----- .../__test__/supplier-config-repository.test.ts | 16 ++++++++-------- .../datastore/src/supplier-config-repository.ts | 10 +++++----- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/internal/datastore/src/__test__/db.ts b/internal/datastore/src/__test__/db.ts index 481166e60..9d0bf0e1e 100644 --- a/internal/datastore/src/__test__/db.ts +++ b/internal/datastore/src/__test__/db.ts @@ -151,14 +151,14 @@ const createSupplierConfigTableCommand = new CreateTableCommand({ TableName: "supplier-config", BillingMode: "PAY_PER_REQUEST", KeySchema: [ - { AttributeName: "PK", KeyType: "HASH" }, // Partition key - { AttributeName: "SK", KeyType: "RANGE" }, // Sort key + { AttributeName: "pk", KeyType: "HASH" }, // Partition key + { AttributeName: "sk", KeyType: "RANGE" }, // Sort key ], GlobalSecondaryIndexes: [ { IndexName: "volumeGroup-index", KeySchema: [ - { AttributeName: "PK", KeyType: "HASH" }, // Partition key for GSI + { AttributeName: "pk", KeyType: "HASH" }, // Partition key for GSI { AttributeName: "volumeGroup", KeyType: "RANGE" }, // Sort key for GSI ], Projection: { @@ -167,8 +167,8 @@ const createSupplierConfigTableCommand = new CreateTableCommand({ }, ], AttributeDefinitions: [ - { AttributeName: "PK", AttributeType: "S" }, - { AttributeName: "SK", AttributeType: "S" }, + { AttributeName: "pk", AttributeType: "S" }, + { AttributeName: "sk", AttributeType: "S" }, { AttributeName: "volumeGroup", AttributeType: "S" }, ], }); diff --git a/internal/datastore/src/__test__/supplier-config-repository.test.ts b/internal/datastore/src/__test__/supplier-config-repository.test.ts index 9fde74f94..ddd44fd4d 100644 --- a/internal/datastore/src/__test__/supplier-config-repository.test.ts +++ b/internal/datastore/src/__test__/supplier-config-repository.test.ts @@ -9,8 +9,8 @@ import { SupplierConfigRepository } from "../supplier-config-repository"; function createLetterVariantItem(variantId: string) { return { - PK: "LETTER_VARIANT", - SK: variantId, + pk: "ENTITY#letter-variant", + sk: `ID#${variantId}`, id: variantId, name: `Variant ${variantId}`, description: `Description for variant ${variantId}`, @@ -29,8 +29,8 @@ function createVolumeGroupItem(groupId: string, status = "PROD") { .toISOString() .split("T")[0]; // Ends in a day to ensure it's active based on end date. Tests can override this if needed. return { - PK: "VOLUME_GROUP", - SK: groupId, + pk: "ENTITY#volume-group", + sk: `ID#${groupId}`, id: groupId, name: `Volume Group ${groupId}`, description: `Description for volume group ${groupId}`, @@ -46,8 +46,8 @@ function createSupplierAllocationItem( supplier: string, ) { return { - PK: `SUPPLIER_ALLOCATION`, - SK: allocationId, + pk: "ENTITY#supplier-allocation", + sk: `ID#${allocationId}`, id: allocationId, status: "PROD", volumeGroup: groupId, @@ -58,8 +58,8 @@ function createSupplierAllocationItem( function createSupplierItem(supplierId: string) { return { - PK: "SUPPLIER", - SK: supplierId, + pk: "ENTITY#supplier", + sk: `ID#${supplierId}`, id: supplierId, name: `Supplier ${supplierId}`, channelType: "LETTER", diff --git a/internal/datastore/src/supplier-config-repository.ts b/internal/datastore/src/supplier-config-repository.ts index 1f82bb0c2..4eeeddb10 100644 --- a/internal/datastore/src/supplier-config-repository.ts +++ b/internal/datastore/src/supplier-config-repository.ts @@ -28,7 +28,7 @@ export class SupplierConfigRepository { const result = await this.ddbClient.send( new GetCommand({ TableName: this.config.supplierConfigTableName, - Key: { PK: "LETTER_VARIANT", SK: variantId }, + Key: { pk: "ENTITY#letter-variant", sk: `ID#${variantId}` }, }), ); if (!result.Item) { @@ -42,7 +42,7 @@ export class SupplierConfigRepository { const result = await this.ddbClient.send( new GetCommand({ TableName: this.config.supplierConfigTableName, - Key: { PK: "VOLUME_GROUP", SK: groupId }, + Key: { pk: "ENTITY#volume-group", sk: `ID#${groupId}` }, }), ); if (!result.Item) { @@ -61,12 +61,12 @@ export class SupplierConfigRepository { KeyConditionExpression: "#pk = :pk AND #group = :groupId", FilterExpression: "#status = :status ", ExpressionAttributeNames: { - "#pk": "PK", + "#pk": "pk", "#group": "volumeGroup", "#status": "status", }, ExpressionAttributeValues: { - ":pk": "SUPPLIER_ALLOCATION", + ":pk": "ENTITY#supplier-allocation", ":groupId": groupId, ":status": "PROD", }, @@ -87,7 +87,7 @@ export class SupplierConfigRepository { const result = await this.ddbClient.send( new GetCommand({ TableName: this.config.supplierConfigTableName, - Key: { PK: "SUPPLIER", SK: supplierId }, + Key: { pk: "ENTITY#supplier", sk: `ID#${supplierId}` }, }), ); if (!result.Item) { From fafd12ffed15538f401267697bbfe6ed5919ba0f Mon Sep 17 00:00:00 2001 From: David Wass Date: Thu, 9 Apr 2026 15:07:17 +0100 Subject: [PATCH 3/9] Initial config dev test data --- .github/workflows/stage-3-build.yaml | 3 +- .../letter-variant/notify-standard-test1.json | 29 +++++++++++++++++++ .../supplier1-volumeGroup-test1.json | 7 +++++ .../supplier2-volumeGroup-test1.json | 7 +++++ .../volume-group/volumeGroup-test1.json | 7 +++++ 5 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 config/suppliers/letter-variant/notify-standard-test1.json create mode 100644 config/suppliers/supplier-allocation/supplier1-volumeGroup-test1.json create mode 100644 config/suppliers/supplier-allocation/supplier2-volumeGroup-test1.json create mode 100644 config/suppliers/volume-group/volumeGroup-test1.json diff --git a/.github/workflows/stage-3-build.yaml b/.github/workflows/stage-3-build.yaml index 516bc9473..253810a11 100644 --- a/.github/workflows/stage-3-build.yaml +++ b/.github/workflows/stage-3-build.yaml @@ -161,7 +161,8 @@ jobs: --targetAccountGroup "nhs-notify-supplier-api-dev" \ --terraformAction "apply" \ --overrideProjectName "nhs" \ - --overrideRoleName "nhs-main-acct-supplier-api-github-deploy" + --overrideRoleName "nhs-main-acct-supplier-api-github-deploy" \ + --internalRef "feature/CCM-12444-supplier-config-publishing-workflow" artefact-proxies: name: "Build proxies" runs-on: ubuntu-latest diff --git a/config/suppliers/letter-variant/notify-standard-test1.json b/config/suppliers/letter-variant/notify-standard-test1.json new file mode 100644 index 000000000..0138a738f --- /dev/null +++ b/config/suppliers/letter-variant/notify-standard-test1.json @@ -0,0 +1,29 @@ +{ + "constraints": { + "blackCoveragePercentage": { + "operator": "LESS_THAN", + "value": 20 + }, + "deliveryDays": { + "operator": "LESS_THAN", + "value": 3 + }, + "sheets": { + "operator": "LESS_THAN", + "value": 5 + }, + "sides": { + "operator": "LESS_THAN", + "value": 10 + } + }, + "description": "Dev test variant for happy path testing", + "id": "notify-standard-test1", + "name": "Dev Happy Path", + "packSpecificationIds": [ + "notify-c5" + ], + "status": "PROD", + "type": "STANDARD", + "volumeGroupId": "volumeGroup-test1" +} diff --git a/config/suppliers/supplier-allocation/supplier1-volumeGroup-test1.json b/config/suppliers/supplier-allocation/supplier1-volumeGroup-test1.json new file mode 100644 index 000000000..81a82b31b --- /dev/null +++ b/config/suppliers/supplier-allocation/supplier1-volumeGroup-test1.json @@ -0,0 +1,7 @@ +{ + "allocationPercentage": 30, + "id": "supplier1-volumeGroup-test1", + "status": "PROD", + "supplier": "supplier1", + "volumeGroup": "volumeGroup-test1" +} diff --git a/config/suppliers/supplier-allocation/supplier2-volumeGroup-test1.json b/config/suppliers/supplier-allocation/supplier2-volumeGroup-test1.json new file mode 100644 index 000000000..9d34abba3 --- /dev/null +++ b/config/suppliers/supplier-allocation/supplier2-volumeGroup-test1.json @@ -0,0 +1,7 @@ +{ + "allocationPercentage": 70, + "id": "supplier2-volumeGroup-test1", + "status": "PROD", + "supplier": "supplier2", + "volumeGroup": "volumeGroup-test1" +} diff --git a/config/suppliers/volume-group/volumeGroup-test1.json b/config/suppliers/volume-group/volumeGroup-test1.json new file mode 100644 index 000000000..3b3bab804 --- /dev/null +++ b/config/suppliers/volume-group/volumeGroup-test1.json @@ -0,0 +1,7 @@ +{ + "description": "Dev Test Volume Group 1", + "id": "volumeGroup-test1", + "name": "Dev Test Volume Group 1", + "startDate": "2026-01-01", + "status": "PROD" +} From 777b1e09df3d09eedcc02eab06efd4e84ef96041 Mon Sep 17 00:00:00 2001 From: David Wass Date: Tue, 14 Apr 2026 10:29:28 +0100 Subject: [PATCH 4/9] add force --- .github/scripts/dispatch_internal_repo_workflow.sh | 11 +++++++++++ .github/workflows/stage-3-build.yaml | 3 ++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/scripts/dispatch_internal_repo_workflow.sh b/.github/scripts/dispatch_internal_repo_workflow.sh index 599292976..0a08d3002 100755 --- a/.github/scripts/dispatch_internal_repo_workflow.sh +++ b/.github/scripts/dispatch_internal_repo_workflow.sh @@ -108,6 +108,10 @@ while [[ $# -gt 0 ]]; do tableName="$2" shift 2 ;; + --force) # Force apply flag (optional) + force="$2" + shift 2 + ;; *) echo "[ERROR] Unknown argument: $1" exit 1 @@ -210,6 +214,10 @@ if [{ -z "$tableName" }]; then tableName="" fi +if [[ -z "$force" ]]; then + force="" +fi + echo "==================== Workflow Dispatch Parameters ====================" echo " infraRepoName: $infraRepoName" echo " releaseVersion: $releaseVersion" @@ -230,6 +238,7 @@ echo " boundedContext: $boundedContext" echo " targetDomain: $targetDomain" echo " version: $version" echo " tableName: $tableName" +echo " force: $force" DISPATCH_EVENT=$(jq -ncM \ --arg infraRepoName "$infraRepoName" \ @@ -250,6 +259,7 @@ DISPATCH_EVENT=$(jq -ncM \ --arg targetDomain "$targetDomain" \ --arg version "$version" \ --arg tableName "$tableName" \ + --arg force "$force" \ '{ "ref": "'"$internalRef"'", "inputs": ( @@ -266,6 +276,7 @@ DISPATCH_EVENT=$(jq -ncM \ (if $targetDomain != "" then { "targetDomain": $targetDomain } else {} end) + (if $version != "" then { "version": $version } else {} end) + (if $tableName != "" then { "tableName": $tableName } else {} end) + + (if $force != "" then { "force": $force } else {} end) + (if $targetAccountGroup != "" then { "targetAccountGroup": $targetAccountGroup } else {} end) + { "releaseVersion": $releaseVersion, diff --git a/.github/workflows/stage-3-build.yaml b/.github/workflows/stage-3-build.yaml index 253810a11..1cbb78848 100644 --- a/.github/workflows/stage-3-build.yaml +++ b/.github/workflows/stage-3-build.yaml @@ -212,4 +212,5 @@ jobs: --targetComponent "config" \ --targetAccountGroup "nhs-notify-suppliers-dev" \ --tableName "supplier-config" \ - --internalRef "feature/CCM-12444-supplier-config-publishing-workflow" + --internalRef "feature/CCM-12444-supplier-config-publishing-workflow" \ + --force "false" From d40f0858c5a2450c11d4d78207a62a23ff52607f Mon Sep 17 00:00:00 2001 From: David Wass Date: Thu, 19 Mar 2026 15:34:59 +0000 Subject: [PATCH 5/9] CCM-13372 - Select Preferred Pack --- .../api/ddb_table_supplier_configuration.tf | 12 ++ .../api/module_lambda_supplier_allocator.tf | 3 +- internal/datastore/src/__test__/db.ts | 11 + .../supplier-config-repository.test.ts | 82 ++++++++ .../src/supplier-config-repository.ts | 39 ++++ .../__tests__/allocate-handler.test.ts | 13 +- .../src/handler/allocate-handler.ts | 38 +++- .../__tests__/supplier-config.test.ts | 188 ++++++++++++++---- .../src/services/supplier-config.ts | 73 ++++++- 9 files changed, 411 insertions(+), 48 deletions(-) diff --git a/infrastructure/terraform/components/api/ddb_table_supplier_configuration.tf b/infrastructure/terraform/components/api/ddb_table_supplier_configuration.tf index 90b876a6f..c43e4cb2c 100644 --- a/infrastructure/terraform/components/api/ddb_table_supplier_configuration.tf +++ b/infrastructure/terraform/components/api/ddb_table_supplier_configuration.tf @@ -30,6 +30,11 @@ resource "aws_dynamodb_table" "supplier-configuration" { type = "S" } + attribute { + name = "packSpecificationId" + type = "S" + } + // The type-index GSI allows us to query for all supplier configurations of a given type (e.g. all letter supplier configurations) global_secondary_index { name = "EntityTypeIndex" @@ -45,6 +50,13 @@ resource "aws_dynamodb_table" "supplier-configuration" { projection_type = "ALL" } + global_secondary_index { + name = "packSpecificationId-index" + hash_key = "PK" + range_key = "packSpecificationId" + projection_type = "ALL" + } + point_in_time_recovery { enabled = true } diff --git a/infrastructure/terraform/components/api/module_lambda_supplier_allocator.tf b/infrastructure/terraform/components/api/module_lambda_supplier_allocator.tf index b568307c9..c2013fb6f 100644 --- a/infrastructure/terraform/components/api/module_lambda_supplier_allocator.tf +++ b/infrastructure/terraform/components/api/module_lambda_supplier_allocator.tf @@ -94,8 +94,7 @@ data "aws_iam_policy_document" "supplier_allocator_lambda" { resources = [ aws_dynamodb_table.supplier-configuration.arn, - "${aws_dynamodb_table.supplier-configuration.arn}/index/volumeGroup-index" - + "${aws_dynamodb_table.supplier-configuration.arn}/index/*" ] } } diff --git a/internal/datastore/src/__test__/db.ts b/internal/datastore/src/__test__/db.ts index 9d0bf0e1e..6f50af34d 100644 --- a/internal/datastore/src/__test__/db.ts +++ b/internal/datastore/src/__test__/db.ts @@ -165,11 +165,22 @@ const createSupplierConfigTableCommand = new CreateTableCommand({ ProjectionType: "ALL", }, }, + { + IndexName: "packSpecificationId-index", + KeySchema: [ + { AttributeName: "PK", KeyType: "HASH" }, // Partition key for GSI + { AttributeName: "packSpecificationId", KeyType: "RANGE" }, // Sort key for GSI + ], + Projection: { + ProjectionType: "ALL", + }, + }, ], AttributeDefinitions: [ { AttributeName: "pk", AttributeType: "S" }, { AttributeName: "sk", AttributeType: "S" }, { AttributeName: "volumeGroup", AttributeType: "S" }, + { AttributeName: "packSpecificationId", AttributeType: "S" }, ], }); diff --git a/internal/datastore/src/__test__/supplier-config-repository.test.ts b/internal/datastore/src/__test__/supplier-config-repository.test.ts index ddd44fd4d..b2beea82e 100644 --- a/internal/datastore/src/__test__/supplier-config-repository.test.ts +++ b/internal/datastore/src/__test__/supplier-config-repository.test.ts @@ -263,4 +263,86 @@ describe("SupplierConfigRepository", () => { `Supplier with id ${supplierId} not found`, ); }); + + test("getSupplierPacksForPackSpecification returns correct supplier packs", async () => { + const packSpecId = "pack-spec-123"; + const supplierId = "supplier-123"; + const supplierPackId = "supplier-pack-123"; + + await dbContext.docClient.send( + new PutCommand({ + TableName: dbContext.config.supplierConfigTableName, + Item: { + PK: "SUPPLIER_PACK", + SK: supplierPackId, + id: supplierPackId, + packSpecificationId: packSpecId, + supplierId, + status: "PROD", + approval: "APPROVED", + }, + }), + ); + + const result = + await repository.getSupplierPacksForPackSpecification(packSpecId); + expect(result).toEqual([ + { + approval: "APPROVED", + id: supplierPackId, + packSpecificationId: packSpecId, + supplierId, + status: "PROD", + }, + ]); + }); + + test("getSupplierPacksForPackSpecification returns empty array for non-existent pack specification", async () => { + const packSpecId = "non-existent-pack-spec"; + const result = + await repository.getSupplierPacksForPackSpecification(packSpecId); + expect(result).toEqual([]); + }); + + test("getPackSpecification returns correct pack specification details", async () => { + const packSpecId = "pack-spec-123"; + + await dbContext.docClient.send( + new PutCommand({ + TableName: dbContext.config.supplierConfigTableName, + Item: { + PK: "PACK_SPECIFICATION", + SK: packSpecId, + id: packSpecId, + name: `Pack Specification ${packSpecId}`, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + version: 1, + billingId: `billing-${packSpecId}`, + postage: { id: "postageId", size: "STANDARD" }, + status: "PROD", + }, + }), + ); + + const result = await repository.getPackSpecification(packSpecId); + expect(result).toEqual({ + billingId: `billing-${packSpecId}`, + createdAt: expect.any(String), + id: packSpecId, + name: `Pack Specification ${packSpecId}`, + postage: { id: "postageId", size: "STANDARD" }, + updatedAt: expect.any(String), + version: 1, + status: "PROD", + }); + }); + + test("getPackSpecification throws error for non-existent pack specification", async () => { + const packSpecId = "non-existent-pack-spec"; + + await expect(repository.getPackSpecification(packSpecId)).rejects.toThrow( + `No pack specification found for id ${packSpecId}`, + ); + }); }); diff --git a/internal/datastore/src/supplier-config-repository.ts b/internal/datastore/src/supplier-config-repository.ts index 4eeeddb10..ac6be53b4 100644 --- a/internal/datastore/src/supplier-config-repository.ts +++ b/internal/datastore/src/supplier-config-repository.ts @@ -5,12 +5,16 @@ import { } from "@aws-sdk/lib-dynamodb"; import { $LetterVariant, + $PackSpecification, $Supplier, $SupplierAllocation, + $SupplierPack, $VolumeGroup, LetterVariant, + PackSpecification, Supplier, SupplierAllocation, + SupplierPack, VolumeGroup, } from "@nhsdigital/nhs-notify-event-schemas-supplier-config"; @@ -97,4 +101,39 @@ export class SupplierConfigRepository { } return suppliers; } + + async getSupplierPacksForPackSpecification( + packSpecId: string, + ): Promise { + const result = await this.ddbClient.send( + new QueryCommand({ + TableName: this.config.supplierConfigTableName, + IndexName: "packSpecificationId-index", + KeyConditionExpression: "#pk = :pk AND #packSpecId = :packSpecId", + ExpressionAttributeNames: { + "#pk": "PK", + "#packSpecId": "packSpecificationId", + }, + ExpressionAttributeValues: { + ":pk": "SUPPLIER_PACK", + ":packSpecId": packSpecId, + }, + }), + ); + + return $SupplierPack.array().parse(result.Items); + } + + async getPackSpecification(packSpecId: string): Promise { + const result = await this.ddbClient.send( + new GetCommand({ + TableName: this.config.supplierConfigTableName, + Key: { PK: "PACK_SPECIFICATION", SK: packSpecId }, + }), + ); + if (!result.Item) { + throw new Error(`No pack specification found for id ${packSpecId}`); + } + return $PackSpecification.parse(result.Item); + } } diff --git a/lambdas/supplier-allocator/src/handler/__tests__/allocate-handler.test.ts b/lambdas/supplier-allocator/src/handler/__tests__/allocate-handler.test.ts index eb1a3bfdb..0b7f452f1 100644 --- a/lambdas/supplier-allocator/src/handler/__tests__/allocate-handler.test.ts +++ b/lambdas/supplier-allocator/src/handler/__tests__/allocate-handler.test.ts @@ -1,6 +1,6 @@ +import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs"; import { SQSEvent, SQSRecord } from "aws-lambda"; import pino from "pino"; -import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs"; import { LetterRequestPreparedEventV2 } from "@nhsdigital/nhs-notify-event-schemas-letter-rendering"; import { LetterRequestPreparedEvent } from "@nhsdigital/nhs-notify-event-schemas-letter-rendering-v1"; import { @@ -158,6 +158,17 @@ function setupDefaultMocks() { priority: 1, billingId: "billing-1", }); + (supplierConfig.getPreferredSupplierPacks as jest.Mock).mockResolvedValue([ + { + packSpecificationId: "pack-spec-1", + }, + ]); + (supplierConfig.getPackSpecification as jest.Mock).mockResolvedValue({ + id: "pack-spec-1", + type: "A4", + colour: false, + duplex: false, + }); } describe("createSupplierAllocatorHandler", () => { diff --git a/lambdas/supplier-allocator/src/handler/allocate-handler.ts b/lambdas/supplier-allocator/src/handler/allocate-handler.ts index 2288fae12..ebfede749 100644 --- a/lambdas/supplier-allocator/src/handler/allocate-handler.ts +++ b/lambdas/supplier-allocator/src/handler/allocate-handler.ts @@ -3,8 +3,10 @@ import { SendMessageCommand } from "@aws-sdk/client-sqs"; import { LetterRequestPreparedEvent } from "@nhsdigital/nhs-notify-event-schemas-letter-rendering-v1"; import { LetterVariant, + PackSpecification, Supplier, SupplierAllocation, + SupplierPack, VolumeGroup, } from "@nhsdigital/nhs-notify-event-schemas-supplier-config"; import { LetterRequestPreparedEventV2 } from "@nhsdigital/nhs-notify-event-schemas-letter-rendering"; @@ -12,8 +14,11 @@ import z from "zod"; import { Unit } from "aws-embedded-metrics"; import { MetricEntry, MetricStatus, buildEMFObject } from "@internal/helpers"; import { + getPackSpecification, + getPreferredSupplierPacks, getSupplierAllocationsForVolumeGroup, getSupplierDetails, + getSuppliersWithValidPack, getVariantDetails, getVolumeGroupDetails, } from "../services/supplier-config"; @@ -83,19 +88,44 @@ async function getSupplierFromConfig(letterEvent: PreparedEvents, deps: Deps) { variantDetails.supplierId, ); - const supplierDetails: Supplier[] = await getSupplierDetails( - supplierAllocations, + const supplierIds = supplierAllocations.map((alloc) => alloc.supplier); + + const allocatedSuppliers: Supplier[] = await getSupplierDetails( + supplierIds, deps, ); + + const preferredSupplierPacks: SupplierPack[] = + await getPreferredSupplierPacks( + variantDetails.packSpecificationIds, + allocatedSuppliers, + deps, + ); + + const preferredPack: PackSpecification = await getPackSpecification( + preferredSupplierPacks[0].packSpecificationId, + deps, + ); + + const suppliersForPack: Supplier[] = await getSuppliersWithValidPack( + allocatedSuppliers, + preferredPack.id, + deps, + ); + deps.logger.info({ description: "Fetched supplier details for supplier allocations", variantId: letterEvent.data.letterVariantId, volumeGroupId: volumeGroupDetails.id, supplierAllocationIds: supplierAllocations.map((a) => a.id), - supplierDetails, + allocatedSuppliers, + eligiblePacks: variantDetails.packSpecificationIds, + preferredSupplierPacks, + preferredPack, + suppliersForPack, }); - return supplierDetails; + return allocatedSuppliers; } catch (error) { deps.logger.error({ description: "Error fetching supplier from config", diff --git a/lambdas/supplier-allocator/src/services/__tests__/supplier-config.test.ts b/lambdas/supplier-allocator/src/services/__tests__/supplier-config.test.ts index 7941d1f08..08cee6bea 100644 --- a/lambdas/supplier-allocator/src/services/__tests__/supplier-config.test.ts +++ b/lambdas/supplier-allocator/src/services/__tests__/supplier-config.test.ts @@ -1,6 +1,9 @@ import { + getPackSpecification, + getPreferredSupplierPacks, getSupplierAllocationsForVolumeGroup, getSupplierDetails, + getSuppliersWithValidPack, getVariantDetails, getVolumeGroupDetails, } from "../supplier-config"; @@ -31,7 +34,7 @@ describe("supplier-config service", () => { afterEach(() => jest.resetAllMocks()); describe("getVariantDetails", () => { - it("returns variant details", async () => { + it("returns variant details for valid id", async () => { const variant = { id: "v1", volumeGroupId: "g1" } as any; const deps = makeDeps(); deps.supplierConfigRepo.getLetterVariant = jest @@ -188,10 +191,7 @@ describe("supplier-config service", () => { describe("getSupplierDetails", () => { it("returns supplier details when found", async () => { - const allocations = [ - { supplier: "s1", variantId: "v1" }, - { supplier: "s2", variantId: "v2" }, - ] as any[]; + const supplierIds = ["s1", "s2"]; const suppliers = [ { id: "s1", name: "Supplier 1", status: "PROD" }, { id: "s2", name: "Supplier 2", status: "PROD" }, @@ -201,7 +201,7 @@ describe("supplier-config service", () => { .fn() .mockResolvedValue(suppliers); - const result = await getSupplierDetails(allocations, deps); + const result = await getSupplierDetails(supplierIds, deps); expect(result).toEqual(suppliers); expect(deps.supplierConfigRepo.getSuppliersDetails).toHaveBeenCalledWith([ @@ -211,23 +211,19 @@ describe("supplier-config service", () => { }); it("throws when no supplier details found", async () => { - const allocations = [{ supplier: "s1", variantId: "v1" }] as any[]; + const supplierIds = ["s1"]; const deps = makeDeps(); deps.supplierConfigRepo.getSuppliersDetails = jest .fn() .mockResolvedValue([]); - await expect(getSupplierDetails(allocations, deps)).rejects.toThrow( + await expect(getSupplierDetails(supplierIds, deps)).rejects.toThrow( /No supplier details found/, ); }); it("extracts supplier ids from allocations and requests details", async () => { - const allocations = [ - { supplier: "s1", variantId: "v1" }, - { supplier: "s3", variantId: "v2" }, - { supplier: "s5", variantId: "v3" }, - ] as any[]; + const supplierIds = ["s1", "s3", "s5"]; const suppliers = [ { id: "s1", name: "Supplier 1", status: "PROD" }, { id: "s3", name: "Supplier 3", status: "PROD" }, @@ -238,7 +234,7 @@ describe("supplier-config service", () => { .fn() .mockResolvedValue(suppliers); - await getSupplierDetails(allocations, deps); + await getSupplierDetails(supplierIds, deps); expect(deps.supplierConfigRepo.getSuppliersDetails).toHaveBeenCalledWith([ "s1", @@ -248,11 +244,7 @@ describe("supplier-config service", () => { }); }); it("logs a warning when supplier allocations count differs from supplier details count", async () => { - const allocations = [ - { supplier: "s1", variantId: "v1" }, - { supplier: "s2", variantId: "v2" }, - { supplier: "s3", variantId: "v3" }, - ] as any[]; + const supplierIds = ["s1", "s2", "s3"]; const suppliers = [ { id: "s1", name: "Supplier 1", status: "PROD" }, { id: "s2", name: "Supplier 2", status: "PROD" }, @@ -262,7 +254,7 @@ describe("supplier-config service", () => { .fn() .mockResolvedValue(suppliers); - await getSupplierDetails(allocations, deps); + await getSupplierDetails(supplierIds, deps); expect(deps.logger.warn).toHaveBeenCalledWith({ description: "Mismatch between supplier allocations and supplier details", @@ -273,10 +265,7 @@ describe("supplier-config service", () => { }); it("does not log a warning when counts match", async () => { - const allocations = [ - { supplier: "s1", variantId: "v1" }, - { supplier: "s2", variantId: "v2" }, - ] as any[]; + const supplierIds = ["s1", "s2"]; const suppliers = [ { id: "s1", name: "Supplier 1", status: "PROD" }, { id: "s2", name: "Supplier 2", status: "PROD" }, @@ -286,16 +275,13 @@ describe("supplier-config service", () => { .fn() .mockResolvedValue(suppliers); - await getSupplierDetails(allocations, deps); + await getSupplierDetails(supplierIds, deps); expect(deps.logger.warn).not.toHaveBeenCalled(); }); it("throws when no active suppliers found", async () => { - const allocations = [ - { supplier: "s1", variantId: "v1" }, - { supplier: "s2", variantId: "v2" }, - ] as any[]; + const supplierIds = ["s1", "s2"]; const suppliers = [ { id: "s1", name: "Supplier 1", status: "DRAFT" }, { id: "s2", name: "Supplier 2", status: "DRAFT" }, @@ -305,7 +291,7 @@ describe("supplier-config service", () => { .fn() .mockResolvedValue(suppliers); - await expect(getSupplierDetails(allocations, deps)).rejects.toThrow( + await expect(getSupplierDetails(supplierIds, deps)).rejects.toThrow( /No active suppliers found/, ); expect(deps.logger.error).toHaveBeenCalledWith( @@ -316,11 +302,7 @@ describe("supplier-config service", () => { }); it("filters to return only active suppliers with PROD status", async () => { - const allocations = [ - { supplier: "s1", variantId: "v1" }, - { supplier: "s2", variantId: "v2" }, - { supplier: "s3", variantId: "v3" }, - ] as any[]; + const supplierIds = ["s1", "s2", "s3"]; const suppliers = [ { id: "s1", name: "Supplier 1", status: "PROD" }, { id: "s2", name: "Supplier 2", status: "DRAFT" }, @@ -331,9 +313,143 @@ describe("supplier-config service", () => { .fn() .mockResolvedValue(suppliers); - const result = await getSupplierDetails(allocations, deps); + const result = await getSupplierDetails(supplierIds, deps); expect(result).toEqual([suppliers[0], suppliers[2]]); expect(result.every((s) => s.status === "PROD")).toBe(true); }); + describe("getPreferredSupplierPacks", () => { + it("returns preferred supplier packs when found", async () => { + const suppliers = [ + { id: "s1", name: "Supplier 1", status: "PROD" }, + { id: "s2", name: "Supplier 2", status: "PROD" }, + ] as any[]; + const supplierPacks = [ + { id: "p1", supplierId: "s1", packSpecificationId: "spec1" }, + { id: "p2", supplierId: "s2", packSpecificationId: "spec1" }, + { id: "p3", supplierId: "s3", packSpecificationId: "spec1" }, + ] as any[]; + const deps = makeDeps(); + deps.supplierConfigRepo.getSupplierPacksForPackSpecification = jest + .fn() + .mockResolvedValue(supplierPacks); + + const result = await getPreferredSupplierPacks( + ["spec1"], + suppliers, + deps, + ); + + expect(result).toEqual([ + { id: "p1", supplierId: "s1", packSpecificationId: "spec1" }, + { id: "p2", supplierId: "s2", packSpecificationId: "spec1" }, + ]); + }); + + it("throws when no preferred supplier packs found", async () => { + const suppliers = [ + { id: "s1", name: "Supplier 1", status: "PROD" }, + { id: "s2", name: "Supplier 2", status: "PROD" }, + ] as any[]; + const deps = makeDeps(); + deps.supplierConfigRepo.getSupplierPacksForPackSpecification = jest + .fn() + .mockResolvedValue([]); + + await expect( + getPreferredSupplierPacks(["spec1"], suppliers, deps), + ).rejects.toThrow(/No preferred supplier packs found/); + expect(deps.logger.error).toHaveBeenCalledWith( + expect.objectContaining({ + description: + "No preferred supplier packs found for pack specification ids and suppliers", + }), + ); + }); + it("does not error when at least 1 pack specification has a preferred supplier pack", async () => { + const suppliers = [ + { id: "s1", name: "Supplier 1", status: "PROD" }, + { id: "s2", name: "Supplier 2", status: "PROD" }, + ] as any[]; + const deps = makeDeps(); + deps.supplierConfigRepo.getSupplierPacksForPackSpecification = jest + .fn() + .mockResolvedValueOnce([]) // no packs for spec1 + .mockResolvedValueOnce([ + { id: "p2", supplierId: "s2", packSpecificationId: "spec2" }, + ]); // preferred pack for spec2 + + const result = await getPreferredSupplierPacks( + ["spec1", "spec2"], + suppliers, + deps, + ); + + expect(result).toEqual([ + { id: "p2", supplierId: "s2", packSpecificationId: "spec2" }, + ]); + }); + + it("throws an error when no suppliers match the pack specification", async () => { + const suppliers = [ + { id: "s4", name: "Supplier 1", status: "PROD" }, + { id: "s5", name: "Supplier 2", status: "PROD" }, + ] as any[]; + const supplierPacks = [ + { id: "p1", supplierId: "s1", packSpecificationId: "spec1" }, + { id: "p2", supplierId: "s2", packSpecificationId: "spec1" }, + { id: "p3", supplierId: "s3", packSpecificationId: "spec1" }, + ] as any[]; + const deps = makeDeps(); + deps.supplierConfigRepo.getSupplierPacksForPackSpecification = jest + .fn() + .mockResolvedValue(supplierPacks); + + await expect( + getPreferredSupplierPacks(["spec1"], suppliers, deps), + ).rejects.toThrow(/No preferred supplier packs found/); + expect(deps.logger.error).toHaveBeenCalledWith( + expect.objectContaining({ + description: + "No preferred supplier packs found for pack specification ids and suppliers", + }), + ); + }); + }); + + describe("getPackSpecification", () => { + it("returns pack specification when found", async () => { + const packSpec = { id: "spec1", name: "Pack Spec 1" } as any; + const deps = makeDeps(); + deps.supplierConfigRepo.getPackSpecification = jest + .fn() + .mockResolvedValue(packSpec); + + const result = await getPackSpecification("spec1", deps); + + expect(result).toBe(packSpec); + }); + }); + + describe("getSuppliersWithValidPack", () => { + it("returns suppliers that have the valid pack specification", async () => { + const suppliers = [ + { id: "s1", name: "Supplier 1", status: "PROD" }, + { id: "s2", name: "Supplier 2", status: "PROD" }, + ] as any[]; + const supplierPacks = [ + { id: "p1", supplierId: "s1", packSpecificationId: "spec1" }, + ] as any[]; + const deps = makeDeps(); + deps.supplierConfigRepo.getSupplierPacksForPackSpecification = jest + .fn() + .mockResolvedValue(supplierPacks); + + const result = await getSuppliersWithValidPack(suppliers, "spec1", deps); + + expect(result).toEqual([ + { id: "s1", name: "Supplier 1", status: "PROD" }, + ]); + }); + }); }); diff --git a/lambdas/supplier-allocator/src/services/supplier-config.ts b/lambdas/supplier-allocator/src/services/supplier-config.ts index 9710a68bd..87d750aa4 100644 --- a/lambdas/supplier-allocator/src/services/supplier-config.ts +++ b/lambdas/supplier-allocator/src/services/supplier-config.ts @@ -1,7 +1,9 @@ import { LetterVariant, + PackSpecification, Supplier, SupplierAllocation, + SupplierPack, VolumeGroup, } from "@nhsdigital/nhs-notify-event-schemas-supplier-config"; import { Deps } from "../config/deps"; @@ -75,11 +77,9 @@ export async function getSupplierAllocationsForVolumeGroup( } export async function getSupplierDetails( - supplierAllocations: SupplierAllocation[], + supplierIds: string[], deps: Deps, ): Promise { - const supplierIds = supplierAllocations.map((alloc) => alloc.supplier); - const supplierDetails: Supplier[] = await deps.supplierConfigRepo.getSuppliersDetails(supplierIds); @@ -93,14 +93,14 @@ export async function getSupplierDetails( ); } // Log a warning if some supplier details are missing compared to allocations - if (supplierAllocations.length !== supplierDetails.length) { + if (supplierIds.length !== supplierDetails.length) { const foundSupplierIds = new Set(supplierDetails.map((s) => s.id)); const missingSupplierIds = supplierIds.filter( (id) => !foundSupplierIds.has(id), ); deps.logger.warn({ description: "Mismatch between supplier allocations and supplier details", - allocationsCount: supplierAllocations.length, + allocationsCount: supplierIds.length, detailsCount: supplierDetails.length, missingSuppliers: missingSupplierIds, }); @@ -117,3 +117,66 @@ export async function getSupplierDetails( } return activeSuppliers; } + +export async function getPreferredSupplierPacks( + packSpecificationIds: string[], + suppliers: Supplier[], + deps: Deps, +): Promise { + for (const packSpecId of packSpecificationIds) { + const supplierPacks = + await deps.supplierConfigRepo.getSupplierPacksForPackSpecification( + packSpecId, + ); + if (supplierPacks.length > 0) { + const preferredPacks = supplierPacks.filter((pack) => + suppliers.some((supplier) => supplier.id === pack.supplierId), + ); + if (preferredPacks.length > 0) { + return preferredPacks; + } + } + } + deps.logger.error({ + description: + "No preferred supplier packs found for pack specification ids and suppliers", + packSpecificationIds, + supplierIds: suppliers.map((s) => s.id), + }); + throw new Error( + `No preferred supplier packs found for pack specification ids ${packSpecificationIds.join(", ")} and suppliers ${suppliers.map((s) => s.id).join(", ")}`, + ); +} + +export async function getPackSpecification( + packSpecId: string, + deps: Deps, +): Promise { + const packSpec = + await deps.supplierConfigRepo.getPackSpecification(packSpecId); + return packSpec; +} + +// This function is used to filter the allocated suppliers based on those that support the supplied pack specification +export async function getSuppliersWithValidPack( + suppliers: Supplier[], + packSpecificationId: string, + deps: Deps, +): Promise { + const suppliersWithValidPack: Supplier[] = []; + const supplierPacks = + await deps.supplierConfigRepo.getSupplierPacksForPackSpecification( + packSpecificationId, + ); + + for (const supplier of suppliers) { + const hasValidPack = supplierPacks.some( + (pack) => pack.supplierId === supplier.id, + ); + if (hasValidPack) { + suppliersWithValidPack.push(supplier); + } + } + + return suppliersWithValidPack; +} From e087ae36c6e17862e34f07324a7df589b3a026c0 Mon Sep 17 00:00:00 2001 From: David Wass Date: Fri, 27 Mar 2026 13:22:49 +0000 Subject: [PATCH 6/9] added more validity checks --- .../src/supplier-config-repository.ts | 5 ++++ .../__tests__/supplier-config.test.ts | 29 ++++++++++++++++++- .../src/services/supplier-config.ts | 8 +++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/internal/datastore/src/supplier-config-repository.ts b/internal/datastore/src/supplier-config-repository.ts index ac6be53b4..d7a644b52 100644 --- a/internal/datastore/src/supplier-config-repository.ts +++ b/internal/datastore/src/supplier-config-repository.ts @@ -110,13 +110,18 @@ export class SupplierConfigRepository { TableName: this.config.supplierConfigTableName, IndexName: "packSpecificationId-index", KeyConditionExpression: "#pk = :pk AND #packSpecId = :packSpecId", + FilterExpression: "#status = :status AND #approval = :approval", ExpressionAttributeNames: { "#pk": "PK", "#packSpecId": "packSpecificationId", + "#status": "status", + "#approval": "approval", }, ExpressionAttributeValues: { ":pk": "SUPPLIER_PACK", ":packSpecId": packSpecId, + ":status": "PROD", + ":approval": "APPROVED", }, }), ); diff --git a/lambdas/supplier-allocator/src/services/__tests__/supplier-config.test.ts b/lambdas/supplier-allocator/src/services/__tests__/supplier-config.test.ts index 08cee6bea..f0ba4d4c9 100644 --- a/lambdas/supplier-allocator/src/services/__tests__/supplier-config.test.ts +++ b/lambdas/supplier-allocator/src/services/__tests__/supplier-config.test.ts @@ -419,7 +419,11 @@ describe("supplier-config service", () => { describe("getPackSpecification", () => { it("returns pack specification when found", async () => { - const packSpec = { id: "spec1", name: "Pack Spec 1" } as any; + const packSpec = { + id: "spec1", + name: "Pack Spec 1", + status: "PROD", + } as any; const deps = makeDeps(); deps.supplierConfigRepo.getPackSpecification = jest .fn() @@ -429,6 +433,29 @@ describe("supplier-config service", () => { expect(result).toBe(packSpec); }); + + it("throws when pack specification is not active based on status", async () => { + const packSpec = { + id: "spec2", + name: "Pack Spec 2", + status: "DRAFT", + } as any; + const deps = makeDeps(); + deps.supplierConfigRepo.getPackSpecification = jest + .fn() + .mockResolvedValue(packSpec); + + await expect(getPackSpecification("spec2", deps)).rejects.toThrow( + /not active/, + ); + expect(deps.logger.error).toHaveBeenCalledWith( + expect.objectContaining({ + description: "Pack specification is not active based on status", + packSpecId: "spec2", + status: "DRAFT", + }), + ); + }); }); describe("getSuppliersWithValidPack", () => { diff --git a/lambdas/supplier-allocator/src/services/supplier-config.ts b/lambdas/supplier-allocator/src/services/supplier-config.ts index 87d750aa4..31db9660f 100644 --- a/lambdas/supplier-allocator/src/services/supplier-config.ts +++ b/lambdas/supplier-allocator/src/services/supplier-config.ts @@ -154,6 +154,14 @@ export async function getPackSpecification( ): Promise { const packSpec = await deps.supplierConfigRepo.getPackSpecification(packSpecId); + if (packSpec.status !== "PROD") { + deps.logger.error({ + description: "Pack specification is not active based on status", + packSpecId, + status: packSpec.status, + }); + throw new Error(`Pack specification with id ${packSpecId} is not active`); + } return packSpec; } From 52afe00b81a33a14b9eac0d4de9b08f900003683 Mon Sep 17 00:00:00 2001 From: David Wass Date: Tue, 7 Apr 2026 10:40:45 +0100 Subject: [PATCH 7/9] CCM-13371 - Determine Eligible packs --- lambdas/supplier-allocator/.tool-versions | 1 + .../src/handler/allocate-handler.ts | 16 ++- .../__tests__/supplier-config.test.ts | 126 ++++++++++++++++++ .../src/services/supplier-config.ts | 82 ++++++++++++ 4 files changed, 219 insertions(+), 6 deletions(-) create mode 100644 lambdas/supplier-allocator/.tool-versions diff --git a/lambdas/supplier-allocator/.tool-versions b/lambdas/supplier-allocator/.tool-versions new file mode 100644 index 000000000..a3128f26b --- /dev/null +++ b/lambdas/supplier-allocator/.tool-versions @@ -0,0 +1 @@ +nodejs 22.22.0 diff --git a/lambdas/supplier-allocator/src/handler/allocate-handler.ts b/lambdas/supplier-allocator/src/handler/allocate-handler.ts index ebfede749..7475b9565 100644 --- a/lambdas/supplier-allocator/src/handler/allocate-handler.ts +++ b/lambdas/supplier-allocator/src/handler/allocate-handler.ts @@ -14,6 +14,7 @@ import z from "zod"; import { Unit } from "aws-embedded-metrics"; import { MetricEntry, MetricStatus, buildEMFObject } from "@internal/helpers"; import { + filterPacksForLetter, getPackSpecification, getPreferredSupplierPacks, getSupplierAllocationsForVolumeGroup, @@ -95,12 +96,14 @@ async function getSupplierFromConfig(letterEvent: PreparedEvents, deps: Deps) { deps, ); + const eligiblePacks: string[] = await filterPacksForLetter( + letterEvent, + variantDetails.packSpecificationIds, + deps, + ); + const preferredSupplierPacks: SupplierPack[] = - await getPreferredSupplierPacks( - variantDetails.packSpecificationIds, - allocatedSuppliers, - deps, - ); + await getPreferredSupplierPacks(eligiblePacks, allocatedSuppliers, deps); const preferredPack: PackSpecification = await getPackSpecification( preferredSupplierPacks[0].packSpecificationId, @@ -119,7 +122,8 @@ async function getSupplierFromConfig(letterEvent: PreparedEvents, deps: Deps) { volumeGroupId: volumeGroupDetails.id, supplierAllocationIds: supplierAllocations.map((a) => a.id), allocatedSuppliers, - eligiblePacks: variantDetails.packSpecificationIds, + variantPacks: variantDetails.packSpecificationIds, + eligiblePacks, preferredSupplierPacks, preferredPack, suppliersForPack, diff --git a/lambdas/supplier-allocator/src/services/__tests__/supplier-config.test.ts b/lambdas/supplier-allocator/src/services/__tests__/supplier-config.test.ts index f0ba4d4c9..ac6b468d5 100644 --- a/lambdas/supplier-allocator/src/services/__tests__/supplier-config.test.ts +++ b/lambdas/supplier-allocator/src/services/__tests__/supplier-config.test.ts @@ -1,4 +1,5 @@ import { + filterPacksForLetter, getPackSpecification, getPreferredSupplierPacks, getSupplierAllocationsForVolumeGroup, @@ -479,4 +480,129 @@ describe("supplier-config service", () => { ]); }); }); + + describe("filterPacksForLetter", () => { + it("returns eligible packs for letter", async () => { + const deps = makeDeps(); + deps.supplierConfigRepo.getPackSpecification = jest + .fn() + .mockResolvedValue({ + id: "spec1", + constraints: { + sheets: { operator: "LESS_THAN", value: 2 }, + }, + } as any); + const letterEvent = { + data: { + pageCount: 1, + }, + } as any; + + const result = await filterPacksForLetter(letterEvent, ["spec1"], deps); + + expect(result).toEqual(["spec1"]); + }); + it("throws when no eligible packs found for letter", async () => { + const deps = makeDeps(); + deps.supplierConfigRepo.getPackSpecification = jest + .fn() + .mockResolvedValue({ + id: "spec1", + constraints: { + sheets: { operator: "LESS_THAN", value: 2 }, + }, + } as any); + const letterEvent = { + data: { + pageCount: 3, + }, + } as any; + + await expect( + filterPacksForLetter(letterEvent, ["spec1"], deps), + ).rejects.toThrow( + "No eligible pack specifications found for letter variant id undefined and pack specification ids spec1", + ); + expect(deps.logger.error).toHaveBeenCalledWith( + expect.objectContaining({ + description: "No eligible pack specifications found for letter", + letterVariantId: undefined, + packSpecificationIds: ["spec1"], + }), + ); + }); + it("returns eligible packs for all constraint types", async () => { + const deps = makeDeps(); + const constraints: { operator: string; value: number }[] = [ + { operator: "EQUALS", value: 2 }, + { operator: "NOT_EQUALS", value: 1 }, + { operator: "GREATER_THAN", value: 1 }, + { operator: "LESS_THAN", value: 3 }, + { operator: "GREATER_THAN_OR_EQUAL", value: 2 }, + { operator: "LESS_THAN_OR_EQUAL", value: 2 }, + ]; + const letterEvent = { + data: { + pageCount: 2, + }, + } as any; + + for (const constraint of constraints) { + deps.supplierConfigRepo.getPackSpecification = jest + .fn() + .mockResolvedValue({ + id: "spec1", + constraints: { + sheets: { + operator: constraint.operator, + value: constraint.value, + }, + }, + } as any); + + const result = await filterPacksForLetter(letterEvent, ["spec1"], deps); + + expect(result).toEqual(["spec1"]); + } + }); + it("throws an error for unsupported operator", async () => { + const deps = makeDeps(); + deps.supplierConfigRepo.getPackSpecification = jest + .fn() + .mockResolvedValue({ + id: "spec1", + constraints: { + sheets: { operator: "UNSUPPORTED_OP", value: 2 }, + }, + } as any); + const letterEvent = { + data: { + pageCount: 2, + }, + } as any; + + await expect( + filterPacksForLetter(letterEvent, ["spec1"], deps), + ).rejects.toThrow( + "Unsupported operator UNSUPPORTED_OP in pack specification constraints", + ); + }); + it("returns all packs when no constraints defined", async () => { + const deps = makeDeps(); + deps.supplierConfigRepo.getPackSpecification = jest + .fn() + .mockResolvedValue({ + id: "spec1", + } as any); + const letterEvent = { + data: { + pageCount: 5, + }, + } as any; + + const result = await filterPacksForLetter(letterEvent, ["spec1"], deps); + + expect(result).toEqual(["spec1"]); + }); + }); }); diff --git a/lambdas/supplier-allocator/src/services/supplier-config.ts b/lambdas/supplier-allocator/src/services/supplier-config.ts index 31db9660f..c3d8c2127 100644 --- a/lambdas/supplier-allocator/src/services/supplier-config.ts +++ b/lambdas/supplier-allocator/src/services/supplier-config.ts @@ -6,8 +6,13 @@ import { SupplierPack, VolumeGroup, } from "@nhsdigital/nhs-notify-event-schemas-supplier-config"; +import { LetterRequestPreparedEventV2 } from "@nhsdigital/nhs-notify-event-schemas-letter-rendering"; +import { LetterRequestPreparedEvent } from "@nhsdigital/nhs-notify-event-schemas-letter-rendering-v1"; + import { Deps } from "../config/deps"; +type PreparedEvents = LetterRequestPreparedEventV2 | LetterRequestPreparedEvent; + export async function getVariantDetails( variantId: string, deps: Deps, @@ -188,3 +193,80 @@ export async function getSuppliersWithValidPack( return suppliersWithValidPack; } + +function evaluateContraint( + actualValue: number, + constraintValue: number, + operator: string, +): boolean { + console.log( + `Evaluating constraint: actualValue ${actualValue}, constraintValue ${constraintValue}, operator ${operator}`, + ); + switch (operator) { + case "EQUALS": { + return actualValue === constraintValue; + } + case "NOT_EQUALS": { + return actualValue !== constraintValue; + } + case "GREATER_THAN": { + return actualValue > constraintValue; + } + case "LESS_THAN": { + return actualValue < constraintValue; + } + case "GREATER_THAN_OR_EQUAL": { + return actualValue >= constraintValue; + } + case "LESS_THAN_OR_EQUAL": { + return actualValue <= constraintValue; + } + default: { + throw new Error( + `Unsupported operator ${operator} in pack specification constraints`, + ); + } + } +} + +// This function is used to filter the pack specifications for a letter based on the letter data pages and pack specification constraints sheets + +export async function filterPacksForLetter( + letterEvent: PreparedEvents, + packSpecificationIds: string[], + deps: Deps, +): Promise { + const filteredPackIds: string[] = []; + for (const packSpecId of packSpecificationIds) { + const packSpec = + await deps.supplierConfigRepo.getPackSpecification(packSpecId); + if ( + !packSpec.constraints || + !packSpec.constraints.sheets || + !packSpec.constraints.sheets.value || + !packSpec.constraints.sheets.operator + ) { + filteredPackIds.push(packSpecId); + } else { + const isValid = evaluateContraint( + letterEvent.data.pageCount, + packSpec.constraints.sheets.value, + packSpec.constraints.sheets.operator, + ); + if (isValid) { + filteredPackIds.push(packSpecId); + } + } + } + if (filteredPackIds.length === 0) { + deps.logger.error({ + description: "No eligible pack specifications found for letter", + letterVariantId: letterEvent.data.letterVariantId, + packSpecificationIds, + }); + throw new Error( + `No eligible pack specifications found for letter variant id ${letterEvent.data.letterVariantId} and pack specification ids ${packSpecificationIds.join(", ")}`, + ); + } + return filteredPackIds; +} From a41805a9e24c666214e8789df4ff24e5d78042cb Mon Sep 17 00:00:00 2001 From: David Wass Date: Tue, 7 Apr 2026 11:29:53 +0100 Subject: [PATCH 8/9] debug logging --- .../supplier-allocator/src/services/supplier-config.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lambdas/supplier-allocator/src/services/supplier-config.ts b/lambdas/supplier-allocator/src/services/supplier-config.ts index c3d8c2127..1eaa1fc48 100644 --- a/lambdas/supplier-allocator/src/services/supplier-config.ts +++ b/lambdas/supplier-allocator/src/services/supplier-config.ts @@ -248,6 +248,14 @@ export async function filterPacksForLetter( ) { filteredPackIds.push(packSpecId); } else { + deps.logger.info({ + description: "Evaluating pack specification constraints for letter", + letterVariantId: letterEvent.data.letterVariantId, + packSpecId, + pageCount: letterEvent.data.pageCount, + constraintValue: packSpec.constraints.sheets.value, + constraintOperator: packSpec.constraints.sheets.operator, + }); const isValid = evaluateContraint( letterEvent.data.pageCount, packSpec.constraints.sheets.value, From ef922ba847de5ecc43f4bf641de7c6b289f6b43c Mon Sep 17 00:00:00 2001 From: David Wass Date: Tue, 14 Apr 2026 14:33:34 +0100 Subject: [PATCH 9/9] CCM-13372 - Update pk and sk values --- .../components/api/ddb_table_supplier_configuration.tf | 2 +- internal/datastore/src/__test__/db.ts | 2 +- .../src/__test__/supplier-config-repository.test.ts | 8 ++++---- internal/datastore/src/supplier-config-repository.ts | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/infrastructure/terraform/components/api/ddb_table_supplier_configuration.tf b/infrastructure/terraform/components/api/ddb_table_supplier_configuration.tf index c43e4cb2c..205c3a1b8 100644 --- a/infrastructure/terraform/components/api/ddb_table_supplier_configuration.tf +++ b/infrastructure/terraform/components/api/ddb_table_supplier_configuration.tf @@ -52,7 +52,7 @@ resource "aws_dynamodb_table" "supplier-configuration" { global_secondary_index { name = "packSpecificationId-index" - hash_key = "PK" + hash_key = "pk" range_key = "packSpecificationId" projection_type = "ALL" } diff --git a/internal/datastore/src/__test__/db.ts b/internal/datastore/src/__test__/db.ts index 6f50af34d..de00a6b16 100644 --- a/internal/datastore/src/__test__/db.ts +++ b/internal/datastore/src/__test__/db.ts @@ -168,7 +168,7 @@ const createSupplierConfigTableCommand = new CreateTableCommand({ { IndexName: "packSpecificationId-index", KeySchema: [ - { AttributeName: "PK", KeyType: "HASH" }, // Partition key for GSI + { AttributeName: "pk", KeyType: "HASH" }, // Partition key for GSI { AttributeName: "packSpecificationId", KeyType: "RANGE" }, // Sort key for GSI ], Projection: { diff --git a/internal/datastore/src/__test__/supplier-config-repository.test.ts b/internal/datastore/src/__test__/supplier-config-repository.test.ts index b2beea82e..74bea98c3 100644 --- a/internal/datastore/src/__test__/supplier-config-repository.test.ts +++ b/internal/datastore/src/__test__/supplier-config-repository.test.ts @@ -273,8 +273,8 @@ describe("SupplierConfigRepository", () => { new PutCommand({ TableName: dbContext.config.supplierConfigTableName, Item: { - PK: "SUPPLIER_PACK", - SK: supplierPackId, + pk: "ENTITY#supplier-pack", + sk: `ID#${supplierPackId}`, id: supplierPackId, packSpecificationId: packSpecId, supplierId, @@ -311,8 +311,8 @@ describe("SupplierConfigRepository", () => { new PutCommand({ TableName: dbContext.config.supplierConfigTableName, Item: { - PK: "PACK_SPECIFICATION", - SK: packSpecId, + pk: "ENTITY#pack_specification", + sk: `ID#${packSpecId}`, id: packSpecId, name: `Pack Specification ${packSpecId}`, createdAt: new Date().toISOString(), diff --git a/internal/datastore/src/supplier-config-repository.ts b/internal/datastore/src/supplier-config-repository.ts index d7a644b52..c4648a63c 100644 --- a/internal/datastore/src/supplier-config-repository.ts +++ b/internal/datastore/src/supplier-config-repository.ts @@ -112,13 +112,13 @@ export class SupplierConfigRepository { KeyConditionExpression: "#pk = :pk AND #packSpecId = :packSpecId", FilterExpression: "#status = :status AND #approval = :approval", ExpressionAttributeNames: { - "#pk": "PK", + "#pk": "pk", "#packSpecId": "packSpecificationId", "#status": "status", "#approval": "approval", }, ExpressionAttributeValues: { - ":pk": "SUPPLIER_PACK", + ":pk": "ENTITY#supplier-pack", ":packSpecId": packSpecId, ":status": "PROD", ":approval": "APPROVED", @@ -133,7 +133,7 @@ export class SupplierConfigRepository { const result = await this.ddbClient.send( new GetCommand({ TableName: this.config.supplierConfigTableName, - Key: { PK: "PACK_SPECIFICATION", SK: packSpecId }, + Key: { pk: "ENTITY#pack_specification", sk: `ID#${packSpecId}` }, }), ); if (!result.Item) {