Repository: digitalservicebund/a2j-rechtsantragstelle
Pull request #2066: feat(BerH): enable optional document upload field
Author: login: ingriddorio
Created: 2025-05-12T08:13:12Z | Merged: 2025-05-12T15:00:55Z

This PR implements the optional document upload field, enabling the user to upload other files or leaving it empty without getting required error message

![Screenshot 2025-05-12 at 09 49 18](https://github.com/user-attachments/assets/cb29fca9-4da8-4832-9fe0-a2c66a278d63)



Diff:
diff --git a/app/domains/beratungshilfe/formular/abgabe/dokumente/context.ts b/app/domains/beratungshilfe/formular/abgabe/dokumente/context.ts
index bc41fa6c85..6d660667dc 100644
--- a/app/domains/beratungshilfe/formular/abgabe/dokumente/context.ts
+++ b/app/domains/beratungshilfe/formular/abgabe/dokumente/context.ts
@@ -4,36 +4,43 @@ import {
   fileUploadLimit,
 } from "~/util/file/pdfFileSchema";
 
-const fileUploadSchema = z
+const fileUploadRequiredSchema = z
   .array(pdfFileMetaDataSchema)
   .nonempty({ message: "fileRequired" })
   .max(fileUploadLimit, { message: "fileLimitReached" })
   .optional(); // remove after e2e tests have been added
 
+const fileUploadOptionalSchema = z
+  .array(pdfFileMetaDataSchema)
+  .max(fileUploadLimit, { message: "fileLimitReached" })
+  .optional(); // remove after e2e tests have been added
+
 export const dokumenteContext = {
-  arbeitslosengeldBeweis: fileUploadSchema,
-  wohngeldBeweis: fileUploadSchema,
-  bafoegBeweis: fileUploadSchema,
-
-  krankengeldBeweis: fileUploadSchema,
-  elterngeldBeweis: fileUploadSchema,
-  buergergeldBeweis: fileUploadSchema,
-  asylbewerberleistungenBeweis: fileUploadSchema,
-  keineLeistungenBeweis: fileUploadSchema,
-  grundsicherungBeweis: fileUploadSchema,
-
-  lebensversicherungBeweis: fileUploadSchema,
-  bausparvertragBeweis: fileUploadSchema,
-  wertpapiereBeweis: fileUploadSchema,
-  guthabenkontoBeweis: fileUploadSchema,
-  sparkontoBeweis: fileUploadSchema,
-  grundeigentumBeweis: fileUploadSchema,
-
-  schwangerschaftAngabeBeweis: fileUploadSchema,
-  schwerbehinderungBeweis: fileUploadSchema,
-  medizinischeGruendeBeweis: fileUploadSchema,
-
-  weitereAusgabenBeweis: fileUploadSchema,
+  arbeitslosengeldBeweis: fileUploadRequiredSchema,
+  wohngeldBeweis: fileUploadRequiredSchema,
+  bafoegBeweis: fileUploadRequiredSchema,
+
+  krankengeldBeweis: fileUploadRequiredSchema,
+  elterngeldBeweis: fileUploadRequiredSchema,
+  buergergeldBeweis: fileUploadRequiredSchema,
+  asylbewerberleistungenBeweis: fileUploadRequiredSchema,
+  keineLeistungenBeweis: fileUploadRequiredSchema,
+  grundsicherungBeweis: fileUploadRequiredSchema,
+
+  lebensversicherungBeweis: fileUploadRequiredSchema,
+  bausparvertragBeweis: fileUploadRequiredSchema,
+  wertpapiereBeweis: fileUploadRequiredSchema,
+  guthabenkontoBeweis: fileUploadRequiredSchema,
+  sparkontoBeweis: fileUploadRequiredSchema,
+  grundeigentumBeweis: fileUploadRequiredSchema,
+
+  schwangerschaftAngabeBeweis: fileUploadRequiredSchema,
+  schwerbehinderungBeweis: fileUploadRequiredSchema,
+  medizinischeGruendeBeweis: fileUploadRequiredSchema,
+
+  weitereAusgabenBeweis: fileUploadRequiredSchema,
+
+  weitereDokumenteBeweis: fileUploadOptionalSchema,
 };
 
 const _contextObject = z.object(dokumenteContext).partial();
diff --git a/app/domains/beratungshilfe/formular/index.ts b/app/domains/beratungshilfe/formular/index.ts
index 176e573097..0aee5a44aa 100644
--- a/app/domains/beratungshilfe/formular/index.ts
+++ b/app/domains/beratungshilfe/formular/index.ts
@@ -18,6 +18,7 @@ import {
   ausgabenStrings,
   weiteresEinkommenStrings,
   eigentumZusammenfassungShowTotalWorthWarnings,
+  getWeitereDokumenteStrings,
 } from "./stringReplacements";
 import { beratungshilfeXstateConfig } from "./xstateConfig";
 import type { AbgabeContext } from "../../shared/formular/abgabe/context";
@@ -50,6 +51,7 @@ export const beratungshilfeFormular = {
     ...ausgabenStrings(context),
     ...geldAnlagenStrings(context),
     ...weiteresEinkommenStrings(context),
+    ...getWeitereDokumenteStrings(context),
   }),
 } satisfies Flow;
 
