Repository: DEFRA/grants-ui
Pull request #58: Modify DeclarationPageController to make submission for non land based grants
Author: login: Andrew-Folga
Created: 2025-04-30T10:34:42Z | Merged: 2025-05-12T09:11:06Z

For the purpose of this PR I have created a new adding-value grant called `adding-value-v1` (see GAS Swagger for the endpoints https://fg-gas-backend.dev.cdp-int.defra.cloud/documentation#/).

The schema is still being negotiated between GAS and GAE teams but to move the dev forward the schema format was pre assumed in this PR.

Note the answers validation that are sent to GAS is not being hooked up yet as there is no agreement yet as to what should the UX be if the call fails.


Diff:
diff --git a/README.md b/README.md
index 1656a3f5d..61d31440b 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,7 @@ Core delivery platform Node.js Frontend Template.
 - [Local Development](#local-development)
   - [Setup](#setup)
   - [Development](#development)
+  - [GAS Integration](#gas-integration)
   - [Production](#production)
   - [Npm scripts](#npm-scripts)
   - [Update dependencies](#update-dependencies)
@@ -98,6 +99,111 @@ To run the application in `development` mode run:
 npm run dev
 ```
 
+### GAS Integration
+
+The Grants Application Service (GAS) is used to store grant definitions that the app submits data against.
+
+Creating a Grant Definition
+A grant definition is created via the GAS backend by making a POST request to the /grants endpoint (see postman folder in the root of the project). This defines the structure and schema of the grant application payload, which the app will later submit.
+
+Example request:
+
+```
+curl --location --request POST 'https://fg-gas-backend.dev.cdp-int.defra.cloud/grants' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+  "code": "adding-value-v4",
+  "questions": {
+    "$schema": "https://json-schema.org/draft/2020-12/schema",
+    "title": "GrantApplicationPayload",
+    "type": "object",
+    "properties": {
+      "$$__referenceNumber": { "type": "string" },
+      "businessNature": { "type": "string" },
+      "businessLegalStatus": { "type": "string" },
+      "isInEngland": { "type": "boolean" },
+      "planningPermissionStatus": { "type": "string" },
+      "projectStartStatus": { "type": "string" },
+      "isLandBusinessOwned": { "type": "boolean" },
+      "hasFiveYearTenancyAgreement": { "type": "boolean" },
+      "isBuildingSmallerAbattoir": { "type": "boolean" },
+      "isBuildingFruitStorage": { "type": "boolean" },
+      "isProvidingServicesToOtherFarmers": { "type": "boolean" },
+      "eligibleItemsNeeded": {
+        "type": "array",
+        "items": { "type": "string" }
+      },
+      "needsStorageFacilities": { "type": "string" },
+      "estimatedCost": { "type": "number" },
+      "canPayRemainingCosts": { "type": "boolean" },
+      "processedProduceType": { "type": "string" },
+      "valueAdditionMethod": { "type": "string" },
+      "impactType": {
+        "type": "array",
+        "items": { "type": "string" }
+      },
+      "hasMechanisationUsage": { "type": "boolean" },
+      "manualLabourEquivalence": { "type": "string" },
+      "grantApplicantType": { "type": "string" },
+      "agentFirstName": { "type": "string" },
+      "agentLastName": { "type": "string" },
+      "agentBusinessName": { "type": "string" },
+      "agentEmail": { "type": "string", "format": "email" },
+      "agentEmailConfirmation": { "type": "string", "format": "email" },
+      "agentMobile": { "type": "string" },
+      "agentLandline": { "type": "string" },
+      "agentBusinessAddress__addressLine1": { "type": "string" },
+      "agentBusinessAddress__addressLine2": { "type": ["string", "null"] },
+      "agentBusinessAddress__town": { "type": "string" },
+      "agentBusinessAddress__county": { "type": ["string", "null"] },
+      "agentBusinessAddress__postcode": { "type": "string" },
+      "applicantFirstName": { "type": "string" },
+      "applicantLastName": { "type": "string" },
+      "applicantEmail": { "type": "string", "format": "email" },
+      "applicantEmailConfirmation": { "type": "string", "format": "email" },
+      "applicantMobile": { "type": "string" },
+      "applicantLandline": { "type": "string" },
+      "applicantBusinessAddress__addressLine1": { "type": "string" },
+      "applicantBusinessAddress__addressLine2": { "type": ["string", "null"] },
+      "applicantBusinessAddress__town": { "type": "string" }
+      // ... more fields if needed
+    }
+  }
+}'
+```
+
+Example response:
+
+```
+{
+    "code": "adding-value-v4"
+}
+```
+
+#### Using the Grant Definition
+
+Once the grant is created, its code (for example, adding-value-v4) must be added to the relevant configuration file to link it with the frontend flow.
+
+The grant code should be added to:
+
+```
+src/server/common/forms/definitions/adding-value.json
+```
+
+under the following section:
+
+```
+{
+  "metadata": {
+    "gas": {
+      "grantCode": "adding-value-v4"
+    }
+  }
+}
+```
+
+This ensures that when the app submits application data, it targets the correct grant definition in GAS.
+
 ### Production
 
 To mimic the application running in `production` mode locally run:
diff --git a/postman/gas.postman_collection.json b/postman/gas.postman_collection.json
new file mode 100644
index 000000000..ef208e13c
--- /dev/null
+++ b/postman/gas.postman_collection.json
@@ -0,0 +1,55 @@
+{
+  "info": {
+    "_postman_id": "7ce0746b-327f-497d-91bf-8da941a66b81",
+    "name": "gas",
+    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
+  },
+  "item": [
+    {
+      "name": "Submit adding-value data",
+      "request": {
+        "method": "POST",
+        "header": [],
+        "body": {
+          "mode": "raw",
+          "raw": "{\n  \"metadata\": {\n    \"sbi\": \"sbi\",\n    \"frn\": \"frn\",\n    \"crn\": \"crn\",\n    \"defraId\": \"defraId\",\n    \"clientRef\": \"408273ba-3f19-4868-a646-2861df0b742d\",\n    \"submittedAt\": \"2025-04-29T17:12:55.408Z\"\n  },\n  \"answers\": {\n    \"$$__referenceNumber\": \"AV-0A2-B08\",\n    \"natureOfBusinessRadiosField\": \"A grower or producer of agricultural or horticultural produce\",\n    \"legalStatusRadiosField\": \"Sole trader\",\n    \"countryYesNoField\": true,\n    \"planningPermissionRadiosField\": \"Not needed\",\n    \"projectStartRadiosField\": \"Yes, preparatory work\",\n    \"tenancyYesNoField\": true,\n    \"smallerAbattoirYesNoField\": true,\n    \"otherFarmersYesNoField\": true,\n    \"projectItemsCheckboxesField\": [\n      \"Constructing or improving buildings for processing\"\n    ],\n    \"storageRadiosField\": \"Yes, we will need storage facilities\",\n    \"projectCostNumberField\": 123456,\n    \"remainingCostsYesNoField\": true,\n    \"produceProcessedRadiosField\": \"Arable produce\",\n    \"howAddingValueRadiosField\": \"Introducing a new product to your farm\",\n    \"projectImpactCheckboxesField\": [\n      \"Increasing range of added-value products\"\n    ],\n    \"mechanisationYesNoField\": true,\n    \"manualLabourAmountRadiosField\": \"Up to 5% of workforce\",\n    \"applyingRadiosField\": \"Agent\",\n    \"applicantFirstName\": \"fdasfdsa\",\n    \"applicantLastName\": \"hgfhgfhgf\",\n    \"applicantEmailAddress\": \"andrew.folga@equalexperts.com\",\n    \"applicantConfirmEmailAddress\": \"andrew.folga@equalexperts.com\",\n    \"applicantMobileNumber\": \"43432423\",\n    \"applicantLandlineNumber\": \"4354654654\",\n    \"applicantBusinessAddress__addressLine1\": \"Address Line One\",\n    \"applicantBusinessAddress__addressLine2\": null,\n    \"applicantBusinessAddress__town\": \"Town Or City\",\n    \"applicantBusinessAddress__county\": null,\n    \"applicantBusinessAddress__postcode\": \"CR3 5NF\",\n    \"applicantProjectPostcode\": \"CR41NJ\"\n  }\n}",
+          "options": {
+            "raw": {
+              "language": "json"
+            }
+          }
+        },
+        "url": {
+          "raw": "https://fg-gas-backend.dev.cdp-int.defra.cloud/grants/adding-value/applications",
+          "protocol": "https",
+          "host": ["fg-gas-backend", "dev", "cdp-int", "defra", "cloud"],
+          "path": ["grants", "adding-value", "applications"]
+        }
+      },
+      "response": []
+    },
+    {
+      "name": "Create new grant definition",
+      "request": {
+        "method": "POST",
+        "header": [],
+        "body": {
+          "mode": "raw",
+          "raw": "{\n  \"code\": \"adding-value-v3\",\n  \"questions\": {\n    \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n    \"title\": \"GrantApplicationPayload\",\n    \"type\": \"object\",\n    \"properties\": {\n        \"$$__referenceNumber\": { \"type\": \"string\" },\n        \"businessNature\": { \"type\": \"string\" },\n        \"businessLegalStatus\": { \"type\": \"string\" },\n        \"isInEngland\": { \"type\": \"boolean\" },\n        \"planningPermissionStatus\": { \"type\": \"string\" },\n        \"projectStartStatus\": { \"type\": \"string\" },\n        \"isLandBusinessOwned\": { \"type\": \"boolean\" },\n        \"hasFiveYearTenancyAgreement\": { \"type\": \"boolean\" },\n        \"isBuildingSmallerAbattoir\": { \"type\": \"boolean\" },\n        \"isBuildingFruitStorage\": { \"type\": \"boolean\" },\n        \"isProvidingServicesToOtherFarmers\": { \"type\": \"boolean\" },\n        \"eligibleItemsNeeded\": {\n        \"type\": \"array\",\n        \"items\": { \"type\": \"string\" }\n        },\n        \"needsStorageFacilities\": { \"type\": \"string\" },\n        \"estimatedCost\": { \"type\": \"number\" },\n        \"canPayRemainingCosts\": { \"type\": \"boolean\" },\n        \"processedProduceType\": { \"type\": \"string\" },\n        \"valueAdditionMethod\": { \"type\": \"string\" },\n        \"impactType\": {\n        \"type\": \"array\",\n        \"items\": { \"type\": \"string\" }\n        },\n        \"hasMechanisationUsage\": { \"type\": \"boolean\" },\n        \"manualLabourEquivalence\": { \"type\": \"string\" },\n        \"grantApplicantType\": { \"type\": \"string\" },\n        \"agentFirstName\": { \"type\": \"string\" },\n        \"agentLastName\": { \"type\": \"string\" },\n        \"agentBusinessName\": { \"type\": \"string\" },\n        \"agentEmail\": { \"type\": \"string\", \"format\": \"email\" },\n        \"agentEmailConfirmation\": { \"type\": \"string\", \"format\": \"email\" },\n        \"agentMobile\": { \"type\": \"string\" },\n        \"agentLandline\": { \"type\": \"string\" },\n        \"agentBusinessAddress__addressLine1\": { \"type\": \"string\" },\n        \"agentBusinessAddress__addressLine2\": {\n        \"type\": [\"string\", \"null\"]\n        },\n        \"agentBusinessAddress__town\": { \"type\": \"string\" },\n        \"agentBusinessAddress__county\": { \"type\": [\"string\", \"null\"] },\n        \"agentBusinessAddress__postcode\": { \"type\": \"string\" },\n        \"applicantFirstName\": { \"type\": \"string\" },\n        \"applicantLastName\": { \"type\": \"string\" },\n        \"applicantEmail\": { \"type\": \"string\", \"format\": \"email\" },\n        \"applicantEmailConfirmation\": {\n        \"type\": \"string\",\n        \"format\": \"email\"\n        },\n        \"applicantMobile\": { \"type\": \"string\" },\n        \"applicantLandline\": { \"type\": \"string\" },\n        \"applicantBusinessAddress__addressLine1\": { \"type\": \"string\" },\n        \"applicantBusinessAddress__addressLine2\": {\n        \"type\": [\"string\", \"null\"]\n        },\n        \"applicantBusinessAddress__town\": { \"type\": \"string\" },\n        \"applicantBusinessAddress__county\": { \"type\": [\"string\", \"null\"] },\n        \"applicantBusinessAddress__postcode\": { \"type\": \"string\" },\n        \"applicantProjectPostcode\": { \"type\": \"string\" }\n    },\n    \"required\": [\n        \"$$__referenceNumber\",\n        \"businessNature\",\n        \"businessLegalStatus\",\n        \"isInEngland\",\n        \"planningPermissionStatus\",\n        \"projectStartStatus\",\n        \"isLandBusinessOwned\",\n        \"isBuildingSmallerAbattoir\",\n        \"estimatedCost\",\n        \"canPayRemainingCosts\",\n        \"hasMechanisationUsage\",\n        \"grantApplicantType\",\n        \"applicantFirstName\",\n        \"applicantLastName\",\n        \"applicantEmail\",\n        \"applicantEmailConfirmation\",\n        \"applicantMobile\",\n        \"applicantLandline\",\n        \"applicantBusinessAddress__addressLine1\",\n        \"applicantBusinessAddress__town\",\n        \"applicantBusinessAddress__postcode\",\n        \"applicantProjectPostcode\"\n    ]\n  },\n  \"metadata\": {\n    \"description\": \"Adding Value\",\n    \"startDate\": \"2025-05-09T13:08:27.125Z\"\n  },\n  \"actions\": [\n      {\n        \"name\": \"calculate-score\",\n        \"method\": \"POST\",\n        \"url\": \"https://ffc-grants-scoring.dev.cdp-int.defra.cloud/scoring/api/v1/adding-value/score?allowPartialScoring=true\"\n      }\n  ]\n}",
+          "options": {
+            "raw": {
+              "language": "json"
+            }
+          }
+        },
+        "url": {
+          "raw": "https://fg-gas-backend.dev.cdp-int.defra.cloud/grants",
+          "protocol": "https",
+          "host": ["fg-gas-backend", "dev", "cdp-int", "defra", "cloud"],
+          "path": ["grants"]
+        }
+      },
+      "response": []
+    }
+  ]
+}
diff --git a/src/server/common/forms/definitions/adding-value.json b/src/server/common/forms/definitions/adding-value.json
index a78bd3e9f..10025fe60 100644
--- a/src/server/common/forms/definitions/adding-value.json
+++ b/src/server/common/forms/definitions/adding-value.json
@@ -2,7 +2,99 @@
   "engine": "V2",
   "name": "Adding value",
   "metadata": {
-    "referenceNumberPrefix": "AV"
+    "referenceNumberPrefix": "AV",
+    "gas": {
+      "grantCode": "adding-value-v3",
+      "submissionSchema": {
+        "$schema": "https://json-schema.org/draft/2020-12/schema",
+        "title": "GrantApplicationPayload",
+        "type": "object",
+        "properties": {
+          "$$__referenceNumber": { "type": "string" },
+          "businessNature": { "type": "string" },
+          "businessLegalStatus": { "type": "string" },
+          "isInEngland": { "type": "boolean" },
+          "planningPermissionStatus": { "type": "string" },
+          "projectStartStatus": { "type": "string" },
+          "isLandBusinessOwned": { "type": "boolean" },
+          "hasFiveYearTenancyAgreement": { "type": "boolean" },
+          "isBuildingSmallerAbattoir": { "type": "boolean" },
+          "isBuildingFruitStorage": { "type": "boolean" },
+          "isProvidingServicesToOtherFarmers": { "type": "boolean" },
+          "eligibleItemsNeeded": {
+            "type": "array",
+            "items": { "type": "string" }
+          },
+          "needsStorageFacilities": { "type": "string" },
+          "estimatedCost": { "type": "number" },
+          "canPayRemainingCosts": { "type": "boolean" },
+          "processedProduceType": { "type": "string" },
+          "valueAdditionMethod": { "type": "string" },
+          "impactType": {
+            "type": "array",
+            "items": { "type": "string" }
+          },
+          "hasMechanisationUsage": { "type": "boolean" },
+          "manualLabourEquivalence": { "type": "string" },
+          "grantApplicantType": { "type": "string" },
+          "agentFirstName": { "type": "string" },
+          "agentLastName": { "type": "string" },
+          "agentBusinessName": { "type": "string" },
+          "agentEmail": { "type": "string", "format": "email" },
+          "agentEmailConfirmation": { "type": "string", "format": "email" },
+          "agentMobile": { "type": "string" },
+          "agentLandline": { "type": "string" },
+          "agentBusinessAddress__addressLine1": { "type": "string" },
+          "agentBusinessAddress__addressLine2": {
+            "type": ["string", "null"]
+          },
+          "agentBusinessAddress__town": { "type": "string" },
+          "agentBusinessAddress__county": { "type": ["string", "null"] },
+          "agentBusinessAddress__postcode": { "type": "string" },
+          "applicantFirstName": { "type": "string" },
+          "applicantLastName": { "type": "string" },
+          "applicantEmail": { "type": "string", "format": "email" },
+          "applicantEmailConfirmation": {
+            "type": "string",
+            "format": "email"
+          },
+          "applicantMobile": { "type": "string" },
+          "applicantLandline": { "type": "string" },
+          "applicantBusinessAddress__addressLine1": { "type": "string" },
+          "applicantBusinessAddress__addressLine2": {
+            "type": ["string", "null"]
+          },
+          "applicantBusinessAddress__town": { "type": "string" },
+          "applicantBusinessAddress__county": { "type": ["string", "null"] },
+          "applicantBusinessAddress__postcode": { "type": "string" },
+          "applicantProjectPostcode": { "type": "string" }
+        },
+        "required": [
+          "$$__referenceNumber",
+          "businessNature",
+          "businessLegalStatus",
+          "isInEngland",
+          "planningPermissionStatus",
+          "projectStartStatus",
+          "isLandBusinessOwned",
+          "isBuildingSmallerAbattoir",
+          "estimatedCost",
+          "canPayRemainingCosts",
+          "hasMechanisationUsage",
+          "grantApplicantType",
+          "applicantFirstName",
+          "applicantLastName",
+          "applicantEmail",
+          "applicantEmailConfirmation",
+          "applicantMobile",
+          "applicantLandline",
+          "applicantBusinessAddress__addressLine1",
+          "applicantBusinessAddress__town",
+          "applicantBusinessAddress__postcode",
+          "applicantProjectPostcode"
+        ]
+      }
+    }
   },
   "pages": [
     {
@@ -35,7 +127,7 @@
       "path": "/nature-of-business",
       "components": [
         {
-          "name": "natureOfBusinessRadiosField",
+          "name": "businessNature",
           "title": "What is your business?",
           "type": "RadiosField",
           "list": "natureOfBusinessList",
@@ -67,7 +159,7 @@
       "path": "/legal-status",
       "components": [
         {
-          "name": "legalStatusRadiosField",
+          "name": "businessLegalStatus",
           "title": "What is the legal status of the business?",
           "type": "RadiosField",
           "list": "legalStatusList",
@@ -104,7 +196,7 @@
       "path": "/country",
       "components": [
         {
-          "name": "countryYesNoField",
+          "name": "isInEngland",
           "title": "Is the planned project in England?",
           "hint": "The site where the work will happen",
           "type": "YesNoField",
@@ -135,7 +227,7 @@
       "path": "/planning-permission",
       "components": [
         {
-          "name": "planningPermissionRadiosField",
+          "name": "planningPermissionStatus",
           "title": "Does the project have planning permission?",
           "type": "RadiosField",
           "list": "planningPermissionList",
@@ -185,7 +277,7 @@
       "path": "/project-start",
       "components": [
         {
-          "name": "projectStartRadiosField",
+          "name": "projectStartStatus",
           "title": "Have you already started work on the project?",
           "type": "RadiosField",
           "list": "projectStartList",
@@ -216,7 +308,7 @@
       "path": "/tenancy",
       "components": [
         {
-          "name": "tenancyYesNoField",
+          "name": "isLandBusinessOwned",
           "title": "Is the planned project on land the business owns?",
           "type": "YesNoField",
           "options": {
@@ -232,7 +324,7 @@
       "path": "/tenancy-length",
       "components": [
         {
-          "name": "tenancyLengthYesNoField",
+          "name": "hasFiveYearTenancyAgreement",
           "title": "Do you have a tenancy agreement for 5 years after the final grant payment?",
           "type": "YesNoField",
           "options": {
@@ -262,7 +354,7 @@
       "path": "/smaller-abattoir",
       "components": [
         {
-          "name": "smallerAbattoirYesNoField",
+          "name": "isBuildingSmallerAbattoir",
           "title": "Do you want to build a new smaller abattoir?",
           "type": "YesNoField",
           "hint": "A smaller abattoir is a red meat abattoir that processes up to 10,000 livestock units each year or a poultry abattoir that slaughters up to 500,000 birds each year.",
@@ -279,7 +371,7 @@
       "path": "/other-farmers",
       "components": [
         {
-          "name": "otherFarmersYesNoField",
+          "name": "isProvidingServicesToOtherFarmers",
           "title": "Will this abattoir provide services to other farmers?",
           "hint": "For example, farmers pay you to slaughter their livestock.",
           "type": "YesNoField",
@@ -311,7 +403,7 @@
       "path": "/fruit-storage",
       "components": [
         {
-          "name": "fruitStorageYesNoField",
+          "name": "isBuildingFruitStorage",
           "title": "Do you want to build new controlled atmosphere storage for top fruit?",
           "hint": "Fruit that grows on trees, for example apples, pears, quinces, medlars, plums, peaches, apricots and cherries",
           "type": "YesNoField",
@@ -329,7 +421,7 @@
       "path": "/project-items",
       "components": [
         {
-          "name": "projectItemsCheckboxesField",
+          "name": "eligibleItemsNeeded",
           "title": "What eligible items does your project need?",
           "hint": "Storage facilities will only be funded as part of a bigger project and cannot be more than 50% of the total grant funding.\n\nSelect all the items your project needs",
           "type": "CheckboxesField",
@@ -362,7 +454,7 @@
       "path": "/storage",
       "components": [
         {
-          "name": "storageRadiosField",
+          "name": "needsStorageFacilities",
           "title": "Does your project also need storage facilities?",
           "hint": "For example, cold stores or controlled atmosphere storage",
           "type": "RadiosField",
@@ -393,7 +485,7 @@
           "content": "<p class=\"govuk-hint\">Do not include VAT<br><br>Enter cost of items, for example {{ 695000 | formatCurrency: 'en-GB', 'GBP', 0, 'decimal' }}</p>"
         },
         {
-          "name": "projectCostNumberField",
+          "name": "estimatedCost",
           "type": "NumberField",
           "title": "Enter amount",
           "options": {
@@ -436,7 +528,7 @@
           "name": "potentialFundingInfo",
           "title": "Html",
           "type": "Html",
-          "content": "<p class=\"govuk-body\"> {% assign estimatedCost = 'projectCostNumberField' | answer %} {% assign eligibilityMultiplier = 0.4 %} {% assign eligibilityAmount = estimatedCost | times: eligibilityMultiplier %} {% if eligibilityAmount <= 300000 and eligibilityAmount >= 25000 %} You may be able to apply for grant funding of up to {{ eligibilityAmount | formatCurrency: 'en-GB', 'GBP', 0 }} (40% of {{ estimatedCost | formatCurrency: 'en-GB', 'GBP', 0 }}).{% elsif eligibilityAmount > 300000 %} You may be able to apply for grant funding of up to £300,000, based on the estimated cost of {{ estimatedCost | formatCurrency: 'en-GB', 'GBP', 0 }}.\n\n\n<div class=\"govuk-inset-text\">The maximum grant you can apply for is £300,000.</div> {% endif %}</p>\n",
+          "content": "<p class=\"govuk-body\"> {% assign estimatedCost = 'estimatedCost' | answer %} {% assign eligibilityMultiplier = 0.4 %} {% assign eligibilityAmount = estimatedCost | times: eligibilityMultiplier %} {% if eligibilityAmount <= 300000 and eligibilityAmount >= 25000 %} You may be able to apply for grant funding of up to {{ eligibilityAmount | formatCurrency: 'en-GB', 'GBP', 0 }} (40% of {{ estimatedCost | formatCurrency: 'en-GB', 'GBP', 0 }}).{% elsif eligibilityAmount > 300000 %} You may be able to apply for grant funding of up to £300,000, based on the estimated cost of {{ estimatedCost | formatCurrency: 'en-GB', 'GBP', 0 }}.\n\n\n<div class=\"govuk-inset-text\">The maximum grant you can apply for is £300,000.</div> {% endif %}</p>\n",
           "options": {},
           "schema": {}
         },
@@ -451,12 +543,12 @@
       ]
     },
     {
-      "title": "{% assign estimatedCost = 'projectCostNumberField' | answer %} {% assign applicantPayMultiplier = 0.6 %} {% assign applicantPayAmount = estimatedCost | times: applicantPayMultiplier %} {% assign grantPayAmount = estimatedCost | minus: applicantPayAmount %} {% if grantPayAmount >= 300000 %} {% assign applicantPayAmount = estimatedCost | minus: 300000 %} {% endif %} Can you pay the remaining costs of {{ applicantPayAmount | formatCurrency: 'en-GB', 'GBP', 0 }}?",
+      "title": "{% assign estimatedCost = 'estimatedCost' | answer %} {% assign applicantPayMultiplier = 0.6 %} {% assign applicantPayAmount = estimatedCost | times: applicantPayMultiplier %} {% assign grantPayAmount = estimatedCost | minus: applicantPayAmount %} {% if grantPayAmount >= 300000 %} {% assign applicantPayAmount = estimatedCost | minus: 300000 %} {% endif %} Can you pay the remaining costs of {{ applicantPayAmount | formatCurrency: 'en-GB', 'GBP', 0 }}?",
       "path": "/remaining-costs",
       "components": [
         {
           "type": "YesNoField",
-          "name": "remainingCostsYesNoField",
+          "name": "canPayRemainingCosts",
           "title": "Can you pay the remaining costs?",
           "options": {
             "customValidationMessages": {
@@ -485,7 +577,7 @@
       "path": "/produce-processed",
       "components": [
         {
-          "name": "produceProcessedRadiosField",
+          "name": "processedProduceType",
           "title": "What type of produce is being processed?",
           "type": "RadiosField",
           "list": "produceProcessedList",
@@ -504,7 +596,7 @@
       "path": "/how-adding-value",
       "components": [
         {
-          "name": "howAddingValueRadiosField",
+          "name": "valueAdditionMethod",
           "title": "How will this project add value to the produce?",
           "type": "RadiosField",
           "list": "howAddingValueList",
@@ -523,7 +615,7 @@
       "path": "/project-impact",
       "components": [
         {
-          "name": "projectImpactCheckboxesField",
+          "name": "impactType",
           "title": "What impact will the project have?",
           "type": "CheckboxesField",
           "list": "projectImpactList",
@@ -542,7 +634,7 @@
       "path": "/mechanisation",
       "components": [
         {
-          "name": "mechanisationYesNoField",
+          "name": "hasMechanisationUsage",
           "title": "Will this project use any mechanisation instead of manual labour?",
           "hint": "For example, a fruit grading and sorting machine that does the work of 2 farm labourers",
           "type": "YesNoField",
@@ -559,7 +651,7 @@
       "path": "/manual-labour-amount",
       "components": [
         {
-          "name": "manualLabourAmountRadiosField",
+          "name": "manualLabourEquivalence",
           "title": "How much manual labour will the mechanisation be equal to?",
           "type": "RadiosField",
           "list": "manualLabourAmountList",
@@ -608,7 +700,7 @@
       "path": "/applying",
       "components": [
         {
-          "name": "applyingRadiosField",
+          "name": "grantApplicantType",
           "title": "Who is applying for this grant?",
           "type": "RadiosField",
           "list": "applyingList",
@@ -675,7 +767,7 @@
         },
         {
           "type": "EmailAddressField",
-          "name": "agentEmailAddress",
+          "name": "agentEmail",
           "title": "Email address",
           "hint": "We will only use this to send you a confirmation",
           "options": {
@@ -691,7 +783,7 @@
         },
         {
           "type": "EmailAddressField",
-          "name": "agentConfirmEmailAddress",
+          "name": "agentEmailConfirmation",
           "title": "Confirm email address",
           "options": {
             "required": true,
@@ -706,7 +798,7 @@
         },
         {
           "type": "TelephoneNumberField",
-          "name": "agentMobileNumber",
+          "name": "agentMobile",
           "title": "Mobile number",
           "hint": "We will only use this to contact you about your application",
           "options": {
@@ -722,7 +814,7 @@
         },
         {
           "type": "TelephoneNumberField",
-          "name": "agentLandlineNumber",
+          "name": "agentLandline",
           "title": "Landline number",
           "hint": "We will only use this to contact you about your application",
           "options": {
@@ -790,7 +882,7 @@
         },
         {
           "type": "EmailAddressField",
-          "name": "applicantEmailAddress",
+          "name": "applicantEmail",
           "title": "Email address",
           "hint": "We will only use this to send you a confirmation",
           "options": {
@@ -806,7 +898,7 @@
         },
         {
           "type": "EmailAddressField",
-          "name": "applicantConfirmEmailAddress",
+          "name": "applicantEmailConfirmation",
           "title": "Confirm email address",
           "options": {
             "required": true,
@@ -821,7 +913,7 @@
         },
         {
           "type": "TelephoneNumberField",
-          "name": "applicantMobileNumber",
+          "name": "applicantMobile",
           "title": "Mobile number",
           "hint": "We will only use this to contact you about your application",
           "options": {
@@ -838,7 +930,7 @@
         },
         {
           "type": "TelephoneNumberField",
-          "name": "applicantLandlineNumber",
+          "name": "applicantLandline",
           "title": "Landline number",
           "hint": "We will only use this to contact you about your application",
           "options": {
@@ -1000,7 +1092,7 @@
         {
           "text": "Packing produce",
           "description": "For example, packing top fruit, bagging vegetables, bottling wine.",
-          "value": "howAddingValuee-A3"
+          "value": "howAddingValue-A3"
         },
         {
           "text": "A new retail facility to sell direct to consumers",
@@ -1289,7 +1381,7 @@
         "conditions": [
           {
             "field": {
-              "name": "planningPermissionRadiosField",
+              "name": "planningPermissionStatus",
               "type": "RadiosField",
               "display": "Does the project have planning permission?"
             },
@@ -1311,7 +1403,7 @@
         "conditions": [
           {
             "field": {
-              "name": "planningPermissionRadiosField",
+              "name": "planningPermissionStatus",
               "type": "RadiosField",
               "display": "Does the project have planning permission?"
             },
@@ -1333,7 +1425,7 @@
         "conditions": [
           {
             "field": {
-              "name": "projectStartRadiosField",
+              "name": "projectStartStatus",
               "type": "RadiosField",
               "display": "Have you already started work on the project?"
             },
@@ -1355,7 +1447,7 @@
         "conditions": [
           {
             "field": {
-              "name": "tenancyYesNoField",
+              "name": "isLandBusinessOwned",
               "type": "YesNoField",
               "display": "Is the planned project on land the business owns?"
             },
@@ -1377,7 +1469,7 @@
         "conditions": [
           {
             "field": {
-              "name": "tenancyLengthYesNoField",
+              "name": "hasFiveYearTenancyAgreement",
               "type": "YesNoField",
               "display": "Do you have a tenancy agreement for 5 years after the final grant payment?"
             },
@@ -1399,7 +1491,7 @@
         "conditions": [
           {
             "field": {
-              "name": "smallerAbattoirYesNoField",
+              "name": "isBuildingSmallerAbattoir",
               "type": "YesNoField",
               "display": "Do you want to build a new smaller abattoir?"
             },
@@ -1421,7 +1513,7 @@
         "conditions": [
           {
             "field": {
-              "name": "smallerAbattoirYesNoField",
+              "name": "isBuildingSmallerAbattoir",
               "type": "YesNoField",
               "display": "Do you want to build a new smaller abattoir?"
             },
@@ -1443,7 +1535,7 @@
         "conditions": [
           {
             "field": {
-              "name": "otherFarmersYesNoField",
+              "name": "isProvidingServicesToOtherFarmers",
               "type": "YesNoField",
               "display": "Will this abattoir provide services to other farmers?"
             },
@@ -1465,7 +1557,7 @@
         "conditions": [
           {
             "field": {
-              "name": "otherFarmersYesNoField",
+              "name": "isProvidingServicesToOtherFarmers",
               "type": "YesNoField",
               "display": "Will this abattoir provide services to other farmers?"
             },
@@ -1487,7 +1579,7 @@
         "conditions": [
           {
             "field": {
-              "name": "fruitStorageYesNoField",
+              "name": "isBuildingFruitStorage",
               "type": "YesNoField",
               "display": "Do you want to build new controlled atmosphere storage for top fruit?"
             },
@@ -1527,7 +1619,7 @@
         "conditions": [
           {
             "field": {
-              "name": "projectItemsCheckboxesField",
+              "name": "eligibleItemsNeeded",
               "type": "CheckboxesField",
               "display": "What eligible items does your project need?"
             },
@@ -1549,7 +1641,7 @@
         "conditions": [
           {
             "field": {
-              "name": "projectCostNumberField",
+              "name": "estimatedCost",
               "type": "NumberField",
               "display": "What is the estimated cost of the items?"
             },
@@ -1571,7 +1663,7 @@
         "conditions": [
           {
             "field": {
-              "name": "remainingCostsYesNoField",
+              "name": "canPayRemainingCosts",
               "type": "YesNoField",
               "display": "Can you pay the remaining costs of £x?"
             },
@@ -1593,7 +1685,7 @@
         "conditions": [
           {
             "field": {
-              "name": "mechanisationYesNoField",
+              "name": "hasMechanisationUsage",
               "type": "YesNoField",
               "display": "Will this project use any mechanisation instead of manual labour?"
             },
@@ -1615,7 +1707,7 @@
         "conditions": [
           {
             "field": {
-              "name": "applyingRadiosField",
+              "name": "grantApplicantType",
               "type": "RadiosField",
               "display": "Who is applying for this grant?"
             },
diff --git a/src/server/common/forms/services/submission.js b/src/server/common/forms/services/submission.js
deleted file mode 100644
index 41cb2ca31..000000000
--- a/src/server/common/forms/services/submission.js
+++ /dev/null
@@ -1,28 +0,0 @@
-export const formSubmissionService = {
-  submit: async function (formData, state) {
-    // Get the reference number from the state
-    const referenceNumber = state?.referenceNumber
-
-    // Create a summary of the form data (excluding sensitive fields)
-    const formDataSummary =
-      Object.keys(formData || {}).length > 0
-        ? {
-            fieldsSubmitted: Object.keys(formData).length,
-            timestamp: new Date().toISOString()
-          }
-        : undefined
-
-    const result = {
-      referenceNumber
-    }
-
-    if (formDataSummary) {
-      result.submissionDetails = formDataSummary
-    }
-
-    return Promise.resolve({
-      message: 'Form submitted successfully',
-      result
-    })
-  }
-}
diff --git a/src/server/common/forms/services/submission.test.js b/src/server/common/forms/services/submission.test.js
deleted file mode 100644
index f1fcd119e..000000000
--- a/src/server/common/forms/services/submission.test.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import { formSubmissionService } from './submission.js'
-
-describe('formSubmissionService', () => {
-  test('submit resolves with expected structure when formData is empty', async () => {
-    const state = {
-      referenceNumber: 'REF123'
-    }
-    await expect(formSubmissionService.submit({}, state)).resolves.toEqual({
-      message: 'Form submitted successfully',
-      result: {
-        referenceNumber: 'REF123'
-      }
-    })
-  })
-
-  test('submit includes submissionDetails when formData has fields', async () => {
-    const state = {
-      referenceNumber: 'REF123'
-    }
-    const formData = {
-      field1: 'value1',
-      field2: 'value2'
-    }
-    const result = await formSubmissionService.submit(formData, state)
-
-    expect(result.message).toBe('Form submitted successfully')
-    expect(result.result.referenceNumber).toBe('REF123')
-    expect(result.result.submissionDetails).toBeDefined()
-    expect(result.result.submissionDetails.fieldsSubmitted).toBe(2)
-    expect(result.result.submissionDetails.timestamp).toBeDefined()
-  })
-
-  test('submit handles null formData', async () => {
-    const state = {
-      referenceNumber: 'REF123'
-    }
-    await expect(formSubmissionService.submit(null, state)).resolves.toEqual({
-      message: 'Form submitted successfully',
-      result: {
-        referenceNumber: 'REF123'
-      }
-    })
-  })
-})
diff --git a/src/server/controllers/declaration/controller.js b/src/server/controllers/declaration/controller.js
index accbb49cd..fc1d9edee 100644
--- a/src/server/controllers/declaration/controller.js
+++ b/src/server/controllers/declaration/controller.js
@@ -1,10 +1,12 @@
 import { SummaryPageController } from '@defra/forms-engine-plugin/controllers/SummaryPageController.js'
-import { formSubmissionService } from '~/src/server/common/forms/services/submission.js'
 import { getFormsCacheService } from '~/src/server/common/helpers/forms-cache/forms-cache.js'
 import {
   storeSlugInContext,
   getConfirmationPath
 } from '~/src/server/common/helpers/form-slug-helper.js'
+import { submitGrantApplication } from '~/src/server/common/services/grant-application.service.js'
+import { transformStateObjectToGasApplication } from '../../common/helpers/grant-application-service/state-to-gas-payload-mapper.js'
+import { transformAnswerKeysToText } from './state-to-gas-answers-mapper.js'
 
 export default class DeclarationPageController extends SummaryPageController {
   /**
@@ -14,6 +16,7 @@ export default class DeclarationPageController extends SummaryPageController {
   constructor(model, pageDef) {
     super(model, pageDef)
     this.viewName = 'declaration-page'
+    this.grantCode = model.def.metadata.gas.grantCode
   }
 
   /**
@@ -61,24 +64,45 @@ export default class DeclarationPageController extends SummaryPageController {
           request.path
         )
 
-        const { result } = await formSubmissionService.submit(
-          request.payload,
-          context.state
+        const identifiers = {
+          clientRef: context.referenceNumber?.toLowerCase(),
+          sbi: 'sbi',
+          frn: 'frn',
+          crn: 'crn',
+          defraId: 'defraId'
+        }
+
+        const stateWithTextAnswers = transformAnswerKeysToText(
+          context.state,
+          this.model.componentDefMap,
+          this.model.listDefMap
+        )
+
+        const applicationData = transformStateObjectToGasApplication(
+          identifiers,
+          stateWithTextAnswers,
+          (state) => state
+        )
+
+        const result = await submitGrantApplication(
+          this.grantCode,
+          applicationData
         )
 
-        context.referenceNumber = result.referenceNumber
         request.logger.debug(
           'DeclarationController: Got reference number:',
-          context.referenceNumber
+          result.clientRef
         )
 
-        // Log submission details if available - this is not needed for the submission but it's useful for debugging
-        if (result.submissionDetails) {
+        // Log submission details if available
+        if (result.clientRef) {
           request.logger.info({
             message: 'Form submission completed',
-            referenceNumber: result.referenceNumber,
-            fieldsSubmitted: result.submissionDetails.fieldsSubmitted,
-            timestamp: result.submissionDetails.timestamp
+            referenceNumber: result.clientRef,
+            numberOfSubmittedFields: context.state
+              ? Object.keys(context.state).length
+              : 0,
+            timestamp: new Date().toISOString()
           })
         }
 
diff --git a/src/server/controllers/declaration/controller.test.js b/src/server/controllers/declaration/controller.test.js
index 6157955b8..476c53d6a 100644
--- a/src/server/controllers/declaration/controller.test.js
+++ b/src/server/controllers/declaration/controller.test.js
@@ -1,7 +1,9 @@
 import { SummaryPageController } from '@defra/forms-engine-plugin/controllers/SummaryPageController.js'
-import { formSubmissionService } from '~/src/server/common/forms/services/submission.js'
 import DeclarationPageController from './controller.js'
 import * as formSlugHelper from '~/src/server/common/helpers/form-slug-helper.js'
+import { submitGrantApplication } from '~/src/server/common/services/grant-application.service.js'
+import { transformStateObjectToGasApplication } from '../../common/helpers/grant-application-service/state-to-gas-payload-mapper.js'
+import { transformAnswerKeysToText } from './state-to-gas-answers-mapper.js'
 
 const mockCacheService = {
   getConfirmationState: jest.fn().mockResolvedValue({ confirmed: true }),
@@ -9,12 +11,28 @@ const mockCacheService = {
   clearState: jest.fn()
 }
 
-jest.mock('@defra/forms-engine-plugin/controllers/SummaryPageController.js')
-jest.mock('~/src/server/common/forms/services/submission.js')
 jest.mock('~/src/server/common/helpers/form-slug-helper.js')
 jest.mock('~/src/server/common/helpers/forms-cache/forms-cache.js', () => ({
   getFormsCacheService: () => mockCacheService
 }))
+jest.mock(
+  '@defra/forms-engine-plugin/controllers/SummaryPageController.js',
+  () => {
+    return {
+      SummaryPageController: class {
+        constructor(model, pageDef) {
+          this.model = model
+          this.pageDef = pageDef
+        }
+      }
+    }
+  }
+)
+jest.mock('~/src/server/common/services/grant-application.service.js')
+jest.mock(
+  '../../common/helpers/grant-application-service/state-to-gas-payload-mapper.js'
+)
+jest.mock('./state-to-gas-answers-mapper.js')
 
 describe('DeclarationPageController', () => {
   let controller
@@ -26,7 +44,17 @@ describe('DeclarationPageController', () => {
   let parentGetHandler
 
   beforeEach(() => {
-    mockModel = {}
+    mockModel = {
+      def: {
+        metadata: {
+          gas: {
+            grantCode: 'adding-value'
+          }
+        }
+      },
+      componentDefMap: {},
+      listDefMap: {}
+    }
     mockPageDef = {}
 
     // Mock the parent's GET handler
@@ -57,8 +85,10 @@ describe('DeclarationPageController', () => {
 
     mockContext = {
       state: {
-        formData: {}
-      }
+        referenceNumber: 'REF123',
+        field1: 'value1'
+      },
+      referenceNumber: 'REF123'
     }
 
     mockH = {
@@ -66,10 +96,12 @@ describe('DeclarationPageController', () => {
       view: jest.fn().mockReturnValue('rendered view')
     }
 
-    formSubmissionService.submit.mockResolvedValue({
-      result: {
-        referenceNumber: 'REF123'
-      }
+    transformAnswerKeysToText.mockReturnValue({ transformedState: true })
+    transformStateObjectToGasApplication.mockReturnValue({
+      transformedApp: true
+    })
+    submitGrantApplication.mockResolvedValue({
+      clientRef: 'ref123'
     })
 
     // Mock the form-slug-helper functions
@@ -196,11 +228,27 @@ describe('DeclarationPageController', () => {
         mockContext,
         'DeclarationController'
       )
-      expect(formSubmissionService.submit).toHaveBeenCalledWith(
-        mockRequest.payload,
-        mockContext.state
+      expect(transformAnswerKeysToText).toHaveBeenCalledWith(
+        mockContext.state,
+        mockModel.componentDefMap,
+        mockModel.listDefMap
       )
-      expect(mockContext.referenceNumber).toBe('REF123')
+
+      expect(transformStateObjectToGasApplication).toHaveBeenCalledWith(
+        {
+          clientRef: 'ref123', // <- note: in controller, it’s `context.referenceNumber?.toLowerCase()`
+          sbi: 'sbi',
+          frn: 'frn',
+          crn: 'crn',
+          defraId: 'defraId'
+        },
+        { transformedState: true },
+        expect.any(Function)
+      )
+
+      expect(submitGrantApplication).toHaveBeenCalledWith('adding-value', {
+        transformedApp: true
+      })
       expect(mockCacheService.setConfirmationState).toHaveBeenCalledWith(
         mockRequest,
         { confirmed: true }
@@ -221,7 +269,7 @@ describe('DeclarationPageController', () => {
       )
       expect(mockRequest.logger.debug).toHaveBeenCalledWith(
         'DeclarationController: Got reference number:',
-        'REF123'
+        'ref123'
       )
       expect(mockRequest.logger.debug).toHaveBeenCalledWith(
         'DeclarationController: Set confirmation state to true'
@@ -233,36 +281,27 @@ describe('DeclarationPageController', () => {
     })
 
     test('should log submission details when available', async () => {
-      const submissionDetails = {
-        fieldsSubmitted: ['field1', 'field2'],
-        timestamp: '2024-03-20T10:00:00Z'
-      }
-      formSubmissionService.submit.mockResolvedValue({
-        result: {
-          referenceNumber: 'REF123',
-          submissionDetails
-        }
-      })
-
       const handler = controller.makePostRouteHandler()
       await handler(mockRequest, mockContext, mockH)
 
       expect(mockRequest.logger.info).toHaveBeenCalledWith({
         message: 'Form submission completed',
-        referenceNumber: 'REF123',
-        fieldsSubmitted: submissionDetails.fieldsSubmitted,
-        timestamp: submissionDetails.timestamp
+        referenceNumber: 'ref123',
+        numberOfSubmittedFields: Object.keys(mockContext.state).length,
+        timestamp: expect.any(String)
       })
     })
 
     test('should handle submission errors', async () => {
       const error = new Error('Submission failed')
-      formSubmissionService.submit.mockRejectedValue(error)
+      submitGrantApplication.mockRejectedValue(error)
 
       const handler = controller.makePostRouteHandler()
+
       await expect(handler(mockRequest, mockContext, mockH)).rejects.toThrow(
         error
       )
+
       expect(mockRequest.logger.error).toHaveBeenCalledWith(
         error,
         'Failed to submit form'
@@ -278,12 +317,15 @@ describe('DeclarationPageController', () => {
         error
       )
 
-      expect(formSubmissionService.submit).toHaveBeenCalled()
+      expect(submitGrantApplication).toHaveBeenCalledWith('adding-value', {
+        transformedApp: true
+      })
       expect(mockContext.referenceNumber).toBe('REF123')
     })
 
     test('should handle case when submission result has no referenceNumber', async () => {
-      formSubmissionService.submit.mockResolvedValueOnce({
+      mockContext.referenceNumber = undefined
+      submitGrantApplication.mockResolvedValueOnce({
         result: {}
       })
 
@@ -294,29 +336,5 @@ describe('DeclarationPageController', () => {
       expect(mockCacheService.setConfirmationState).toHaveBeenCalled()
       expect(mockH.redirect).toHaveBeenCalled()
     })
-
-    test('should handle case when payload is empty', async () => {
-      mockRequest.payload = {}
-
-      const handler = controller.makePostRouteHandler()
-      await handler(mockRequest, mockContext, mockH)
-
-      expect(formSubmissionService.submit).toHaveBeenCalledWith(
-        {},
-        mockContext.state
-      )
-    })
-
-    test('should handle case when context state is undefined', async () => {
-      mockContext.state = undefined
-
-      const handler = controller.makePostRouteHandler()
-      await handler(mockRequest, mockContext, mockH)
-
-      expect(formSubmissionService.submit).toHaveBeenCalledWith(
-        mockRequest.payload,
-        undefined
-      )
-    })
   })
 })
diff --git a/src/server/controllers/declaration/gas-answers.schema.js b/src/server/controllers/declaration/gas-answers.schema.js
new file mode 100644
index 000000000..595e0ec8a
--- /dev/null
+++ b/src/server/controllers/declaration/gas-answers.schema.js
@@ -0,0 +1,61 @@
+import Joi from 'joi'
+
+/**
+ * Joi schema definitions for application answers validation
+ */
+
+const applicationAnswersSchema = Joi.object({
+  $$__referenceNumber: Joi.string(),
+
+  natureOfBusinessRadiosField: Joi.string(),
+  legalStatusRadiosField: Joi.string(),
+  countryYesNoField: Joi.boolean(),
+  planningPermissionRadiosField: Joi.string(),
+  projectStartRadiosField: Joi.string(),
+  tenancyYesNoField: Joi.boolean(),
+  smallerAbattoirYesNoField: Joi.boolean(),
+  otherFarmersYesNoField: Joi.boolean(),
+
+  projectItemsCheckboxesField: Joi.array().items(Joi.string()),
+  storageRadiosField: Joi.string(),
+  projectCostNumberField: Joi.number(),
+  remainingCostsYesNoField: Joi.boolean(),
+  produceProcessedRadiosField: Joi.string(),
+  howAddingValueRadiosField: Joi.string(),
+  projectImpactCheckboxesField: Joi.array().items(Joi.string()),
+  mechanisationYesNoField: Joi.boolean(),
+  manualLabourAmountRadiosField: Joi.string(),
+  applyingRadiosField: Joi.string(),
+
+  applicantFirstName: Joi.string(),
+  applicantLastName: Joi.string(),
+  applicantEmailAddress: Joi.string().email(),
+  applicantConfirmEmailAddress: Joi.string().email(),
+  applicantMobileNumber: Joi.string(),
+  applicantLandlineNumber: Joi.string(),
+
+  applicantBusinessAddress__addressLine1: Joi.string(),
+  applicantBusinessAddress__addressLine2: Joi.string().allow(null),
+  applicantBusinessAddress__town: Joi.string(),
+  applicantBusinessAddress__county: Joi.string().allow(null),
+  applicantBusinessAddress__postcode: Joi.string(),
+  applicantProjectPostcode: Joi.string()
+})
+
+const defaultValidationOptions = {
+  presence: 'optional'
+}
+
+/**
+ * Validates an object against the applicationAnswersSchema
+ * Only validates types of properties that are present, doesn't require any fields
+ * @param {object} payload - The object to validate
+ * @param {object} options - Joi validation options (optional)
+ * @returns {object} - Joi validation result
+ */
+export function validateApplicationAnswers(
+  payload,
+  options = defaultValidationOptions
+) {
+  return applicationAnswersSchema.validate(payload, options)
+}
diff --git a/src/server/controllers/declaration/state-to-gas-answers-mapper.js b/src/server/controllers/declaration/state-to-gas-answers-mapper.js
new file mode 100644
index 000000000..e283cbe67
--- /dev/null
+++ b/src/server/controllers/declaration/state-to-gas-answers-mapper.js
@@ -0,0 +1,58 @@
+/**
+ * @typedef {object} ActionArea
+ * @property {string} unit - Area units (ha, m2, etc)
+ * @property {number} quantity - Area value
+ */
+
+/**
+ * @typedef {object} ActionApplication
+ * @property {string} parcelId - The parcel identifier
+ * @property {string} sheetId - The sheet identifier
+ * @property {string} code - Action code
+ * @property {ActionArea} appliedFor - Area applied for
+ */
+
+/**
+ * @typedef {object} GASAnswers
+ * @property {string} [scheme] - Scheme
+ * @property {number} [year] - Scheme year
+ * @property {boolean} [hasCheckedLandIsUpToDate] - Land details are up to date
+ * @property {ActionApplication[]} [actionApplications] - Action applications information
+ */
+
+/**
+ * Transforms answer keys in a FormContext object to their corresponding text values
+ * @param {object} state - FormContext object
+ * @param {object} componentDefMap - Component definitions map
+ * @param {object} listDefMap - List definitions map
+ * @returns {object} - FormContext object with answer keys replaced with text values
+ */
+export function transformAnswerKeysToText(state, componentDefMap, listDefMap) {
+  const transformedState = {}
+
+  for (const [key, value] of Object.entries(state)) {
+    const componentDef = componentDefMap.get(key)
+
+    if (componentDef?.list) {
+      const listId = componentDef.list
+      const listEntries = listDefMap.get(listId).items
+
+      if (Array.isArray(value)) {
+        // Handle array values (like checkboxes)
+        transformedState[key] = value.map((itemValue) => {
+          const entry = listEntries.find((e) => e.value === itemValue)
+          return entry ? entry.text : itemValue
+        })
+      } else {
+        // Handle single value (like radios, dropdowns)
+        const entry = listEntries.find((e) => e.value === value)
+        transformedState[key] = entry ? entry.text : value
+      }
+    } else {
+      // For non-list fields, just copy as-is
+      transformedState[key] = value
+    }
+  }
+
+  return transformedState
+}
diff --git a/src/server/controllers/declaration/state-to-gas-answers-mapper.test.js b/src/server/controllers/declaration/state-to-gas-answers-mapper.test.js
new file mode 100644
index 000000000..0d699f0de
--- /dev/null
+++ b/src/server/controllers/declaration/state-to-gas-answers-mapper.test.js
@@ -0,0 +1,148 @@
+import { validateApplicationAnswers } from './gas-answers.schema.js'
+import { transformAnswerKeysToText } from './state-to-gas-answers-mapper.js'
+
+describe('transformAnswerKeysToText', () => {
+  const listDefMap = new Map([
+    [
+      'schemeList',
+      {
+        items: [
+          { value: 'scheme-1', text: 'Scheme One' },
+          { value: 'scheme-2', text: 'Scheme Two' }
+        ]
+      }
+    ],
+    [
+      'actionCodeList',
+      {
+        items: [
+          { value: 'code-1', text: 'Action Code One' },
+          { value: 'code-2', text: 'Action Code Two' }
+        ]
+      }
+    ]
+  ])
+
+  const componentDefMap = new Map([
+    ['scheme', { list: 'schemeList' }],
+    ['applicantFirstName', {}],
+    ['isInEngland', {}],
+    ['code', { list: 'actionCodeList' }]
+  ])
+
+  it('should transform single list fields into text', () => {
+    const state = { scheme: 'scheme-1' }
+    const result = transformAnswerKeysToText(state, componentDefMap, listDefMap)
+    expect(result.scheme).toBe('Scheme One')
+  })
+
+  it('should transform non-list fields as-is', () => {
+    const state = { applicantFirstName: 'John', isInEngland: true }
+    const result = transformAnswerKeysToText(state, componentDefMap, listDefMap)
+    expect(result.applicantFirstName).toBe('John')
+    expect(result.isInEngland).toBe(true)
+  })
+
+  it('should handle array fields (checkbox style) into array of text', () => {
+    const extendedComponentDefMap = new Map(componentDefMap)
+    extendedComponentDefMap.set('multiSelectField', { list: 'schemeList' })
+
+    const state = { multiSelectField: ['scheme-1', 'scheme-2'] }
+    const result = transformAnswerKeysToText(
+      state,
+      extendedComponentDefMap,
+      listDefMap
+    )
+
+    expect(result.multiSelectField).toEqual(['Scheme One', 'Scheme Two'])
+  })
+
+  it('should fallback to using raw value if no list entry is found', () => {
+    const state = { scheme: 'unknown-scheme' }
+    const result = transformAnswerKeysToText(state, componentDefMap, listDefMap)
+    expect(result.scheme).toBe('unknown-scheme')
+  })
+
+  it('should handle empty state gracefully', () => {
+    const result = transformAnswerKeysToText({}, componentDefMap, listDefMap)
+    expect(result).toEqual({})
+  })
+})
+
+describe('schema validation', () => {
+  describe('output always conforms to GASPayload schema structure', () => {
+    const validPayload = {
+      $$__referenceNumber: 'AV-108-333',
+      natureOfBusinessRadiosField:
+        'A grower or producer of agricultural or horticultural produce',
+      legalStatusRadiosField: 'Sole trader',
+      countryYesNoField: true,
+      planningPermissionRadiosField: 'Not needed',
+      projectStartRadiosField: 'Yes, preparatory work',
+      tenancyYesNoField: true,
+      smallerAbattoirYesNoField: true,
+      otherFarmersYesNoField: true,
+      projectItemsCheckboxesField: [
+        'Constructing or improving buildings for processing'
+      ],
+      storageRadiosField: 'Yes, we will need storage facilities',
+      projectCostNumberField: 123456,
+      remainingCostsYesNoField: true,
+      produceProcessedRadiosField: 'Arable produce',
+      howAddingValueRadiosField: 'Introducing a new product to your farm',
+      projectImpactCheckboxesField: [
+        'Increasing range of added-value products'
+      ],
+      mechanisationYesNoField: true,
+      manualLabourAmountRadiosField: 'Up to 5% of workforce',
+      applyingRadiosField: 'Applicant',
+      applicantFirstName: 'John',
+      applicantLastName: 'Doe',
+      applicantEmailAddress: 'john.doe@example.com',
+      applicantConfirmEmailAddress: 'john.doe@example.com',
+      applicantMobileNumber: '1234567890',
+      applicantLandlineNumber: '0987654321',
+      applicantBusinessAddress__addressLine1: '123 Main St',
+      applicantBusinessAddress__addressLine2: null,
+      applicantBusinessAddress__town: 'Townsville',
+      applicantBusinessAddress__county: null,
+      applicantBusinessAddress__postcode: 'AB12 3CD',
+      applicantProjectPostcode: 'XY45 6ZT'
+    }
+
+    it('should validate a correct payload without errors', () => {
+      const result = validateApplicationAnswers(validPayload)
+      expect(result.error).toBeUndefined()
+      expect(result.value).toEqual(validPayload)
+    })
+
+    it('should return an error if a boolean field has wrong type', () => {
+      const invalidPayload = {
+        ...validPayload,
+        countryYesNoField: 'yes'
+      }
+      const result = validateApplicationAnswers(invalidPayload)
+      expect(result.error).toBeDefined()
+      expect(result.error.details[0].path).toContain('countryYesNoField')
+    })
+
+    it('should allow missing optional fields without error', () => {
+      const partialPayload = {
+        applicantFirstName: 'John'
+      }
+      const result = validateApplicationAnswers(partialPayload)
+      expect(result.error).toBeUndefined()
+      expect(result.value.applicantFirstName).toBe('John')
+    })
+
+    it('should return an error if email fields are invalid', () => {
+      const invalidPayload = {
+        ...validPayload,
+        applicantEmailAddress: 'not-an-email'
+      }
+      const result = validateApplicationAnswers(invalidPayload)
+      expect(result.error).toBeDefined()
+      expect(result.error.details[0].path).toContain('applicantEmailAddress')
+    })
+  })
+})
diff --git a/src/server/index.js b/src/server/index.js
index 9f2b95e80..54bd42dab 100644
--- a/src/server/index.js
+++ b/src/server/index.js
@@ -19,7 +19,6 @@ import csp from '~/src/plugins/content-security-policy.js'
 import sso from '~/src/plugins/sso.js'
 import { formsService } from '~/src/server/common/forms/services/form.js'
 import { outputService } from '~/src/server/common/forms/services/output.js'
-import { formSubmissionService } from '~/src/server/common/forms/services/submission.js'
 import { catchAll } from '~/src/server/common/helpers/errors.js'
 import { requestLogger } from '~/src/server/common/helpers/logging/request-logger.js'
 import { setupProxy } from '~/src/server/common/helpers/proxy/setup-proxy.js'
@@ -102,7 +101,6 @@ const registerFormsPlugin = async (server) => {
       cacheName: config.get(SESSION_CACHE_NAME),
       services: {
         formsService,
-        formSubmissionService,
         outputService
       },
       filters: {


Reviews:
user: login: swdpcomputing
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T12:28:41Z
user: login: ScenarioFactory
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T08:30:56Z
user: login: ScenarioFactory
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T08:51:23Z
user: login: Andrew-Folga
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T09:11:47Z

Review comments:
diff_hunk: @@ -0,0 +1,61 @@
+import Joi from 'joi'
+
+/**
+ * Joi schema definitions for application answers validation
+ */
+
+const applicationAnswersSchema = Joi.object({
+  $$__referenceNumber: Joi.string(),
+
+  natureOfBusinessRadiosField: Joi.string(),
+  legalStatusRadiosField: Joi.string(),
+  countryYesNoField: Joi.boolean(),
+  planningPermissionRadiosField: Joi.string(),
+  projectStartRadiosField: Joi.string(),
+  tenancyYesNoField: Joi.boolean(),
+  smallerAbattoirYesNoField: Joi.boolean(),
+  otherFarmersYesNoField: Joi.boolean(),
+
+  projectItemsCheckboxesField: Joi.array().items(Joi.string()),
+  storageRadiosField: Joi.string(),
+  projectCostNumberField: Joi.number(),
+  remainingCostsYesNoField: Joi.boolean(),
+  produceProcessedRadiosField: Joi.string(),
+  howAddingValueRadiosField: Joi.string(),
+  projectImpactCheckboxesField: Joi.array().items(Joi.string()),
+  mechanisationYesNoField: Joi.boolean(),
+  manualLabourAmountRadiosField: Joi.string(),
+  applyingRadiosField: Joi.string(),
+
+  applicantFirstName: Joi.string(),
+  applicantLastName: Joi.string(),
+  applicantEmailAddress: Joi.string().email(),
+  applicantConfirmEmailAddress: Joi.string().email(),
+  applicantMobileNumber: Joi.string(),
+  applicantLandlineNumber: Joi.string(),
+
+  applicantBusinessAddress__addressLine1: Joi.string(),
+  applicantBusinessAddress__addressLine2: Joi.string().allow(null),
+  applicantBusinessAddress__town: Joi.string(),
+  applicantBusinessAddress__county: Joi.string().allow(null),
+  applicantBusinessAddress__postcode: Joi.string(),
+  applicantProjectPostcode: Joi.string()
+})
+
+const defaultValidationOptions = {
+  presence: 'optional'
+}
+
+/**
+ * Validates an object against the applicationAnswersSchema
+ * Only validates types of properties that are present, doesn't require any fields
+ * @param {object} payload - The object to validate
+ * @param {object} options - Joi validation options (optional)
+ * @returns {object} - Joi validation result
+ */
+export function validateApplicationAnswers(
path: src/server/controllers/declaration/gas-answers.schema.js
user: login: ScenarioFactory
body: Do we call this function? I'm looking 
created_at: 2025-05-12T08:30:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,61 @@
+import Joi from 'joi'
+
+/**
+ * Joi schema definitions for application answers validation
+ */
+
+const applicationAnswersSchema = Joi.object({
+  $$__referenceNumber: Joi.string(),
+
+  natureOfBusinessRadiosField: Joi.string(),
+  legalStatusRadiosField: Joi.string(),
+  countryYesNoField: Joi.boolean(),
+  planningPermissionRadiosField: Joi.string(),
+  projectStartRadiosField: Joi.string(),
+  tenancyYesNoField: Joi.boolean(),
+  smallerAbattoirYesNoField: Joi.boolean(),
+  otherFarmersYesNoField: Joi.boolean(),
+
+  projectItemsCheckboxesField: Joi.array().items(Joi.string()),
+  storageRadiosField: Joi.string(),
+  projectCostNumberField: Joi.number(),
+  remainingCostsYesNoField: Joi.boolean(),
+  produceProcessedRadiosField: Joi.string(),
+  howAddingValueRadiosField: Joi.string(),
+  projectImpactCheckboxesField: Joi.array().items(Joi.string()),
+  mechanisationYesNoField: Joi.boolean(),
+  manualLabourAmountRadiosField: Joi.string(),
+  applyingRadiosField: Joi.string(),
+
+  applicantFirstName: Joi.string(),
+  applicantLastName: Joi.string(),
+  applicantEmailAddress: Joi.string().email(),
+  applicantConfirmEmailAddress: Joi.string().email(),
+  applicantMobileNumber: Joi.string(),
+  applicantLandlineNumber: Joi.string(),
+
+  applicantBusinessAddress__addressLine1: Joi.string(),
+  applicantBusinessAddress__addressLine2: Joi.string().allow(null),
+  applicantBusinessAddress__town: Joi.string(),
+  applicantBusinessAddress__county: Joi.string().allow(null),
+  applicantBusinessAddress__postcode: Joi.string(),
+  applicantProjectPostcode: Joi.string()
+})
+
+const defaultValidationOptions = {
+  presence: 'optional'
+}
+
+/**
+ * Validates an object against the applicationAnswersSchema
+ * Only validates types of properties that are present, doesn't require any fields
+ * @param {object} payload - The object to validate
+ * @param {object} options - Joi validation options (optional)
+ * @returns {object} - Joi validation result
+ */
+export function validateApplicationAnswers(
path: src/server/controllers/declaration/gas-answers.schema.js
user: login: Andrew-Folga
body: This is still in the works as we do not have the UX refined yet in case errors occur.
created_at: 2025-05-12T09:11:47Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-05-09T15:47:57Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=DEFRA_grants-ui&pullRequest=58) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=DEFRA_grants-ui&pullRequest=58&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=DEFRA_grants-ui&pullRequest=58&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=DEFRA_grants-ui&pullRequest=58&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [92.3% Coverage on New Code](https://sonarcloud.io/component_measures?id=DEFRA_grants-ui&pullRequest=58&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=DEFRA_grants-ui&pullRequest=58&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=DEFRA_grants-ui&pullRequest=58)


author_association: NONE