diff --git a/app/src/main/java/de/medapp/wunddoku/domain/catalog/DocumentationCatalog.kt b/app/src/main/java/de/medapp/wunddoku/domain/catalog/DocumentationCatalog.kt index 80fc6b0..1827030 100644 --- a/app/src/main/java/de/medapp/wunddoku/domain/catalog/DocumentationCatalog.kt +++ b/app/src/main/java/de/medapp/wunddoku/domain/catalog/DocumentationCatalog.kt @@ -28,11 +28,11 @@ import de.medapp.wunddoku.domain.model.InputSpec * Abschnitt 2.3b. * * [CATALOG_VERSION] wird beim Erstellen einer neuen Dokumentation als rein informatives - * Metadatum in Documentation.catalogVersion übernommen. Auf 5 erhöht (Decubitus als einfache Checkbox, Umbau "Empfohlene Wundversorgung"). + * Metadatum in Documentation.catalogVersion übernommen. Auf 6 erhöht (Wundart/Wundgrund geteilt, Wundrandschutz neu geordnet, Granudacyn und Freitext entkoppelt). */ object DocumentationCatalog { - const val CATALOG_VERSION: Int = 5 + const val CATALOG_VERSION: Int = 6 /** * Label der automatisch an jeden Block angehängten Freitext-Option. Zentral hier definiert, @@ -65,30 +65,31 @@ object DocumentationCatalog { CheckItemDefinition(id = "wundart_ulcus_cruris_arteriosum", label = "Ulcus Cruris art.", order = 6), CheckItemDefinition(id = "wundart_ulcus_cruris_venosum_arteriosum", label = "Ulcus Cruris ven.-art.", order = 7), CheckItemDefinition(id = "wundart_diabetisches_fusssyndrom", label = "Diab. Fuß Syndrom", order = 8), - CheckItemDefinition(id = "wundart_post_operative_wundheilungsstoerung", label = "Post-operative Wundheilungsstörung / Dehiszenz", order = 9), - CheckItemDefinition(id = "wundart_schuerfwunde", label = "Schürfwunde", order = 10), - CheckItemDefinition(id = "wundart_intertrigo", label = "Intertrigo / Wunde nach Intertrigo", order = 11), - CheckItemDefinition(id = "wundart_iad", label = "IAD (Inkontinenz-assoziierte Dermatitis) / Wunde nach IAD", order = 12), - CheckItemDefinition(id = "wundart_amputationswunde", label = "Amputationswunde", order = 13), - CheckItemDefinition(id = "wundart_spalthautentnahmestelle", label = "Spalthautentnahmestelle", order = 14), + CheckItemDefinition(id = "wundart_post_operative_wundheilungsstoerung", label = "Post-operative Wundheilungsstörung", order = 9), + CheckItemDefinition(id = "wundart_dehiszenz", label = "Dehiszenz", order = 10), + CheckItemDefinition(id = "wundart_schuerfwunde", label = "Schürfwunde", order = 11), + CheckItemDefinition(id = "wundart_intertrigo", label = "Intertrigo / Wunde nach Intertrigo", order = 12), + CheckItemDefinition(id = "wundart_iad", label = "IAD (Inkontinenz-assoziierte Dermatitis) / Wunde nach IAD", order = 13), + CheckItemDefinition(id = "wundart_amputationswunde", label = "Amputationswunde", order = 14), + CheckItemDefinition(id = "wundart_spalthautentnahmestelle", label = "Spalthautentnahmestelle", order = 15), CheckItemDefinition( id = "wundart_transplantatverlust", label = "Wunde nach Transplantatverlust", - order = 15, + order = 16, inputSpec = InputSpec( pdfTemplate = "Wunde nach Transplantatverlust, Spalthautdeckung ca. {0}%", fields = listOf(InputFieldDef.Number(label = "Spalthautdeckung ca. (%)", maxLength = 3)), ), ), - CheckItemDefinition(id = "wundart_sekundaer_heilende_wunde", label = "Sekundär heilende Wunde", order = 16), - CheckItemDefinition(id = "wundart_tumorwunde", label = "Tumorwunde", order = 17), - CheckItemDefinition(id = "wundart_wunde_nach_abszess", label = "Wunde nach Abszess", order = 18), - CheckItemDefinition(id = "wundart_verbrennung_verbruehung", label = "Verbrennung / Verbrühung", order = 19), - CheckItemDefinition(id = "wundart_platzwunde", label = "Platzwunde", order = 20), - CheckItemDefinition(id = "wundart_schnittwunde", label = "Schnittwunde", order = 21), - CheckItemDefinition(id = "wundart_bisswunde", label = "Bisswunde", order = 22), - CheckItemDefinition(id = "wundart_chemisch_veraetzung", label = "Chemisch hervorgerufene Wunde / Verätzung", order = 23), - CheckItemDefinition(id = "wundart_strahlenschaden", label = "Strahlenschaden", order = 24), + CheckItemDefinition(id = "wundart_sekundaer_heilende_wunde", label = "Sekundär heilende Wunde", order = 17), + CheckItemDefinition(id = "wundart_tumorwunde", label = "Tumorwunde", order = 18), + CheckItemDefinition(id = "wundart_wunde_nach_abszess", label = "Wunde nach Abszess", order = 19), + CheckItemDefinition(id = "wundart_verbrennung_verbruehung", label = "Verbrennung / Verbrühung", order = 20), + CheckItemDefinition(id = "wundart_platzwunde", label = "Platzwunde", order = 21), + CheckItemDefinition(id = "wundart_schnittwunde", label = "Schnittwunde", order = 22), + CheckItemDefinition(id = "wundart_bisswunde", label = "Bisswunde", order = 23), + CheckItemDefinition(id = "wundart_chemisch_veraetzung", label = "Chemisch hervorgerufene Wunde / Verätzung", order = 24), + CheckItemDefinition(id = "wundart_strahlenschaden", label = "Strahlenschaden", order = 25), ), ), BlockDefinition( @@ -169,24 +170,25 @@ object DocumentationCatalog { items = listOf( CheckItemDefinition(id = "wundgrund_wie_ausgestanzt", label = "Wie ausgestanzt", order = 1), CheckItemDefinition(id = "wundgrund_kraterfoermig", label = "Kraterförmig", order = 2), - CheckItemDefinition(id = "wundgrund_unregelmaessig_landkartenartig", label = "Unregelmäßig (landkartenartig/amorph)", order = 3), - CheckItemDefinition(id = "wundgrund_linear_spaltfoermig", label = "Linear/spaltförmig", order = 4), - CheckItemDefinition(id = "wundgrund_rund", label = "Rund", order = 5), - CheckItemDefinition(id = "wundgrund_oval_elliptisch", label = "Oval/elliptisch", order = 6), - CheckItemDefinition(id = "wundgrund_quadrangulaer", label = "Quadrangulär", order = 7), - CheckItemDefinition(id = "wundgrund_eben", label = "Eben", order = 8), - CheckItemDefinition(id = "wundgrund_auf_niveau", label = "Auf Niveau", order = 9), - CheckItemDefinition(id = "wundgrund_ueber_niveau", label = "Über Niveau", order = 10), - CheckItemDefinition(id = "wundgrund_glaenzend", label = "Glänzend", order = 11), - CheckItemDefinition(id = "wundgrund_grob", label = "Grob", order = 12), - CheckItemDefinition(id = "wundgrund_fein", label = "Fein", order = 13), - CheckItemDefinition(id = "wundgrund_glatt", label = "Glatt", order = 14), - CheckItemDefinition(id = "wundgrund_rot", label = "Rot", order = 15), - CheckItemDefinition(id = "wundgrund_blass", label = "Blass", order = 16), - CheckItemDefinition(id = "wundgrund_knochen_sehnen", label = "Knochen/Sehnen", order = 17), - CheckItemDefinition(id = "wundgrund_haemorrhagie", label = "Hämorrhagie", order = 18), - CheckItemDefinition(id = "wundgrund_nicht_kaum_einsehbar", label = "Nicht/kaum einsehbar", order = 19), - CheckItemDefinition(id = "wundgrund_erscheinung_erscheinung", label = "Erscheinung", order = 20, isFreeText = true), + CheckItemDefinition(id = "wundgrund_unregelmaessig", label = "Unregelmäßig", order = 3), + CheckItemDefinition(id = "wundgrund_landkartenartig_amorph", label = "Landkartenartig/amorph", order = 4), + CheckItemDefinition(id = "wundgrund_linear_spaltfoermig", label = "Linear/spaltförmig", order = 5), + CheckItemDefinition(id = "wundgrund_rund", label = "Rund", order = 6), + CheckItemDefinition(id = "wundgrund_oval_elliptisch", label = "Oval/elliptisch", order = 7), + CheckItemDefinition(id = "wundgrund_quadrangulaer", label = "Quadrangulär", order = 8), + CheckItemDefinition(id = "wundgrund_eben", label = "Eben", order = 9), + CheckItemDefinition(id = "wundgrund_auf_niveau", label = "Auf Niveau", order = 10), + CheckItemDefinition(id = "wundgrund_ueber_niveau", label = "Über Niveau", order = 11), + CheckItemDefinition(id = "wundgrund_glaenzend", label = "Glänzend", order = 12), + CheckItemDefinition(id = "wundgrund_grob", label = "Grob", order = 13), + CheckItemDefinition(id = "wundgrund_fein", label = "Fein", order = 14), + CheckItemDefinition(id = "wundgrund_glatt", label = "Glatt", order = 15), + CheckItemDefinition(id = "wundgrund_rot", label = "Rot", order = 16), + CheckItemDefinition(id = "wundgrund_blass", label = "Blass", order = 17), + CheckItemDefinition(id = "wundgrund_knochen_sehnen", label = "Knochen/Sehnen", order = 18), + CheckItemDefinition(id = "wundgrund_haemorrhagie", label = "Hämorrhagie", order = 19), + CheckItemDefinition(id = "wundgrund_nicht_kaum_einsehbar", label = "Nicht/kaum einsehbar", order = 20), + CheckItemDefinition(id = "wundgrund_erscheinung_erscheinung", label = "Erscheinung", order = 21, isFreeText = true), ), ), BlockDefinition( @@ -502,61 +504,61 @@ object DocumentationCatalog { CheckItemDefinition(id = "empfohlene_wundversorgung_sterile_kompresse", label = "Sterile Kompresse", order = 2), CheckItemDefinition(id = "empfohlene_wundversorgung_nacl", label = "NaCl 0,9%", order = 3), CheckItemDefinition(id = "empfohlene_wundversorgung_octenisept", label = "Octenisept", order = 4), - // Granudacyn: Checkbox + direkt folgendes Freitextfeld, das nur bei angehakter - // Checkbox aktiv ist (= vorhandenes isFreeText-Verhalten, s. FreeTextCheckItemRow). - CheckItemDefinition(id = "empfohlene_wundversorgung_granudacyn", label = "Granudacyn", order = 5, isFreeText = true), + // Granudacyn: einfache Checkbox. Der Freitext ist ein eigener, separat + // anzuhakender Punkt direkt darunter (Bezeichnung "Freitext" wird in PDF/JPG + // und, sobald angehakt, auch in der App nicht angezeigt). + CheckItemDefinition(id = "empfohlene_wundversorgung_granudacyn", label = "Granudacyn", order = 5), + CheckItemDefinition(id = "empfohlene_wundversorgung_granudacyn_freitext", label = FREE_TEXT_LABEL, order = 6, isFreeText = true), CheckItemDefinition( id = "empfohlene_wundversorgung_getraenkte_kompresse", label = "Getränkte Kompresse", - order = 6, + order = 7, inputSpec = InputSpec( pdfTemplate = "Getränkte Kompresse für ca. {0} Min. auflegen", fields = listOf(InputFieldDef.Choice(label = "Minuten", options = MINUTE_1_TO_15_OPTIONS)), ), ), - CheckItemDefinition(id = "empfohlene_wundversorgung_tupfend", label = "Tupfend", order = 7), - CheckItemDefinition(id = "empfohlene_wundversorgung_wischend", label = "Wischend", order = 8), - CheckItemDefinition(id = "empfohlene_wundversorgung_nachspuelen_nacl", label = "Nachspülen mit NaCl 0,9%", order = 9), - CheckItemDefinition(id = "empfohlene_wundversorgung_umgebung_trocken_tupfen", label = "Umgebung trocken tupfen", order = 10), - CheckItemDefinition(id = "empfohlene_wundversorgung_primaer_abdecken_austamponieren", label = "Primär Wunde abdecken/austamponieren", order = 11), - CheckItemDefinition(id = "empfohlene_wundversorgung_ueber_wundrand_hinaus", label = "Über Wundrand hinaus", order = 12), - CheckItemDefinition(id = "empfohlene_wundversorgung_nicht_ueber_wundrand_mit", label = "Nicht über Wundrand mit", order = 13), - CheckItemDefinition(id = "empfohlene_wundversorgung_nicht_ueber_wundrand_mit_freitext", label = FREE_TEXT_LABEL, order = 14, isFreeText = true), - CheckItemDefinition(id = "empfohlene_wundversorgung_hinweis_wundrandschutz", label = "Wundrandschutz:", order = 15, isStaticHint = true), - CheckItemDefinition(id = "empfohlene_wundversorgung_wundrandschutz_ja", label = "Ja", order = 16), - CheckItemDefinition(id = "empfohlene_wundversorgung_wundrandschutz_nein", label = "Nein", order = 17), - CheckItemDefinition(id = "empfohlene_wundversorgung_wundrandschutz_freitext", label = FREE_TEXT_LABEL, order = 18, isFreeText = true), + CheckItemDefinition(id = "empfohlene_wundversorgung_tupfend", label = "Tupfend", order = 8), + CheckItemDefinition(id = "empfohlene_wundversorgung_wischend", label = "Wischend", order = 9), + CheckItemDefinition(id = "empfohlene_wundversorgung_nachspuelen_nacl", label = "Nachspülen mit NaCl 0,9%", order = 10), + CheckItemDefinition(id = "empfohlene_wundversorgung_umgebung_trocken_tupfen", label = "Umgebung trocken tupfen", order = 11), + CheckItemDefinition(id = "empfohlene_wundversorgung_primaer_abdecken_austamponieren", label = "Primär Wunde abdecken/austamponieren", order = 12), + CheckItemDefinition(id = "empfohlene_wundversorgung_ueber_wundrand_hinaus", label = "Über Wundrand hinaus", order = 13), + CheckItemDefinition(id = "empfohlene_wundversorgung_nicht_ueber_wundrand_mit", label = "Nicht über Wundrand mit", order = 14), + CheckItemDefinition(id = "empfohlene_wundversorgung_nicht_ueber_wundrand_mit_freitext", label = FREE_TEXT_LABEL, order = 15, isFreeText = true), + // Wundrandschutz: EINE Checkbox mit eigenem Freitextfeld (kein Ja/Nein mehr). + CheckItemDefinition(id = "empfohlene_wundversorgung_wundrandschutz", label = "Wundrandschutz", order = 16, isFreeText = true), // "Wie oft pro Woche": eigene Checkbox; die Auswahl (1-7x) ist erst nach dem // Anhaken aktiv (InputSpec.requiresOwnCheckbox). CheckItemDefinition( id = "empfohlene_wundversorgung_wundrandschutz_tage_pro_woche", label = "Wie oft pro Woche", - order = 19, + order = 17, inputSpec = InputSpec( pdfTemplate = "{0}x pro Woche", fields = listOf(InputFieldDef.Choice(label = "Tage pro Woche", options = WEEKDAY_COUNT_OPTIONS)), requiresOwnCheckbox = true, ), ), - CheckItemDefinition(id = "empfohlene_wundversorgung_hinweis_sekundaere_wundauflage", label = "Sekundäre Wundauflage:", order = 20, isStaticHint = true), - CheckItemDefinition(id = "empfohlene_wundversorgung_sekundaer_sterile_kompresse", label = "Sterile Kompresse", order = 21), - CheckItemDefinition(id = "empfohlene_wundversorgung_saugkompresse", label = "Saugkompresse", order = 22), - CheckItemDefinition(id = "empfohlene_wundversorgung_sonstige_wundauflage", label = "Sonstiges (Wundauflage)", order = 23, isFreeText = true), - CheckItemDefinition(id = "empfohlene_wundversorgung_hinweis_fixieren_mit", label = "Fixieren mit:", order = 24, isStaticHint = true), - CheckItemDefinition(id = "empfohlene_wundversorgung_mullbinde", label = "Mullbinde", order = 25), - CheckItemDefinition(id = "empfohlene_wundversorgung_klebevlies", label = "Klebevlies", order = 26), - CheckItemDefinition(id = "empfohlene_wundversorgung_folienverband", label = "Folienverband", order = 27), - CheckItemDefinition(id = "empfohlene_wundversorgung_schlauchverband", label = "Schlauchverband", order = 28), - CheckItemDefinition(id = "empfohlene_wundversorgung_kohaesive_fixierbinde", label = "Kohäsive Fixierbinde", order = 29), - CheckItemDefinition(id = "empfohlene_wundversorgung_kohaesive_fixierbinde_freitext", label = FREE_TEXT_LABEL, order = 30, isFreeText = true), - CheckItemDefinition(id = "empfohlene_wundversorgung_kompressionsverband_system", label = "Kompressionsverband / System", order = 31), - CheckItemDefinition(id = "empfohlene_wundversorgung_kompressionsverband_system_freitext", label = FREE_TEXT_LABEL, order = 32, isFreeText = true), - CheckItemDefinition(id = "empfohlene_wundversorgung_wechselintervall", label = "Wechselintervall", order = 33, isFreeText = true), - CheckItemDefinition(id = "empfohlene_wundversorgung_hinweis_bedarf", label = "+ bei Bedarf", order = 34, isStaticHint = true), + CheckItemDefinition(id = "empfohlene_wundversorgung_hinweis_sekundaere_wundauflage", label = "Sekundäre Wundauflage:", order = 18, isStaticHint = true), + CheckItemDefinition(id = "empfohlene_wundversorgung_sekundaer_sterile_kompresse", label = "Sterile Kompresse", order = 19), + CheckItemDefinition(id = "empfohlene_wundversorgung_saugkompresse", label = "Saugkompresse", order = 20), + CheckItemDefinition(id = "empfohlene_wundversorgung_sonstige_wundauflage", label = "Sonstiges (Wundauflage)", order = 21, isFreeText = true), + CheckItemDefinition(id = "empfohlene_wundversorgung_hinweis_fixieren_mit", label = "Fixieren mit:", order = 22, isStaticHint = true), + CheckItemDefinition(id = "empfohlene_wundversorgung_mullbinde", label = "Mullbinde", order = 23), + CheckItemDefinition(id = "empfohlene_wundversorgung_klebevlies", label = "Klebevlies", order = 24), + CheckItemDefinition(id = "empfohlene_wundversorgung_folienverband", label = "Folienverband", order = 25), + CheckItemDefinition(id = "empfohlene_wundversorgung_schlauchverband", label = "Schlauchverband", order = 26), + CheckItemDefinition(id = "empfohlene_wundversorgung_kohaesive_fixierbinde", label = "Kohäsive Fixierbinde", order = 27), + CheckItemDefinition(id = "empfohlene_wundversorgung_kohaesive_fixierbinde_freitext", label = FREE_TEXT_LABEL, order = 28, isFreeText = true), + CheckItemDefinition(id = "empfohlene_wundversorgung_kompressionsverband_system", label = "Kompressionsverband / System", order = 29), + CheckItemDefinition(id = "empfohlene_wundversorgung_kompressionsverband_system_freitext", label = FREE_TEXT_LABEL, order = 30, isFreeText = true), + CheckItemDefinition(id = "empfohlene_wundversorgung_wechselintervall", label = "Wechselintervall", order = 31, isFreeText = true), + CheckItemDefinition(id = "empfohlene_wundversorgung_hinweis_bedarf", label = "+ bei Bedarf", order = 32, isStaticHint = true), CheckItemDefinition( id = "empfohlene_wundversorgung_hinweis_abklaerung", label = "Generell: bei akuter Verschlechterung sofort ärztliche Abklärung", - order = 35, + order = 33, isStaticHint = true, ), ), @@ -575,9 +577,8 @@ object DocumentationCatalog { CheckItemDefinition(id = "ziel_abheilung", label = "Abheilung", order = 7), CheckItemDefinition(id = "ziel_geruchsreduzierung", label = "Geruchsreduzierung", order = 8), CheckItemDefinition(id = "ziel_einfache_durchfuehrung", label = "Einfache Durchführung", order = 9), - CheckItemDefinition(id = "ziel_wundrandschutz", label = "Wundrandschutz", order = 10), - CheckItemDefinition(id = "ziel_aufbruch_vorhandenes_material", label = "Aufbruch des vorhandenen Materials", order = 11), - CheckItemDefinition(id = "ziel_selbstversorgung_foerdern", label = "Selbstversorgung der Wunde durch Patient/Angehörige unterstützen/fördern", order = 12), + CheckItemDefinition(id = "ziel_aufbruch_vorhandenes_material", label = "Aufbruch des vorhandenen Materials", order = 10), + CheckItemDefinition(id = "ziel_selbstversorgung_foerdern", label = "Selbstversorgung der Wunde durch Patient/Angehörige unterstützen/fördern", order = 11), ), ), BlockDefinition( diff --git a/app/src/main/java/de/medapp/wunddoku/pdf/AndroidPdfDocumentExporter.kt b/app/src/main/java/de/medapp/wunddoku/pdf/AndroidPdfDocumentExporter.kt index 2b0a5cf..02c3d28 100644 --- a/app/src/main/java/de/medapp/wunddoku/pdf/AndroidPdfDocumentExporter.kt +++ b/app/src/main/java/de/medapp/wunddoku/pdf/AndroidPdfDocumentExporter.kt @@ -2,10 +2,13 @@ package de.medapp.wunddoku.pdf import android.content.Context import android.graphics.Bitmap +import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.graphics.Typeface import android.graphics.pdf.PdfDocument +import android.text.Layout +import android.text.StaticLayout import android.text.TextPaint import de.medapp.wunddoku.domain.error.FailureCategory import de.medapp.wunddoku.domain.model.InputSpecCodec @@ -66,6 +69,8 @@ class AndroidPdfDocumentExporter( private const val ITEM_TEXT_SIZE = 11f private const val FREE_TEXT_TEXT_SIZE = 10.5f private const val FREE_TEXT_INDENT = 20f + private const val RUNNING_HEADER_TEXT_SIZE = 9.5f + private const val RUNNING_HEADER_SPACING_AFTER = 10f // PDF-003: Signaturbereich. Fachlich handschriftliche Unterschriften, keine // kryptographische PDF-Signatur (s. Aufgabenstellung Abschnitt 5). @@ -160,6 +165,9 @@ class AndroidPdfDocumentExporter( marginRight = MARGIN, marginTop = MARGIN, marginBottom = MARGIN, + pageHeaderDrawer = PageHeaderDrawer { canvas, pageNumber, left, top, width -> + drawRunningHeader(canvas, pageNumber, left, top, width, model, paints) + }, ) drawHeader(cursor, model, paints) @@ -243,6 +251,32 @@ class AndroidPdfDocumentExporter( textSize = FREE_TEXT_TEXT_SIZE color = Color.DKGRAY } + // Zwischenüberschriften innerhalb eines Blocks: kursiv + unterstrichen. + val itemSubheading = TextPaint().apply { + isAntiAlias = true + textSize = ITEM_TEXT_SIZE + typeface = Typeface.create(Typeface.DEFAULT, Typeface.ITALIC) + isUnderlineText = true + color = Color.BLACK + } + // Wichtiger Hinweis: fett + unterstrichen. + val itemWarning = TextPaint().apply { + isAntiAlias = true + textSize = ITEM_TEXT_SIZE + typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD) + isUnderlineText = true + color = Color.BLACK + } + // Seitenkopf ab Seite 2 (Patient + Erstelldatum). + val runningHeader = TextPaint().apply { + isAntiAlias = true + textSize = RUNNING_HEADER_TEXT_SIZE + color = Color.DKGRAY + } + val runningHeaderLine = Paint().apply { + color = Color.LTGRAY + strokeWidth = 1f + } // PDF-003: Signaturbereich. val signatureLabel = TextPaint().apply { @@ -286,6 +320,37 @@ class AndroidPdfDocumentExporter( cursor.drawDivider(spacingAfter = 12f) } + /** + * Seitenkopf für alle Seiten AB Seite 2 (Änderungswunsch: auf jeder Seite müssen Name des + * Patienten und Erstellungsdatum stehen): Seite 1 trägt bereits den großen Dokumentkopf + * ([drawHeader]). Gilt auch für Unterschriften- und Fotoseiten. Verwendet ausschließlich + * Snapshot-Daten aus dem [PdfExportModel]. Liefert die belegte Höhe zurück. + */ + private fun drawRunningHeader( + canvas: Canvas, + pageNumber: Int, + left: Float, + top: Float, + width: Float, + model: PdfExportModel, + paints: ExportPaints, + ): Float { + if (pageNumber <= 1) return 0f + val text = "Patient: ${model.patientFullName}, geb. ${model.patientBirthDate.format(HEADER_DATE_FORMATTER)}" + + " · ${model.patientDisplayAddress} · Erstellt am: ${model.createdAt.format(HEADER_DATE_FORMATTER)}" + val layout = StaticLayout.Builder.obtain(text, 0, text.length, paints.runningHeader, width.toInt().coerceAtLeast(1)) + .setAlignment(Layout.Alignment.ALIGN_NORMAL) + .setIncludePad(false) + .build() + canvas.save() + canvas.translate(left, top) + layout.draw(canvas) + canvas.restore() + val lineY = top + layout.height + 4f + canvas.drawLine(left, lineY, left + width, lineY, paints.runningHeaderLine) + return layout.height + 4f + 1f + RUNNING_HEADER_SPACING_AFTER + } + /** Überschriften erscheinen im PDF immer mit abschließendem Doppelpunkt (kein doppelter * Doppelpunkt, falls der Titel bereits mit einem endet). */ private fun headingText(title: String): String { @@ -294,6 +359,8 @@ class AndroidPdfDocumentExporter( } private fun drawBlock(cursor: PdfPageCursor, block: PdfExportBlock, paints: ExportPaints) { + // Änderungswunsch: "Empfohlene Wundversorgung" beginnt immer auf einer eigenen Seite. + if (block.startsOnNewPage) cursor.startNewPageUnlessEmpty() cursor.drawText(headingText(block.title), paints.blockTitle, spacingAfter = 6f) for (item in block.items) { @@ -330,14 +397,37 @@ class AndroidPdfDocumentExporter( return } + // Generische "Freitext"-Einträge (Änderungswunsch): Bezeichnung wird NICHT gedruckt, nur + // der eingegebene Text (eingerückt unter dem vorhergehenden Eintrag). Ein angehakter, + // aber leerer Freitext erzeugt keine Ausgabe. + if (!item.showLabel) { + if (item.freeTextValue.isNotBlank()) { + cursor.drawText( + text = item.freeTextValue, + paint = paints.item, + indent = FREE_TEXT_INDENT, + spacingAfter = 6f, + ) + } + return + } + // Die Freitext-Option bekommt einen abschließenden Doppelpunkt, sofern tatsächlich Text // folgt (z. B. "Granudacyn" ohne Text bleibt ohne hängenden Doppelpunkt), normale // Optionen nie. - val labelText = if (item.isFreeText && item.freeTextValue.isNotBlank()) "${item.label}:" else item.label + val baseLabel = if (item.isFreeText && item.freeTextValue.isNotBlank()) "${item.label}:" else item.label + + // Änderungswunsch: Zwischenüberschriften kursiv + unterstrichen, wichtiger Hinweis fett + + // unterstrichen mit Ausrufezeichen davor und dahinter. + val (labelText, labelPaint) = when (item.style) { + PdfItemStyle.SUBHEADING -> baseLabel to paints.itemSubheading + PdfItemStyle.WARNING -> "! $baseLabel !" to paints.itemWarning + PdfItemStyle.NORMAL -> baseLabel to paints.item + } cursor.drawText( text = labelText, - paint = paints.item, + paint = labelPaint, indent = ITEM_INDENT, spacingAfter = 3f, ) diff --git a/app/src/main/java/de/medapp/wunddoku/pdf/DocumentationToPdfExportModelMapper.kt b/app/src/main/java/de/medapp/wunddoku/pdf/DocumentationToPdfExportModelMapper.kt index 269e5dd..b93996e 100644 --- a/app/src/main/java/de/medapp/wunddoku/pdf/DocumentationToPdfExportModelMapper.kt +++ b/app/src/main/java/de/medapp/wunddoku/pdf/DocumentationToPdfExportModelMapper.kt @@ -1,6 +1,9 @@ package de.medapp.wunddoku.pdf +import de.medapp.wunddoku.domain.catalog.DocumentationCatalog +import de.medapp.wunddoku.domain.model.CheckItem import de.medapp.wunddoku.domain.model.Documentation +import de.medapp.wunddoku.domain.model.DocumentationBlock import de.medapp.wunddoku.domain.usecase.BlockEditedPolicy import java.time.ZoneId @@ -27,22 +30,24 @@ object DocumentationToPdfExportModelMapper { .sortedBy { it.order } .map { block -> PdfExportBlock( + definitionId = block.definitionId, title = block.title, - // Änderungswunsch: nur angehakte Optionen erscheinen im PDF - nicht - // angehakte Checkboxen werden komplett weggelassen (nicht nur unausgefüllt - // dargestellt). Ein leeres Ergebnis ist hier ausgeschlossen: BlockEditedPolicy + startsOnNewPage = block.definitionId in BLOCKS_ON_OWN_PAGE, + // Nur angehakte Optionen erscheinen im PDF. Hinweistexte (isStaticHint) sind + // technisch immer angehakt und werden gruppenabhängig gefiltert, s. + // [exportedItems]. Ein leeres Ergebnis ist ausgeschlossen: BlockEditedPolicy // (Filter oben) lässt nur Blöcke mit mindestens einer angehakten Option durch. - items = block.items - .filter { it.checked } - .sortedBy { it.order } - .map { item -> - PdfExportItem( - label = item.label, - isFreeText = item.isFreeText, - freeTextValue = item.freeTextValue, - inputSpec = item.inputSpec, - ) - }, + items = exportedItems(block).map { item -> + PdfExportItem( + label = item.label, + isFreeText = item.isFreeText, + freeTextValue = item.freeTextValue, + inputSpec = item.inputSpec, + // Generische "Freitext"-Bezeichnung nie drucken, nur den Text. + showLabel = !(item.isFreeText && item.label == DocumentationCatalog.FREE_TEXT_LABEL), + style = styleFor(item), + ) + }, ) } @@ -80,4 +85,67 @@ object DocumentationToPdfExportModelMapper { photos = photos, ) } + + /** Blöcke, die im PDF/JPG immer auf einer neuen Seite beginnen. */ + private val BLOCKS_ON_OWN_PAGE: Set = setOf("empfohlene_wundversorgung") + + /** Zwischenüberschriften (kursiv + unterstrichen), anhand der stabilen Definitions-IDs. */ + private val SUBHEADING_DEFINITION_IDS: Set = setOf( + "empfohlene_wundversorgung_hinweis_reinigen_mit", + "empfohlene_wundversorgung_primaer_abdecken_austamponieren", + "empfohlene_wundversorgung_wundrandschutz", + "empfohlene_wundversorgung_hinweis_sekundaere_wundauflage", + "empfohlene_wundversorgung_hinweis_fixieren_mit", + "empfohlene_wundversorgung_wechselintervall", + ) + + /** Wichtige Hinweise (fett + unterstrichen, mit Ausrufezeichen). */ + private val WARNING_DEFINITION_IDS: Set = setOf( + "empfohlene_wundversorgung_hinweis_abklaerung", + ) + + private fun styleFor(item: CheckItem): PdfItemStyle = when (item.definitionId) { + in WARNING_DEFINITION_IDS -> PdfItemStyle.WARNING + in SUBHEADING_DEFINITION_IDS -> PdfItemStyle.SUBHEADING + else -> PdfItemStyle.NORMAL + } + + /** + * Bestimmt die zu exportierenden Einträge eines Blocks (sortiert nach order): + * - normale Einträge: nur wenn angehakt + * - Hinweistexte (isStaticHint, technisch immer angehakt): Ein Hinweis, dem im Block + * eigene Einträge folgen (bis zum nächsten Hinweis), ist eine Gruppenüberschrift und + * wird nur gedruckt, wenn mindestens einer dieser Einträge angehakt ist (z. B. "Zu + * versorgendes Areal" bei Größe). Hinweise ohne folgende Einträge ("+ bei Bedarf", + * "Generell: ...") bleiben immer erhalten. Der automatisch angehängte generische + * Freitext eines Blocks gehört zu keiner Gruppe. Trägt der erste angehakte Eintrag der + * Gruppe dieselbe Bezeichnung wie der Hinweis, entfällt der Hinweis (keine Doppelung). + * + * Ausschließlich Snapshot-Daten, kein Zugriff auf den aktuellen Katalog. + */ + private fun exportedItems(block: DocumentationBlock): List { + val sorted = block.items.sortedBy { it.order } + val genericFreeTextId = "${block.definitionId}_freetext" + val result = mutableListOf() + for ((index, item) in sorted.withIndex()) { + if (!item.checked) continue + if (!item.isStaticHint) { + result.add(item) + continue + } + val groupItems = sorted + .drop(index + 1) + .takeWhile { !it.isStaticHint } + .filter { it.definitionId != genericFreeTextId } + val keep = when { + groupItems.isEmpty() -> true + else -> { + val firstChecked = groupItems.firstOrNull { it.checked } + firstChecked != null && !firstChecked.label.trim().equals(item.label.trim(), ignoreCase = true) + } + } + if (keep) result.add(item) + } + return result + } } diff --git a/app/src/main/java/de/medapp/wunddoku/pdf/PdfExportModel.kt b/app/src/main/java/de/medapp/wunddoku/pdf/PdfExportModel.kt index 94d0ae8..f978cdc 100644 --- a/app/src/main/java/de/medapp/wunddoku/pdf/PdfExportModel.kt +++ b/app/src/main/java/de/medapp/wunddoku/pdf/PdfExportModel.kt @@ -27,7 +27,12 @@ data class PdfExportModel( ) data class PdfExportBlock( + /** Stabile Definitions-ID des Blocks (z. B. "empfohlene_wundversorgung"). */ + val definitionId: String = "", val title: String, + /** `true`: der Block beginnt im PDF/JPG zwingend auf einer neuen Seite (Änderungswunsch: + * "Empfohlene Wundversorgung" auf eigener Seite). */ + val startsOnNewPage: Boolean = false, /** Bereits gefiltert (NUR angehakte Optionen, s. Mapper - Änderungswunsch) und sortiert * (itemOrder). Nicht angehakte Optionen erscheinen im PDF überhaupt nicht mehr. */ val items: List, @@ -45,8 +50,23 @@ data class PdfExportItem( val isFreeText: Boolean, val freeTextValue: String, val inputSpec: String = "", + /** `false`: die Bezeichnung wird nicht gedruckt, nur der eingegebene Text (generische + * "Freitext"-Einträge, Änderungswunsch: Schriftzug "Freitext" im PDF/JPG nicht anzeigen). */ + val showLabel: Boolean = true, + val style: PdfItemStyle = PdfItemStyle.NORMAL, ) +/** Darstellung der Bezeichnung eines Eintrags im PDF (rein layoutbezogen). */ +enum class PdfItemStyle { + NORMAL, + + /** Zwischenüberschrift innerhalb eines Blocks: kursiv und unterstrichen. */ + SUBHEADING, + + /** Wichtiger Hinweis: fett, unterstrichen, mit Ausrufezeichen davor und dahinter. */ + WARNING, +} + /** * Minimale, für den Fotoanhang benötigte Referenz. Enthält bewusst nur [fileName] - * `documentationId` steht bereits am [PdfExportModel] selbst zur Verfügung (nötig, um über diff --git a/app/src/main/java/de/medapp/wunddoku/pdf/PdfPageCursor.kt b/app/src/main/java/de/medapp/wunddoku/pdf/PdfPageCursor.kt index b3c2cfd..dbb79bf 100644 --- a/app/src/main/java/de/medapp/wunddoku/pdf/PdfPageCursor.kt +++ b/app/src/main/java/de/medapp/wunddoku/pdf/PdfPageCursor.kt @@ -10,6 +10,15 @@ import android.text.Layout import android.text.StaticLayout import android.text.TextPaint +/** + * Zeichnet den Seitenkopf einer (neuen) Seite und liefert die dafür belegte Höhe zurück + * (0f, wenn auf dieser Seite kein Seitenkopf gezeichnet wird, z. B. Seite 1 mit dem großen + * Dokumentkopf). [left]/[top] sind die obere linke Ecke des Inhaltsbereichs, [width] dessen Breite. + */ +internal fun interface PageHeaderDrawer { + fun draw(canvas: Canvas, pageNumber: Int, left: Float, top: Float, width: Float): Float +} + /** * Verwaltet Seiten, Zeichenfläche und die aktuelle vertikale Schreibposition ("Cursor") beim * Erzeugen eines [PdfDocument]. Kapselt die komplette Seitenumbruch-Logik an einer Stelle, damit @@ -33,8 +42,13 @@ internal class PdfPageCursor( private val marginRight: Float, private val marginTop: Float, private val marginBottom: Float, + /** Optionaler Seitenkopf, der auf JEDER neu begonnenen Seite gezeichnet wird (Änderungswunsch: + * Patientenname und Erstellungsdatum auf jeder Seite). */ + private val pageHeaderDrawer: PageHeaderDrawer? = null, ) { private var pageNumber = 0 + /** Y-Position, ab der auf der aktuellen Seite Inhalt beginnt (nach einem evtl. Seitenkopf). */ + private var pageContentTop: Float = marginTop private var currentPage: PdfDocument.Page? = null private var canvas: Canvas? = null private var cursorY: Float = marginTop @@ -74,7 +88,15 @@ internal class PdfPageCursor( val page = pdfDocument.startPage(info) currentPage = page canvas = page.canvas - cursorY = marginTop + val headerHeight = pageHeaderDrawer?.draw( + canvas = page.canvas, + pageNumber = pageNumber, + left = marginLeft, + top = marginTop, + width = pageWidth - marginLeft - marginRight, + ) ?: 0f + pageContentTop = marginTop + headerHeight + cursorY = pageContentTop } private fun finishCurrentPageIfOpen() { @@ -98,6 +120,15 @@ internal class PdfPageCursor( startNewPage() } + /** + * Beginnt eine neue Seite, außer die aktuelle Seite ist noch völlig leer (Cursor steht am + * Inhaltsanfang) - so entstehen keine Leerseiten, wenn ein Block, der immer auf einer + * eigenen Seite beginnt, ohnehin ganz oben stünde. + */ + fun startNewPageUnlessEmpty() { + if (currentPage == null || cursorY > pageContentTop) startNewPage() + } + /** * Zeichnet [bitmap] proportional eingepasst (kein Zuschneiden, keine Verzerrung) horizontal * über die volle Inhaltsbreite und vertikal zentriert im aktuell verbleibenden Platz der diff --git a/app/src/main/java/de/medapp/wunddoku/ui/documentation/edit/DocumentationEditScreen.kt b/app/src/main/java/de/medapp/wunddoku/ui/documentation/edit/DocumentationEditScreen.kt index 589df24..f973555 100644 --- a/app/src/main/java/de/medapp/wunddoku/ui/documentation/edit/DocumentationEditScreen.kt +++ b/app/src/main/java/de/medapp/wunddoku/ui/documentation/edit/DocumentationEditScreen.kt @@ -85,6 +85,7 @@ import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import de.medapp.wunddoku.R +import de.medapp.wunddoku.domain.catalog.DocumentationCatalog import de.medapp.wunddoku.domain.model.CheckItem import de.medapp.wunddoku.domain.model.InputFieldDef import de.medapp.wunddoku.domain.model.InputSpecCodec @@ -907,19 +908,25 @@ private fun FreeTextCheckItemRow( onCheckedChanged: (checkItemStateId: String, checked: Boolean) -> Unit, onFreeTextChanged: (checkItemStateId: String, text: String) -> Unit, ) { + // Änderungswunsch: Ist ein generischer "Freitext"-Eintrag angehakt, wird der Schriftzug + // "Freitext" nicht mehr angezeigt (nur Häkchenkästchen + Eingabefeld). Fachlich benannte + // Freitext-Einträge (z. B. "Wundrandschutz", "Wechselintervall") behalten ihre Bezeichnung. + val hideName = item.checked && item.label == DocumentationCatalog.FREE_TEXT_LABEL + val fieldLabel: (@Composable () -> Unit)? = + if (hideName) null else ({ Text(stringResource(R.string.documentation_edit_free_text_label)) }) Column { Row(verticalAlignment = Alignment.CenterVertically) { Checkbox( checked = item.checked, onCheckedChange = { checked -> onCheckedChanged(item.checkItemStateId, checked) }, ) - Text(item.label) + if (!hideName) Text(item.label) } OutlinedTextField( value = item.freeTextValue, onValueChange = { text -> onFreeTextChanged(item.checkItemStateId, text) }, enabled = item.checked, - label = { Text(stringResource(R.string.documentation_edit_free_text_label)) }, + label = fieldLabel, modifier = Modifier.fillMaxWidth().padding(start = 40.dp), minLines = 2, ) diff --git a/app/src/test/java/de/medapp/wunddoku/domain/catalog/DocumentationCatalogTest.kt b/app/src/test/java/de/medapp/wunddoku/domain/catalog/DocumentationCatalogTest.kt new file mode 100644 index 0000000..acdd53d --- /dev/null +++ b/app/src/test/java/de/medapp/wunddoku/domain/catalog/DocumentationCatalogTest.kt @@ -0,0 +1,78 @@ +package de.medapp.wunddoku.domain.catalog + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Test + +/** + * Strukturprüfungen für den statischen Katalog sowie die fachlichen Änderungswünsche + * (Wundart/Wundgrund geteilt, Wundrandschutz, Granudacyn). + */ +class DocumentationCatalogTest { + + private fun items(blockId: String) = DocumentationCatalog.findBlock(blockId)!!.items + + @Test + fun alle_checkitem_ids_sind_katalogweit_eindeutig() { + val ids = DocumentationCatalog.blocks.flatMap { b -> b.items.map { it.id } } + assertEquals(ids.size, ids.toSet().size) + } + + @Test + fun order_ist_je_block_eindeutig_und_aufsteigend() { + for (block in DocumentationCatalog.blocks) { + val orders = block.items.map { it.order } + assertEquals("Block ${block.id}", orders.sorted(), orders) + assertEquals("Block ${block.id}", orders.size, orders.toSet().size) + } + } + + @Test + fun wundart_postoperativ_und_dehiszenz_sind_getrennte_punkte() { + val wundart = items("wundart") + assertEquals("Post-operative Wundheilungsstörung", wundart.single { it.id == "wundart_post_operative_wundheilungsstoerung" }.label) + assertEquals("Dehiszenz", wundart.single { it.id == "wundart_dehiszenz" }.label) + assertTrue(wundart.none { it.label.contains("/ Dehiszenz") }) + } + + @Test + fun wundgrund_unregelmaessig_und_landkartenartig_sind_getrennte_punkte() { + val wundgrund = items("wundgrund_erscheinung") + assertEquals("Unregelmäßig", wundgrund.single { it.id == "wundgrund_unregelmaessig" }.label) + assertEquals("Landkartenartig/amorph", wundgrund.single { it.id == "wundgrund_landkartenartig_amorph" }.label) + assertNull(wundgrund.firstOrNull { it.id == "wundgrund_unregelmaessig_landkartenartig" }) + } + + @Test + fun wundrandschutz_ist_aus_ziel_entfernt() { + assertTrue(items("ziel").none { it.label.contains("Wundrandschutz") }) + } + + @Test + fun wundrandschutz_ist_eine_einzelne_freitext_box_ohne_ja_nein() { + val ev = items("empfohlene_wundversorgung") + val wrs = ev.single { it.id == "empfohlene_wundversorgung_wundrandschutz" } + assertEquals("Wundrandschutz", wrs.label) + assertTrue(wrs.isFreeText) + assertNull(ev.firstOrNull { it.id == "empfohlene_wundversorgung_hinweis_wundrandschutz" }) + assertNull(ev.firstOrNull { it.id == "empfohlene_wundversorgung_wundrandschutz_ja" }) + assertNull(ev.firstOrNull { it.id == "empfohlene_wundversorgung_wundrandschutz_nein" }) + // "Wie oft pro Woche" bleibt als eigene Checkbox erhalten. + assertNotNull(ev.firstOrNull { it.id == "empfohlene_wundversorgung_wundrandschutz_tage_pro_woche" }) + } + + @Test + fun granudacyn_ist_einfache_checkbox_und_freitext_ein_eigener_punkt_direkt_darunter() { + val ev = items("empfohlene_wundversorgung") + val granudacyn = ev.single { it.id == "empfohlene_wundversorgung_granudacyn" } + val freitext = ev.single { it.id == "empfohlene_wundversorgung_granudacyn_freitext" } + + assertFalse(granudacyn.isFreeText) + assertTrue(freitext.isFreeText) + assertEquals(DocumentationCatalog.FREE_TEXT_LABEL, freitext.label) + assertEquals(granudacyn.order + 1, freitext.order) + } +} diff --git a/app/src/test/java/de/medapp/wunddoku/pdf/DocumentationToPdfExportModelMapperTest.kt b/app/src/test/java/de/medapp/wunddoku/pdf/DocumentationToPdfExportModelMapperTest.kt index df3d5bd..133aab6 100644 --- a/app/src/test/java/de/medapp/wunddoku/pdf/DocumentationToPdfExportModelMapperTest.kt +++ b/app/src/test/java/de/medapp/wunddoku/pdf/DocumentationToPdfExportModelMapperTest.kt @@ -172,10 +172,156 @@ class DocumentationToPdfExportModelMapperTest { val items = DocumentationToPdfExportModelMapper.map(doc).blocks.single().items - assertEquals(PdfExportItem("Freitext", isFreeText = true, freeTextValue = "Rötung", inputSpec = ""), items[0]) + // Generischer "Freitext": Bezeichnung wird nicht gedruckt (showLabel = false). + assertEquals( + PdfExportItem("Freitext", isFreeText = true, freeTextValue = "Rötung", inputSpec = "", showLabel = false), + items[0], + ) assertEquals(PdfExportItem("Maße", isFreeText = false, freeTextValue = "3|4", inputSpec = "SPEC"), items[1]) } + // --- Änderungswünsche: Freitext, Hinweisgruppen, Stile, eigene Seite ------------------ + + @Test + fun benannter_freitext_behaelt_seine_bezeichnung() { + val doc = documentation( + listOf( + block("a", 1, items = listOf( + item("w", 1, checked = true, label = "Wundrandschutz", isFreeText = true, freeTextValue = "Cavilon"), + )), + ) + ) + + assertTrue(DocumentationToPdfExportModelMapper.map(doc).blocks.single().items.single().showLabel) + } + + @Test + fun gruppenhinweis_ohne_angehakten_eintrag_der_gruppe_entfaellt() { + // Größe: "Zu versorgendes Areal" darf nicht erscheinen, wenn nur die Maße ausgefüllt sind. + val doc = documentation( + listOf( + block("groesse", 1, items = listOf( + item("h_masse", 1, checked = true, label = "Maße", isStaticHint = true), + item("masse", 2, checked = true, label = "Maße (Länge / Breite / Tiefe)", freeTextValue = "3|4|1", inputSpec = "SPEC"), + item("h_areal", 3, checked = true, label = "Zu versorgendes Areal", isStaticHint = true), + item("areal", 4, checked = false, label = "Zu versorgendes Areal"), + item("areal_ft", 5, checked = false, label = "Freitext für zu versorgendes Areal", isFreeText = true), + // generischer Freitext des Blocks gehört zu keiner Gruppe + item("groesse_freetext", 6, checked = false, label = "Freitext", isFreeText = true), + )), + ) + ) + + val labels = DocumentationToPdfExportModelMapper.map(doc).blocks.single().items.map { it.label } + + assertEquals(listOf("Maße", "Maße (Länge / Breite / Tiefe)"), labels) + } + + @Test + fun generischer_freitext_laesst_gruppenhinweis_nicht_erscheinen() { + val doc = documentation( + listOf( + block("groesse", 1, items = listOf( + item("masse", 1, checked = true, label = "Maße", freeTextValue = "3|4|1", inputSpec = "SPEC"), + item("h_areal", 2, checked = true, label = "Zu versorgendes Areal", isStaticHint = true), + item("areal", 3, checked = false, label = "Areal"), + item("groesse_freetext", 4, checked = true, label = "Freitext", isFreeText = true, freeTextValue = "x"), + )), + ) + ) + + val labels = DocumentationToPdfExportModelMapper.map(doc).blocks.single().items.map { it.label } + + assertEquals(listOf("Maße", "Freitext"), labels) + } + + @Test + fun gruppenhinweis_mit_angehaktem_eintrag_bleibt_erhalten() { + val doc = documentation( + listOf( + block("a", 1, items = listOf( + item("h", 1, checked = true, label = "Fixieren mit:", isStaticHint = true), + item("m", 2, checked = true, label = "Mullbinde"), + item("k", 3, checked = false, label = "Klebevlies"), + )), + ) + ) + + val labels = DocumentationToPdfExportModelMapper.map(doc).blocks.single().items.map { it.label } + + assertEquals(listOf("Fixieren mit:", "Mullbinde"), labels) + } + + @Test + fun gruppenhinweis_mit_gleichnamigem_angehaktem_eintrag_wird_nicht_doppelt_gedruckt() { + val doc = documentation( + listOf( + block("a", 1, items = listOf( + item("h", 1, checked = true, label = "Zu versorgendes Areal", isStaticHint = true), + item("areal", 2, checked = true, label = "Zu versorgendes Areal"), + )), + ) + ) + + val labels = DocumentationToPdfExportModelMapper.map(doc).blocks.single().items.map { it.label } + + assertEquals(listOf("Zu versorgendes Areal"), labels) + } + + @Test + fun abschliessende_hinweise_ohne_folgende_eintraege_bleiben_immer_erhalten() { + val doc = documentation( + listOf( + block("empfohlene_wundversorgung", 1, items = listOf( + item("x", 1, checked = true, label = "Mullbinde"), + item("empfohlene_wundversorgung_hinweis_bedarf", 2, checked = true, label = "+ bei Bedarf", isStaticHint = true), + item("empfohlene_wundversorgung_hinweis_abklaerung", 3, checked = true, label = "Generell: ...", isStaticHint = true), + item("empfohlene_wundversorgung_freetext", 4, checked = false, label = "Freitext", isFreeText = true), + )), + ) + ) + + val labels = DocumentationToPdfExportModelMapper.map(doc).blocks.single().items.map { it.label } + + assertEquals(listOf("Mullbinde", "+ bei Bedarf", "Generell: ..."), labels) + } + + @Test + fun stile_werden_anhand_der_definitions_id_vergeben() { + val doc = documentation( + listOf( + block("empfohlene_wundversorgung", 1, items = listOf( + item("empfohlene_wundversorgung_hinweis_reinigen_mit", 1, checked = true, label = "Reinigen mit:", isStaticHint = true), + item("empfohlene_wundversorgung_nacl", 2, checked = true, label = "NaCl 0,9%"), + item("empfohlene_wundversorgung_primaer_abdecken_austamponieren", 3, checked = true, label = "Primär Wunde abdecken/austamponieren"), + item("empfohlene_wundversorgung_hinweis_abklaerung", 4, checked = true, label = "Generell: ...", isStaticHint = true), + )), + ) + ) + + val styles = DocumentationToPdfExportModelMapper.map(doc).blocks.single().items.map { it.style } + + assertEquals( + listOf(PdfItemStyle.SUBHEADING, PdfItemStyle.NORMAL, PdfItemStyle.SUBHEADING, PdfItemStyle.WARNING), + styles, + ) + } + + @Test + fun empfohlene_wundversorgung_beginnt_auf_eigener_seite_andere_bloecke_nicht() { + val doc = documentation( + listOf( + block("weitere_ueberlegung", 1, items = listOf(item("a", 1, checked = true))), + block("empfohlene_wundversorgung", 2, items = listOf(item("b", 1, checked = true))), + block("ziel", 3, items = listOf(item("c", 1, checked = true))), + ) + ) + + val blocks = DocumentationToPdfExportModelMapper.map(doc).blocks + + assertEquals(listOf(false, true, false), blocks.map { it.startsOnNewPage }) + } + // --- Sortierung ---------------------------------------------------------------------- @Test