diff --git a/app/domains/beratungshilfe/formular/stringReplacements.ts b/app/domains/beratungshilfe/formular/stringReplacements.ts
index 7e425271f3..748842b435 100644
--- a/app/domains/beratungshilfe/formular/stringReplacements.ts
+++ b/app/domains/beratungshilfe/formular/stringReplacements.ts
@@ -126,3 +126,11 @@ export const getMissingInformationStrings = (
     ...requiresEinkommenDone,
   };
 };
+
+export const getWeitereDokumenteStrings = (
+  context: BeratungshilfeFormularContext,
+) => {
+  return {
+    hasWeitereDokumente: context.weitereDokumenteBeweis !== null,
+  };
+};
diff --git a/app/domains/beratungshilfe/services/pdf/userUploadedFiles/attachUserUploadedFilesToPdf.ts b/app/domains/beratungshilfe/services/pdf/userUploadedFiles/attachUserUploadedFilesToPdf.ts
index 11627fd840..047a30fd95 100644
--- a/app/domains/beratungshilfe/services/pdf/userUploadedFiles/attachUserUploadedFilesToPdf.ts
+++ b/app/domains/beratungshilfe/services/pdf/userUploadedFiles/attachUserUploadedFilesToPdf.ts
@@ -4,6 +4,7 @@ import {
   getStaatlicheLeistungenStrings,
   weiteresEinkommenStrings,
   ausgabenStrings,
+  getWeitereDokumenteStrings,
 } from "~/domains/beratungshilfe/formular/stringReplacements";
 import { geldAnlagenStrings } from "~/domains/shared/formular/stringReplacements";
 import { appendPagesToPdf } from "~/services/pdf/appendPagesToPdf";
@@ -19,6 +20,7 @@ export async function attachUserUploadedFilesToPdf(
   const weiteresEinkommen = weiteresEinkommenStrings(userData);
   const ausgaben = ausgabenStrings(userData);
   const geldAnlagen = geldAnlagenStrings(userData);
+  const weitereDokumente = getWeitereDokumenteStrings(userData);
 
   const relevantFiles: Uint8Array[] = [];
 
@@ -167,6 +169,15 @@ export async function attachUserUploadedFilesToPdf(
     )),
   );
 
+  relevantFiles.push(
+    ...(await getRelevantFiles(
+      userData.weitereDokumenteBeweis,
+      sessionId,
+      flowId,
+      weitereDokumente.hasWeitereDokumente,
+    )),
+  );
+
   const pdfWithUserFiles = await PDFDocument.load(mainPdfBuffer);
 
   for (const file of relevantFiles) {


Reviews:
user: login: Spencer6497
body: 🚀 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T11:26:43Z
user: login: ingriddorio
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T13:53:01Z

Review comments:
diff_hunk: @@ -4,36 +4,43 @@ import {
   fileUploadLimit,
 } from "~/util/file/pdfFileSchema";
 
-const fileUploadSchema = z
+const fileUploadRequiredSchema = z
   .array(pdfFileMetaDataSchema)
   .nonempty({ message: "fileRequired" })
   .max(fileUploadLimit, { message: "fileLimitReached" })
   .optional(); // remove after e2e tests have been added
 
+const fileUploadOptionalSchema = z
+  .array(pdfFileMetaDataSchema)
+  .max(fileUploadLimit, { message: "fileLimitReached" })
+  .optional(); // remove after e2e tests have been added
path: app/domains/beratungshilfe/formular/abgabe/dokumente/context.ts
user: login: Spencer6497
body: can we remove the `.optional()` now that we have e2e tests? 
created_at: 2025-05-12T11:26:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,36 +4,43 @@ import {
   fileUploadLimit,
 } from "~/util/file/pdfFileSchema";
 
-const fileUploadSchema = z
+const fileUploadRequiredSchema = z
   .array(pdfFileMetaDataSchema)
   .nonempty({ message: "fileRequired" })
   .max(fileUploadLimit, { message: "fileLimitReached" })
   .optional(); // remove after e2e tests have been added
 
+const fileUploadOptionalSchema = z
+  .array(pdfFileMetaDataSchema)
+  .max(fileUploadLimit, { message: "fileLimitReached" })
+  .optional(); // remove after e2e tests have been added
path: app/domains/beratungshilfe/formular/abgabe/dokumente/context.ts
user: login: ingriddorio
body: Sure!
created_at: 2025-05-12T13:53:01Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-05-12T14:59:49Z
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=digitalservicebund_a2j-rechtsantragstelle&pullRequest=2066) **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=digitalservicebund_a2j-rechtsantragstelle&pullRequest=2066&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=digitalservicebund_a2j-rechtsantragstelle&pullRequest=2066&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=digitalservicebund_a2j-rechtsantragstelle&pullRequest=2066&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [100.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=digitalservicebund_a2j-rechtsantragstelle&pullRequest=2066&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=digitalservicebund_a2j-rechtsantragstelle&pullRequest=2066&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=digitalservicebund_a2j-rechtsantragstelle&pullRequest=2066)


author_association: NONE