diff --git a/app/schemas/de.medapp.wunddoku.data.database.AppDatabase/5.json b/app/schemas/de.medapp.wunddoku.data.database.AppDatabase/5.json new file mode 100644 index 0000000..f42f89f --- /dev/null +++ b/app/schemas/de.medapp.wunddoku.data.database.AppDatabase/5.json @@ -0,0 +1,629 @@ +{ + "formatVersion": 1, + "database": { + "version": 5, + "identityHash": "12448571f5ffcf00398fa1d8260c9b2b", + "entities": [ + { + "tableName": "patients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `firstName` TEXT NOT NULL, `lastName` TEXT NOT NULL, `birthDate` TEXT NOT NULL, `street` TEXT NOT NULL, `postalCode` TEXT NOT NULL, `city` TEXT NOT NULL, `phone1` TEXT NOT NULL, `phone2` TEXT, `phone3` TEXT, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "firstName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastName", + "columnName": "lastName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birthDate", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "street", + "columnName": "street", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "postalCode", + "columnName": "postalCode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "city", + "columnName": "city", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phone1", + "columnName": "phone1", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phone2", + "columnName": "phone2", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "phone3", + "columnName": "phone3", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_patients_lastName_firstName", + "unique": false, + "columnNames": [ + "lastName", + "firstName" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_patients_lastName_firstName` ON `${TABLE_NAME}` (`lastName`, `firstName`)" + }, + { + "name": "index_patients_birthDate", + "unique": false, + "columnNames": [ + "birthDate" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_patients_birthDate` ON `${TABLE_NAME}` (`birthDate`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "documentations", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `patientId` TEXT NOT NULL, `status` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `continuedFromDocumentationId` TEXT, `pdfLastGeneratedAt` INTEGER, `catalogVersion` INTEGER NOT NULL, `patientFirstNameSnapshot` TEXT NOT NULL, `patientLastNameSnapshot` TEXT NOT NULL, `patientBirthDateSnapshot` TEXT NOT NULL, `patientStreetSnapshot` TEXT NOT NULL, `patientPostalCodeSnapshot` TEXT NOT NULL, `patientCitySnapshot` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`patientId`) REFERENCES `patients`(`id`) ON UPDATE NO ACTION ON DELETE RESTRICT , FOREIGN KEY(`continuedFromDocumentationId`) REFERENCES `documentations`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "patientId", + "columnName": "patientId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "continuedFromDocumentationId", + "columnName": "continuedFromDocumentationId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "pdfLastGeneratedAt", + "columnName": "pdfLastGeneratedAt", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "catalogVersion", + "columnName": "catalogVersion", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "patientFirstNameSnapshot", + "columnName": "patientFirstNameSnapshot", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "patientLastNameSnapshot", + "columnName": "patientLastNameSnapshot", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "patientBirthDateSnapshot", + "columnName": "patientBirthDateSnapshot", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "patientStreetSnapshot", + "columnName": "patientStreetSnapshot", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "patientPostalCodeSnapshot", + "columnName": "patientPostalCodeSnapshot", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "patientCitySnapshot", + "columnName": "patientCitySnapshot", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_documentations_patientId_createdAt", + "unique": false, + "columnNames": [ + "patientId", + "createdAt" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_documentations_patientId_createdAt` ON `${TABLE_NAME}` (`patientId`, `createdAt`)" + }, + { + "name": "index_documentations_status", + "unique": false, + "columnNames": [ + "status" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_documentations_status` ON `${TABLE_NAME}` (`status`)" + }, + { + "name": "index_documentations_continuedFromDocumentationId", + "unique": false, + "columnNames": [ + "continuedFromDocumentationId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_documentations_continuedFromDocumentationId` ON `${TABLE_NAME}` (`continuedFromDocumentationId`)" + } + ], + "foreignKeys": [ + { + "table": "patients", + "onDelete": "RESTRICT", + "onUpdate": "NO ACTION", + "columns": [ + "patientId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "documentations", + "onDelete": "SET NULL", + "onUpdate": "NO ACTION", + "columns": [ + "continuedFromDocumentationId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "block_states", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `documentationId` TEXT NOT NULL, `blockDefinitionId` TEXT NOT NULL, `titleSnapshot` TEXT NOT NULL, `blockOrder` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`documentationId`) REFERENCES `documentations`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "documentationId", + "columnName": "documentationId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockDefinitionId", + "columnName": "blockDefinitionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "titleSnapshot", + "columnName": "titleSnapshot", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockOrder", + "columnName": "blockOrder", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_block_states_documentationId", + "unique": false, + "columnNames": [ + "documentationId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_block_states_documentationId` ON `${TABLE_NAME}` (`documentationId`)" + }, + { + "name": "index_block_states_documentationId_blockDefinitionId", + "unique": true, + "columnNames": [ + "documentationId", + "blockDefinitionId" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_block_states_documentationId_blockDefinitionId` ON `${TABLE_NAME}` (`documentationId`, `blockDefinitionId`)" + } + ], + "foreignKeys": [ + { + "table": "documentations", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "documentationId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "check_item_states", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `blockStateId` TEXT NOT NULL, `checkItemDefinitionId` TEXT NOT NULL, `labelSnapshot` TEXT NOT NULL, `itemOrder` INTEGER NOT NULL, `checked` INTEGER NOT NULL, `isFreeText` INTEGER NOT NULL, `freeTextValue` TEXT NOT NULL, `isStaticHint` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`blockStateId`) REFERENCES `block_states`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockStateId", + "columnName": "blockStateId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "checkItemDefinitionId", + "columnName": "checkItemDefinitionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "labelSnapshot", + "columnName": "labelSnapshot", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "itemOrder", + "columnName": "itemOrder", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "checked", + "columnName": "checked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFreeText", + "columnName": "isFreeText", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "freeTextValue", + "columnName": "freeTextValue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStaticHint", + "columnName": "isStaticHint", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_check_item_states_blockStateId", + "unique": false, + "columnNames": [ + "blockStateId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_check_item_states_blockStateId` ON `${TABLE_NAME}` (`blockStateId`)" + }, + { + "name": "index_check_item_states_blockStateId_checkItemDefinitionId", + "unique": true, + "columnNames": [ + "blockStateId", + "checkItemDefinitionId" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_check_item_states_blockStateId_checkItemDefinitionId` ON `${TABLE_NAME}` (`blockStateId`, `checkItemDefinitionId`)" + } + ], + "foreignKeys": [ + { + "table": "block_states", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "blockStateId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "photos", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `documentationId` TEXT NOT NULL, `fileName` TEXT NOT NULL, `orderIndex` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`documentationId`) REFERENCES `documentations`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "documentationId", + "columnName": "documentationId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fileName", + "columnName": "fileName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "orderIndex", + "columnName": "orderIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_photos_documentationId_orderIndex", + "unique": false, + "columnNames": [ + "documentationId", + "orderIndex" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_photos_documentationId_orderIndex` ON `${TABLE_NAME}` (`documentationId`, `orderIndex`)" + } + ], + "foreignKeys": [ + { + "table": "documentations", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "documentationId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "signatures", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `documentationId` TEXT NOT NULL, `signerName` TEXT NOT NULL, `fileName` TEXT NOT NULL, `orderIndex` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`documentationId`) REFERENCES `documentations`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "documentationId", + "columnName": "documentationId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "signerName", + "columnName": "signerName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fileName", + "columnName": "fileName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "orderIndex", + "columnName": "orderIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_signatures_documentationId", + "unique": false, + "columnNames": [ + "documentationId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_signatures_documentationId` ON `${TABLE_NAME}` (`documentationId`)" + } + ], + "foreignKeys": [ + { + "table": "documentations", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "documentationId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "wound_carers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `signatureFileName` TEXT NOT NULL, `updatedAt` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "signatureFileName", + "columnName": "signatureFileName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '12448571f5ffcf00398fa1d8260c9b2b')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/de/medapp/wunddoku/data/database/AppDatabase.kt b/app/src/main/java/de/medapp/wunddoku/data/database/AppDatabase.kt index 5c1cec3..2c8c2c1 100644 --- a/app/src/main/java/de/medapp/wunddoku/data/database/AppDatabase.kt +++ b/app/src/main/java/de/medapp/wunddoku/data/database/AppDatabase.kt @@ -44,6 +44,10 @@ import de.medapp.wunddoku.data.entity.converter.DocumentationStatusConverter * Version 4 (vorher 3, Änderungswunsch): neue, eigenständige Tabelle `wound_carers` * (Namensvorschläge + Standard-Vorbelegung im Signatur-Dialog, s. [WoundCarerEntity]) - rein * additiv ([MIGRATION_3_4]), keine bestehenden Tabellen betroffen. + * + * Version 5 (vorher 4, finale fachliche Katalog-Inhalte): `check_item_states.isStaticHint` neu + * (nicht editierbarer Hinweistext, z. B. "+ bei Bedarf") - rein additive Spalte mit + * Default-Wert ([MIGRATION_4_5]), keine bestehenden Daten verändert. */ @Database( entities = [ @@ -55,7 +59,7 @@ import de.medapp.wunddoku.data.entity.converter.DocumentationStatusConverter SignatureEntity::class, WoundCarerEntity::class, ], - version = 4, + version = 5, exportSchema = true, ) @TypeConverters(DocumentationStatusConverter::class) @@ -83,7 +87,7 @@ abstract class AppDatabase : RoomDatabase() { DATABASE_NAME, ) // Kein fallbackToDestructiveMigration() mehr ab Version 3 - s. Klassenkommentar. - .addMigrations(MIGRATION_2_3, MIGRATION_3_4) + .addMigrations(MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5) .build().also { instance = it } } } diff --git a/app/src/main/java/de/medapp/wunddoku/data/database/Migrations.kt b/app/src/main/java/de/medapp/wunddoku/data/database/Migrations.kt index 31f5ec1..94c4706 100644 --- a/app/src/main/java/de/medapp/wunddoku/data/database/Migrations.kt +++ b/app/src/main/java/de/medapp/wunddoku/data/database/Migrations.kt @@ -111,3 +111,23 @@ val MIGRATION_3_4 = object : Migration(3, 4) { ) } } + +/** + * Migration von Schema-Version 4 auf 5 (finale fachliche Katalog-Inhalte: neuer Content-Typ + * "statischer Hinweistext", z. B. "+ bei Bedarf" bei "Empfohlene Wundversorgung"). + * + * Rein additiv - eine einzelne neue, NOT NULL-Spalte mit Default `false` (0) auf einer + * bestehenden Tabelle. Anders als bei der Signaturen-Migration (2->3, dort war ein + * NULL->NOT-NULL-Wechsel auf einer bereits befüllten Spalte nötig) ist hier ein einfaches + * `ALTER TABLE ... ADD COLUMN` mit Default-Wert ausreichend und von SQLite direkt unterstützt - + * kein Tabellenneubau nötig. Bestehende Zeilen erhalten automatisch `isStaticHint = 0` + * (entspricht ihrem bisherigen, unveränderten Verhalten als normale Checkbox- bzw. + * Freitext-Zeile). + */ +val MIGRATION_4_5 = object : Migration(4, 5) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + "ALTER TABLE `check_item_states` ADD COLUMN `isStaticHint` INTEGER NOT NULL DEFAULT 0" + ) + } +} diff --git a/app/src/main/java/de/medapp/wunddoku/data/entity/CheckItemStateEntity.kt b/app/src/main/java/de/medapp/wunddoku/data/entity/CheckItemStateEntity.kt index 3269dac..25ec104 100644 --- a/app/src/main/java/de/medapp/wunddoku/data/entity/CheckItemStateEntity.kt +++ b/app/src/main/java/de/medapp/wunddoku/data/entity/CheckItemStateEntity.kt @@ -15,6 +15,18 @@ import androidx.room.PrimaryKey * zusätzliche CheckItemStateEntity mit `isFreeText = true` - das ist die Freitext-Option des * Blocks (Änderungswunsch: Freitext als weitere auswählbare Option statt eigenes Feld am * Block). Für normale Checkbox-Optionen bleibt `isFreeText = false` und `freeTextValue` leer. + * Derselbe Mechanismus wird jetzt auch für vom KATALOG selbst definierte Freitext-Checkboxen + * verwendet (z. B. "Wundgrund"/"Erscheinung" oder die vier Empfänger-Freitexte bei "Kopie + * nachrichtlich an") - technisch identisch zur automatisch angehängten Option, nur mit eigener, + * fachlich benannter [labelSnapshot] statt des generischen "Freitext"-Labels. + * + * [isStaticHint]: NEU. Markiert eine Zeile als reinen, nicht editierbaren Hinweistext (z. B. + * "+ bei Bedarf") - keine Checkbox in der UI, kein Umschalten möglich. Solche Zeilen werden + * beim Anlegen (s. DocumentationRepository.insertBlocksFromCatalog) mit `checked = true` + * fest verankert, damit die bestehende, unverändert gebliebene "nur angehakte Elemente"-Regel + * im PDF-Export sie automatisch mit anzeigt, ohne dass diese Regel selbst angepasst werden + * musste. [BlockEditedPolicy] schließt `isStaticHint`-Zeilen dagegen explizit von der + * "bearbeitet"-Prüfung aus, da ein reiner Hinweistext keine Nutzereingabe darstellt. */ @Entity( tableName = "check_item_states", @@ -40,4 +52,5 @@ data class CheckItemStateEntity( val checked: Boolean, val isFreeText: Boolean = false, val freeTextValue: String = "", + val isStaticHint: Boolean = false, ) diff --git a/app/src/main/java/de/medapp/wunddoku/data/mapper/DocumentationMapper.kt b/app/src/main/java/de/medapp/wunddoku/data/mapper/DocumentationMapper.kt index e3ef34e..a0121c2 100644 --- a/app/src/main/java/de/medapp/wunddoku/data/mapper/DocumentationMapper.kt +++ b/app/src/main/java/de/medapp/wunddoku/data/mapper/DocumentationMapper.kt @@ -71,6 +71,7 @@ fun CheckItemStateEntity.toDomain(): CheckItem = CheckItem( order = itemOrder, checked = checked, isFreeText = isFreeText, + isStaticHint = isStaticHint, freeTextValue = freeTextValue, ) diff --git a/app/src/main/java/de/medapp/wunddoku/data/repository/DocumentationRepository.kt b/app/src/main/java/de/medapp/wunddoku/data/repository/DocumentationRepository.kt index e051892..1ec75c0 100644 --- a/app/src/main/java/de/medapp/wunddoku/data/repository/DocumentationRepository.kt +++ b/app/src/main/java/de/medapp/wunddoku/data/repository/DocumentationRepository.kt @@ -185,6 +185,7 @@ class DocumentationRepository( itemOrder = item.itemOrder, checked = item.checked, isFreeText = item.isFreeText, + isStaticHint = item.isStaticHint, freeTextValue = item.freeTextValue, ) } @@ -376,16 +377,30 @@ class DocumentationRepository( /** * Legt für jeden Katalog-Block einen BlockState an, sowie je ein CheckItemState pro - * Katalog-Checkbox PLUS eine zusätzliche, automatisch angehängte Freitext-Option - * (isFreeText = true) - siehe DocumentationCatalog.FREE_TEXT_LABEL und - * BlockDefinition.freeTextDefinitionId. + * Katalog-Eintrag PLUS eine zusätzliche, automatisch angehängte generische Freitext-Option + * (isFreeText = true, Label "Freitext") - siehe DocumentationCatalog.FREE_TEXT_LABEL und + * BlockDefinition.freeTextDefinitionId. Diese generische Option wird WEITERHIN für JEDEN + * Block angehängt, unabhängig davon, ob der Block selbst bereits eigene, fachlich benannte + * `isFreeText`-Einträge definiert (z. B. "Wundgrund + Erscheinung" oder "Kopie + * nachrichtlich an") - so bestätigt und akzeptiert, auch wenn das für einzelne Blöcke eine + * zusätzliche, generisch benannte Freitext-Checkbox neben den fachlichen Einträgen bedeutet. * - * Änderungswunsch: Die Freitext-Option erscheint als ERSTES Element im Block (itemOrder = 0, - * niedriger als jede Katalog-Checkbox, deren `order` bei 1 beginnt) - Vorbereitung dafür, - * dass echte Multiple-Choice-Varianten später als weitere Optionen NACH dem Freitext folgen - * können. Betrifft ausschließlich NEU erstellte Dokumentationen: bereits bestehende - * BlockState-/CheckItemState-Zeilen behalten ihre historisch gespeicherte itemOrder - * unverändert (Snapshot-Prinzip, s. Klassendokumentation zu titleSnapshot/labelSnapshot). + * Katalog-Einträge werden 1:1 aus [BlockDefinition.items] übernommen: `isFreeText` und + * `isStaticHint` steuern direkt das Verhalten der jeweiligen Zeile (normale Checkbox, + * eigene benannte Freitext-Checkbox, oder nicht editierbarer Hinweistext) - keine + * block-spezifische Sonderlogik nötig, die Katalogdefinition allein entscheidet. + * + * Hinweistext-Einträge (`isStaticHint = true`) werden bereits HIER mit `checked = true` + * angelegt (statt änderbar per UI-Checkbox) - dadurch greift die bestehende, unveränderte + * "nur angehakte Elemente"-Regel im PDF-Export automatisch auch für sie, ohne dass diese + * Regel selbst angepasst werden musste. [de.medapp.wunddoku.domain.usecase.BlockEditedPolicy] + * schließt solche Zeilen dagegen explizit aus der "bearbeitet"-Prüfung aus. + * + * Die generische Freitext-Option erscheint als ERSTES Element im Block (itemOrder = 0, + * niedriger als jeder Katalog-Eintrag, dessen `order` bei 1 beginnt). Betrifft ausschließlich + * NEU erstellte Dokumentationen: bereits bestehende BlockState-/CheckItemState-Zeilen + * behalten ihre historisch gespeicherte itemOrder unverändert (Snapshot-Prinzip, s. + * Klassendokumentation zu titleSnapshot/labelSnapshot). */ private suspend fun insertBlocksFromCatalog(documentationId: String, now: Long) { for (blockDef in DocumentationCatalog.blocks) { @@ -403,19 +418,23 @@ class DocumentationRepository( ) ) - val normalItems = blockDef.items.map { itemDef -> + val catalogItems = blockDef.items.map { itemDef -> CheckItemStateEntity( id = UUID.randomUUID().toString(), blockStateId = blockStateId, checkItemDefinitionId = itemDef.id, labelSnapshot = itemDef.label, itemOrder = itemDef.order, - checked = false, - isFreeText = false, + // Hinweistexte starten bereits "angehakt" (s. Methodenkommentar) - alle + // anderen Einträge (Checkbox oder eigene Freitext-Checkbox) starten + // unangehakt/leer, wie bisher. + checked = itemDef.isStaticHint, + isFreeText = itemDef.isFreeText, + isStaticHint = itemDef.isStaticHint, freeTextValue = "", ) } - val freeTextItem = CheckItemStateEntity( + val genericFreeTextItem = CheckItemStateEntity( id = UUID.randomUUID().toString(), blockStateId = blockStateId, checkItemDefinitionId = blockDef.freeTextDefinitionId, @@ -423,9 +442,10 @@ class DocumentationRepository( itemOrder = 0, checked = false, isFreeText = true, + isStaticHint = false, freeTextValue = "", ) - checkItemStateDao.insertAll(normalItems + freeTextItem) + checkItemStateDao.insertAll(catalogItems + genericFreeTextItem) } } } 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 2cf4ff6..063e1c2 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 @@ -3,10 +3,30 @@ package de.medapp.wunddoku.domain.catalog /** * Zentrale, statische Definition aller Dokumentationsblöcke und ihrer Checkboxen. * - * WICHTIG: Die hier verwendeten Inhalte sind ausschließlich neutrale Platzhalter - * (Vorgabe aus der Aufgabenstellung, Abschnitt 25: "Erfinde keine medizinischen Inhalte"). - * Die späteren echten Inhalte (ca. 20 fachliche Blöcke) werden ausschließlich in dieser - * Datei ergänzt – keine andere Stelle im Code (UI, Datenbank) muss dafür geändert werden. + * Enthält die mit dem Kunden final abgestimmten Dokumentationsabschnitte. Basis ist die + * handschriftlich vorgegebene 22-Punkte-Reihenfolge, ergänzt/verfeinert nach Abgleich mit dem + * ursprünglichen Papierformular ("Wunddokumentation" von Harald Klock) - dabei wurden: + * + * - mehrere Abschnitte, die im Chat/der handschriftlichen Liste nur als Freitext geführt waren, + * auf dem Papierbogen aber eine differenzierte Checkbox-Struktur zeigen, entsprechend + * erweitert (Wundgrund, Belag, Wundrand, Exsudat, Größe, Fistel/Tasche, Lokalisation, + * Empfohlene Wundversorgung), + * - zwei auf dem Papierbogen vorhandene, aber in der handschriftlichen Liste fehlende + * Abschnitte wieder aufgenommen ("Weitere Überlegung" - vermutlich versehentlich in der + * handschriftlichen Liste ausgelassen; "Nächster Termin" - auf dem Bogen als eigenes Feld + * vorhanden), + * - "Verlauf" bewusst NICHT um die alten Papier-Checkboxen erweitert (explizit vom Kunden + * bestätigt: bleibt reiner Freitext, die alte Checkbox-Auswahl wurde bewusst verworfen), + * - "Wundumgebung" und "Auswirkungen auf das alltägliche Leben" um die Papier-Checkbox-Listen + * ergänzt, jeweils MIT vorangestelltem freiem Textfeld (die ohnehin an jedem Block + * automatisch vorhandene generische Freitext-Option deckt das ab, s. [FREE_TEXT_LABEL]), + * - "Wundart" um die Papier-Checkbox "nicht bekannt/diagnostiziert" ergänzt (zusätzlich zum + * Freitext - NICHT Teil der früher verworfenen Wundarten-Liste, sondern ein eigenständiges + * Status-Flag). + * + * Frühere, im Kundenchat zwischenzeitlich diskutierte, aber später ausdrücklich verworfene + * Inhalte (Wundart-Typenliste, Verlauf-Checkboxen, "Schmerzempfinden 1-10") sind weiterhin + * bewusst NICHT enthalten. * * Diese Definitionen sind bewusst reiner Kotlin-Code und keine Room-Tabellen: Sie ändern sich * nur durch neue App-Versionen, nicht durch Nutzerinteraktion. Siehe Architektur-Dokument @@ -16,51 +36,370 @@ package de.medapp.wunddoku.domain.catalog * Metadatum in Documentation.catalogVersion übernommen. Erhöhe diesen Wert, wenn sich Blöcke * oder Checkboxen inhaltlich ändern (nicht bei rein redaktionellen Korrekturen wie * Tippfehlern) – rein zu Nachvollziehbarkeitszwecken, nicht sicherheitsrelevant für die - * historische Korrektheit (das übernehmen die Label-/Titel-Snapshots). + * historische Korrektheit (das übernehmen die Label-/Titel-Snapshots). Auf 3 erhöht mit dem + * Abgleich gegen das ursprüngliche Papierformular. */ object DocumentationCatalog { - const val CATALOG_VERSION: Int = 1 + const val CATALOG_VERSION: Int = 3 /** * Label der automatisch an jeden Block angehängten Freitext-Option (Änderungswunsch: * "Freitext soll selbst wie eine weitere auswählbare Option behandelt werden"). Zentral - * hier definiert, damit Repository (Erstellung/Fortsetzen) und ggf. der spätere PDF-Export - * dieselbe Bezeichnung verwenden. + * hier definiert, damit Repository (Erstellung/Fortsetzen) und PDF-Export dieselbe + * Bezeichnung verwenden. Gilt weiterhin unverändert für ALLE Blöcke, auch für solche, die + * fachlich "nur Checkboxen" wären - ausdrücklich bestätigt und kein Fehler. */ const val FREE_TEXT_LABEL: String = "Freitext" val blocks: List = listOf( BlockDefinition( - id = "block_01", - title = "Block 1", + id = "wundart", + title = "Wundart", order = 1, items = listOf( - CheckItemDefinition(id = "block_01_a", label = "Option A", order = 1), - CheckItemDefinition(id = "block_01_b", label = "Option B", order = 2), + CheckItemDefinition(id = "wundart_nicht_bekannt_diagnostiziert", label = "nicht bekannt/diagnostiziert", order = 1), ), ), BlockDefinition( - id = "block_02", - title = "Block 2", + id = "lokalisation", + title = "Lokalisation", order = 2, items = listOf( - CheckItemDefinition(id = "block_02_a", label = "Option A", order = 1), - CheckItemDefinition(id = "block_02_b", label = "Option B", order = 2), - CheckItemDefinition(id = "block_02_c", label = "Option C", order = 3), + CheckItemDefinition(id = "lokalisation_multiple_wunden", label = "multiple Wunden im Bereich", order = 1), ), ), BlockDefinition( - id = "block_03", - title = "Block 3", + id = "groesse", + title = "Größe", order = 3, items = listOf( - CheckItemDefinition(id = "block_03_a", label = "Option A", order = 1), - CheckItemDefinition(id = "block_03_b", label = "Option B", order = 2), + CheckItemDefinition(id = "groesse_multiple_wunden_max", label = "multiple Wunden mit max. (cm)", order = 1, isFreeText = true), + CheckItemDefinition(id = "groesse_wunde", label = "Wunde", order = 2), + CheckItemDefinition(id = "groesse_areal", label = "Areal", order = 3), + CheckItemDefinition(id = "groesse_masse", label = "Maße (Länge x Breite, ca. cm)", order = 4, isFreeText = true), + CheckItemDefinition(id = "groesse_vertikale_tiefe", label = "Vertikale Tiefe (ca. cm)", order = 5, isFreeText = true), ), ), - // Weitere Blöcke (insgesamt ca. 20) werden hier ergänzt, sobald die fachlichen - // Inhalte geliefert werden. Struktur und Reihenfolge bleiben identisch. + BlockDefinition( + id = "verlauf", + title = "Verlauf", + order = 4, + items = emptyList(), + ), + BlockDefinition( + id = "exsudat", + title = "Exsudat", + order = 5, + items = listOf( + CheckItemDefinition(id = "exsudat_kein", label = "Kein", order = 1), + CheckItemDefinition(id = "exsudat_wenig", label = "Wenig", order = 2), + CheckItemDefinition(id = "exsudat_maessig", label = "Mäßig", order = 3), + CheckItemDefinition(id = "exsudat_viel", label = "Viel", order = 4), + CheckItemDefinition(id = "exsudat_uebermaessig", label = "Übermäßig", order = 5), + CheckItemDefinition(id = "exsudat_waessrig", label = "Wässrig", order = 6), + CheckItemDefinition(id = "exsudat_zaehfluessig", label = "Zähflüssig", order = 7), + CheckItemDefinition(id = "exsudat_eitrig", label = "Eitrig", order = 8), + CheckItemDefinition(id = "exsudat_blutig", label = "Blutig", order = 9), + CheckItemDefinition(id = "exsudat_klar", label = "Klar", order = 10), + CheckItemDefinition(id = "exsudat_gelblich", label = "Gelblich", order = 11), + CheckItemDefinition(id = "exsudat_gruenlich", label = "Grünlich", order = 12), + CheckItemDefinition(id = "exsudat_braeunlich", label = "Bräunlich", order = 13), + CheckItemDefinition(id = "exsudat_roetlich", label = "Rötlich", order = 14), + CheckItemDefinition(id = "exsudat_weisslich", label = "Weißlich", order = 15), + ), + ), + BlockDefinition( + id = "wundgrund_erscheinung", + title = "Wundgrund + Erscheinung", + order = 6, + items = listOf( + CheckItemDefinition(id = "wundgrund_eben", label = "Eben", order = 1), + CheckItemDefinition(id = "wundgrund_unregelmaessig", label = "Unregelmäßig", order = 2), + CheckItemDefinition(id = "wundgrund_knospig", label = "Knospig", order = 3), + CheckItemDefinition(id = "wundgrund_auf_niveau", label = "Auf Niveau", order = 4), + CheckItemDefinition(id = "wundgrund_ueber_niveau", label = "Über Niveau", order = 5), + CheckItemDefinition(id = "wundgrund_glaenzend", label = "Glänzend", order = 6), + CheckItemDefinition(id = "wundgrund_grob", label = "Grob", order = 7), + CheckItemDefinition(id = "wundgrund_fein", label = "Fein", order = 8), + CheckItemDefinition(id = "wundgrund_glatt", label = "Glatt", order = 9), + CheckItemDefinition(id = "wundgrund_rot", label = "Rot", order = 10), + CheckItemDefinition(id = "wundgrund_blass", label = "Blass", order = 11), + CheckItemDefinition(id = "wundgrund_knochen_sehnen", label = "Knochen/Sehnen", order = 12), + CheckItemDefinition(id = "wundgrund_haemorrhagie", label = "Hämorrhagie", order = 13), + CheckItemDefinition(id = "wundgrund_nicht_kaum_einsehbar", label = "Nicht/kaum einsehbar", order = 14), + CheckItemDefinition(id = "wundgrund_erscheinung_erscheinung", label = "Erscheinung", order = 15, isFreeText = true), + ), + ), + BlockDefinition( + id = "belag", + title = "Belag", + order = 7, + items = listOf( + CheckItemDefinition(id = "belag_nein", label = "Nein", order = 1), + CheckItemDefinition(id = "belag_ja", label = "Ja", order = 2), + CheckItemDefinition(id = "belag_fibrin", label = "Fibrin", order = 3), + CheckItemDefinition(id = "belag_fremdkoerper", label = "Fremdkörper", order = 4), + CheckItemDefinition(id = "belag_nekrose", label = "Nekrose", order = 5), + CheckItemDefinition(id = "belag_kruste", label = "Kruste", order = 6), + CheckItemDefinition(id = "belag_mit_fibrinsaum", label = "Mit Fibrinsaum", order = 7), + CheckItemDefinition(id = "belag_trocken", label = "Trocken", order = 8), + CheckItemDefinition(id = "belag_feucht", label = "Feucht", order = 9), + CheckItemDefinition(id = "belag_100_prozent", label = "100%", order = 10), + CheckItemDefinition(id = "belag_75_prozent", label = "75%", order = 11), + CheckItemDefinition(id = "belag_50_prozent", label = "50%", order = 12), + CheckItemDefinition(id = "belag_25_prozent_plus", label = "25%+<", order = 13), + ), + ), + BlockDefinition( + id = "wundrand", + title = "Wundrand", + order = 8, + items = listOf( + CheckItemDefinition(id = "wundrand_unauffaellig", label = "Unauffällig", order = 1), + CheckItemDefinition(id = "wundrand_partiell_unterschiedlich", label = "Partiell unterschiedlich", order = 2), + CheckItemDefinition(id = "wundrand_rosarot", label = "Rosarot", order = 3), + CheckItemDefinition(id = "wundrand_rot", label = "Rot", order = 4), + CheckItemDefinition(id = "wundrand_blaeulich", label = "Bläulich", order = 5), + CheckItemDefinition(id = "wundrand_blass", label = "Blass", order = 6), + CheckItemDefinition(id = "wundrand_blutig", label = "Blutig", order = 7), + CheckItemDefinition(id = "wundrand_mit_kruste_belegt", label = "Mit Kruste belegt", order = 8), + CheckItemDefinition(id = "wundrand_mazeriert", label = "Mazeriert", order = 9), + CheckItemDefinition(id = "wundrand_unterminiert", label = "Unterminiert", order = 10), + CheckItemDefinition(id = "wundrand_trocken", label = "Trocken", order = 11), + CheckItemDefinition(id = "wundrand_glatt", label = "Glatt", order = 12), + CheckItemDefinition(id = "wundrand_flach_einlaufend", label = "Flach einlaufend", order = 13), + CheckItemDefinition(id = "wundrand_wallartig", label = "Wallartig", order = 14), + CheckItemDefinition(id = "wundrand_wulstig", label = "Wulstig", order = 15), + CheckItemDefinition(id = "wundrand_zerklueftet", label = "Zerklüftet", order = 16), + CheckItemDefinition(id = "wundrand_ueberhaengende_strukturen_lefzen", label = "Überhängende Strukturen/Lefzen", order = 17), + ), + ), + BlockDefinition( + id = "wundumgebung", + title = "Wundumgebung", + order = 9, + items = listOf( + CheckItemDefinition(id = "wundumgebung_unauffaellig", label = "Unauffällig", order = 1), + CheckItemDefinition(id = "wundumgebung_oedem", label = "Ödem", order = 2), + CheckItemDefinition(id = "wundumgebung_ekzem", label = "Ekzem", order = 3), + CheckItemDefinition(id = "wundumgebung_schuppig", label = "Schuppig", order = 4), + CheckItemDefinition(id = "wundumgebung_narbengewebe", label = "Narbengewebe", order = 5), + CheckItemDefinition(id = "wundumgebung_hornhaut_verhaertet", label = "Hornhaut/verhärtet", order = 6), + CheckItemDefinition(id = "wundumgebung_geroetet", label = "Gerötet", order = 7), + CheckItemDefinition(id = "wundumgebung_blaeulich", label = "Bläulich", order = 8), + CheckItemDefinition(id = "wundumgebung_blass", label = "Blass", order = 9), + CheckItemDefinition(id = "wundumgebung_hyperkeratose", label = "Hyperkeratose", order = 10), + CheckItemDefinition(id = "wundumgebung_pergamenthaut", label = "Pergamenthaut", order = 11), + CheckItemDefinition(id = "wundumgebung_rissig_kratzspuren", label = "Rissig/Kratzspuren", order = 12), + CheckItemDefinition(id = "wundumgebung_spannungsblasen", label = "Spannungsblasen", order = 13), + CheckItemDefinition(id = "wundumgebung_haematome", label = "Hämatome", order = 14), + ), + ), + BlockDefinition( + id = "fistel_tasche", + title = "Fistel / Tasche", + order = 10, + items = listOf( + CheckItemDefinition(id = "fistel_tasche_nein", label = "Nein", order = 1), + CheckItemDefinition(id = "fistel_tasche_ja", label = "Ja", order = 2), + CheckItemDefinition(id = "fistel_tasche_zirkulaer", label = "Zirkulär", order = 3), + CheckItemDefinition(id = "fistel_tasche_von_bis_uhr", label = "Von ... bis ... Uhr", order = 4, isFreeText = true), + CheckItemDefinition(id = "fistel_tasche_tiefe", label = "Tiefe (ca. cm)", order = 5, isFreeText = true), + ), + ), + BlockDefinition( + id = "entzuendungszeichen", + title = "Entzündungszeichen", + order = 11, + items = listOf( + CheckItemDefinition(id = "entzuendungszeichen_nein", label = "Nein", order = 1), + CheckItemDefinition(id = "entzuendungszeichen_temperatur_erhoeht", label = "Temperatur erhöht", order = 2), + CheckItemDefinition(id = "entzuendungszeichen_schwellung", label = "Schwellung", order = 3), + CheckItemDefinition(id = "entzuendungszeichen_roetung", label = "Rötung", order = 4), + CheckItemDefinition(id = "entzuendungszeichen_funktionsstoerung", label = "Funktionsstörung", order = 5), + CheckItemDefinition(id = "entzuendungszeichen_schmerz", label = "Schmerz", order = 6), + ), + ), + BlockDefinition( + id = "geruch", + title = "Geruch", + order = 12, + items = listOf( + CheckItemDefinition(id = "geruch_ja", label = "Ja", order = 1), + CheckItemDefinition(id = "geruch_nein", label = "Nein", order = 2), + CheckItemDefinition(id = "geruch_gering", label = "Gering", order = 3), + CheckItemDefinition(id = "geruch_deutlich_wahrnehmbar", label = "Deutlich wahrnehmbar", order = 4), + ), + ), + BlockDefinition( + id = "schmerz", + title = "Schmerz", + order = 13, + items = listOf( + CheckItemDefinition(id = "schmerz_ja", label = "Ja", order = 1), + CheckItemDefinition(id = "schmerz_nein", label = "Nein", order = 2), + CheckItemDefinition(id = "schmerz_immer", label = "Immer", order = 3), + CheckItemDefinition(id = "schmerz_zeitweise", label = "Zeitweise", order = 4), + CheckItemDefinition(id = "schmerz_bei_verbandwechsel", label = "Bei Verbandwechsel", order = 5), + CheckItemDefinition(id = "schmerz_vorwiegend_nachts", label = "Vorwiegend nachts", order = 6), + CheckItemDefinition(id = "schmerz_wunde", label = "Wunde", order = 7), + CheckItemDefinition(id = "schmerz_umgebung", label = "Umgebung", order = 8), + ), + ), + BlockDefinition( + id = "juckreiz", + title = "Juckreiz", + order = 14, + items = listOf( + CheckItemDefinition(id = "juckreiz_ja", label = "Ja", order = 1), + CheckItemDefinition(id = "juckreiz_nein", label = "Nein", order = 2), + CheckItemDefinition(id = "juckreiz_wunde", label = "Wunde", order = 3), + CheckItemDefinition(id = "juckreiz_umgebung", label = "Umgebung", order = 4), + CheckItemDefinition(id = "juckreiz_immer_oft", label = "Immer / oft", order = 5), + CheckItemDefinition(id = "juckreiz_zeitweise", label = "Zeitweise", order = 6), + ), + ), + BlockDefinition( + id = "vitalwerte", + title = "Vitalwerte", + order = 15, + items = emptyList(), + ), + BlockDefinition( + id = "auswirkungen_alltag", + title = "Auswirkungen auf das alltägliche Leben", + order = 16, + items = listOf( + CheckItemDefinition(id = "auswirkungen_alltag_ja", label = "Ja", order = 1), + CheckItemDefinition(id = "auswirkungen_alltag_nein", label = "Nein", order = 2), + CheckItemDefinition(id = "auswirkungen_alltag_nicht_ermittelbar", label = "Nicht ermittelbar", order = 3), + ), + ), + BlockDefinition( + id = "bekannte_allergie", + title = "Bekannte Allergie", + order = 17, + items = emptyList(), + ), + BlockDefinition( + id = "beratung_erfolgte", + title = "Beratung erfolgte", + order = 18, + items = listOf( + CheckItemDefinition(id = "beratung_verbandwechsel", label = "Verbandwechsel", order = 1), + CheckItemDefinition(id = "beratung_hygiene", label = "Hygiene", order = 2), + CheckItemDefinition(id = "beratung_ernaehrung", label = "Ernährung", order = 3), + CheckItemDefinition(id = "beratung_lagerung_druckentlastung", label = "Lagerung / Druckentlastung", order = 4), + CheckItemDefinition(id = "beratung_kleidung_schuhe", label = "Kleidung / Schuhe", order = 5), + CheckItemDefinition(id = "beratung_hautpflege", label = "Hautpflege", order = 6), + ), + ), + BlockDefinition( + id = "weitere_ueberlegung", + title = "Weitere Überlegung", + order = 19, + items = listOf( + CheckItemDefinition(id = "weitere_ueberlegung_hausarztvisite", label = "Hausarztvisite", order = 1), + CheckItemDefinition(id = "weitere_ueberlegung_kompressionstherapie", label = "Kompressionstherapie", order = 2), + CheckItemDefinition(id = "weitere_ueberlegung_wundabstrich", label = "Wundabstrich", order = 3), + CheckItemDefinition(id = "weitere_ueberlegung_angepasste_schmerztherapie", label = "Angepasste Schmerztherapie", order = 4), + CheckItemDefinition(id = "weitere_ueberlegung_anpassung_wundversorgung", label = "Anpassung der Wundversorgung", order = 5), + CheckItemDefinition(id = "weitere_ueberlegung_facharztliches_konsil", label = "Fachärztliches Konsil", order = 6), + CheckItemDefinition(id = "weitere_ueberlegung_ernaehrungsberatung", label = "Ernährungsberatung", order = 7), + CheckItemDefinition(id = "weitere_ueberlegung_pflegeberatung", label = "Pflegeberatung", order = 8), + CheckItemDefinition(id = "weitere_ueberlegung_hilfsmittel", label = "Hilfsmittel", order = 9), + CheckItemDefinition(id = "weitere_ueberlegung_bewegung_lagerungsplan", label = "Bewegung / Lagerungsplan", order = 10), + CheckItemDefinition(id = "weitere_ueberlegung_verbandwechsel_pflegedienst", label = "Verbandwechsel durch Pflegefachkräfte / Pflegedienst", order = 11), + CheckItemDefinition( + id = "weitere_ueberlegung_aerztliche_abklaerung_dringend", + label = "Ärztliche Abklärung zeitnah/dringend erforderlich / erbeten, Gefahr von bleibenden körperlichen Schäden / lebensbedrohlichen Prozessen", + order = 12, + ), + ), + ), + BlockDefinition( + id = "sonstiges", + title = "Sonstiges", + order = 20, + items = emptyList(), + ), + BlockDefinition( + id = "empfohlene_wundversorgung", + title = "Empfohlene Wundversorgung", + order = 21, + items = listOf( + CheckItemDefinition(id = "empfohlene_wundversorgung_hinweis_reinigen_mit", label = "Reinigen mit:", order = 1, isStaticHint = true), + 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), + CheckItemDefinition(id = "empfohlene_wundversorgung_granudacyn", label = "Granudacyn", order = 5), + CheckItemDefinition(id = "empfohlene_wundversorgung_getraenkte_kompresse", label = "Getränkte Kompresse für ca. ... Min. auflegen", order = 6, isFreeText = true), + 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, isFreeText = true), + CheckItemDefinition(id = "empfohlene_wundversorgung_hinweis_wundrandschutz_mit", label = "Wundrandschutz mit (bis 3x Wo.):", order = 14, isStaticHint = true), + CheckItemDefinition(id = "empfohlene_wundversorgung_secura", label = "Secura", order = 15), + CheckItemDefinition(id = "empfohlene_wundversorgung_cavilon", label = "Cavilon", order = 16), + CheckItemDefinition(id = "empfohlene_wundversorgung_hinweis_sekundaere_wundauflage", label = "Sekundäre Wundauflage:", order = 17, isStaticHint = true), + CheckItemDefinition(id = "empfohlene_wundversorgung_sekundaer_sterile_kompresse", label = "Sterile Kompresse", order = 18), + CheckItemDefinition(id = "empfohlene_wundversorgung_saugkompresse", label = "Saugkompresse", order = 19), + CheckItemDefinition(id = "empfohlene_wundversorgung_hinweis_fixieren_mit", label = "Fixieren mit:", order = 20, isStaticHint = true), + CheckItemDefinition(id = "empfohlene_wundversorgung_mullbinde", label = "Mullbinde", order = 21), + CheckItemDefinition(id = "empfohlene_wundversorgung_klebevlies", label = "Klebevlies", order = 22), + CheckItemDefinition(id = "empfohlene_wundversorgung_folienverband", label = "Folienverband", order = 23), + CheckItemDefinition(id = "empfohlene_wundversorgung_schlauchverband", label = "Schlauchverband", order = 24), + CheckItemDefinition(id = "empfohlene_wundversorgung_kohaesive_fixierbinde", label = "Kohäsive Fixierbinde", order = 25), + CheckItemDefinition(id = "empfohlene_wundversorgung_kompressionsverband", label = "Kompressionsverband", order = 26), + CheckItemDefinition(id = "empfohlene_wundversorgung_wechselintervall", label = "Wechselintervall", order = 27, isFreeText = true), + CheckItemDefinition(id = "empfohlene_wundversorgung_hinweis_bedarf", label = "+ bei Bedarf", order = 28, isStaticHint = true), + CheckItemDefinition( + id = "empfohlene_wundversorgung_hinweis_abklaerung", + label = "Generell: bei akuter Verschlechterung sofort ärztliche Abklärung", + order = 29, + isStaticHint = true, + ), + ), + ), + BlockDefinition( + id = "ziel", + title = "Ziel", + order = 22, + items = listOf( + CheckItemDefinition(id = "ziel_sauberes_wundmilieu", label = "Sauberes Wundmilieu", order = 1), + CheckItemDefinition(id = "ziel_infektionsprophylaxe", label = "Infektionsprophylaxe", order = 2), + CheckItemDefinition(id = "ziel_abpolsterung", label = "Abpolsterung", order = 3), + CheckItemDefinition(id = "ziel_schmerzreduktion", label = "Schmerzreduktion", order = 4), + CheckItemDefinition(id = "ziel_angepasstes_exsudatmanagement", label = "Angepasstes Exsudatmanagement", order = 5), + CheckItemDefinition(id = "ziel_stabile_wundverhaeltnisse", label = "Stabile Wundverhältnisse", order = 6), + 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), + ), + ), + BlockDefinition( + id = "kopie_nachrichtlich_an", + title = "Kopie nachrichtlich an", + order = 23, + items = listOf( + CheckItemDefinition(id = "kopie_nachrichtlich_an_arzt", label = "Ärztin / Arzt", order = 1, isFreeText = true), + CheckItemDefinition(id = "kopie_nachrichtlich_an_pflege", label = "Pflege", order = 2, isFreeText = true), + CheckItemDefinition(id = "kopie_nachrichtlich_an_patient", label = "Patient", order = 3, isFreeText = true), + CheckItemDefinition(id = "kopie_nachrichtlich_an_sonstige", label = "Sonstige", order = 4, isFreeText = true), + ), + ), + BlockDefinition( + id = "naechster_termin", + title = "Nächster Termin", + order = 24, + items = emptyList(), + ), ) fun findBlock(blockDefinitionId: String): BlockDefinition? = @@ -74,16 +413,39 @@ data class BlockDefinition( val items: List, ) { /** - * Stabile, aus der Block-ID abgeleitete ID für die automatisch angehängte Freitext-Option - * dieses Blocks (siehe DocumentationCatalog.FREE_TEXT_LABEL). Kein Bestandteil von [items], - * da die Freitext-Option kein fachlicher Katalog-Eintrag ist, sondern strukturell zu jedem - * Block gehört. + * Stabile, aus der Block-ID abgeleitete ID für die automatisch angehängte, generische + * Freitext-Option dieses Blocks (siehe DocumentationCatalog.FREE_TEXT_LABEL). Kein + * Bestandteil von [items], da diese generische Freitext-Option kein fachlicher + * Katalog-Eintrag ist, sondern strukturell zu JEDEM Block gehört - unabhängig davon, ob + * der Block selbst bereits eigene `isFreeText`-Einträge in [items] definiert. */ val freeTextDefinitionId: String get() = "${id}_freetext" } +/** + * Ein einzelner Katalog-Eintrag innerhalb eines Blocks. + * + * Normalfall: reine Checkbox ([isFreeText] = false, [isStaticHint] = false). + * + * [isFreeText] = true: eine vom Katalog selbst definierte, fachlich eigenständig benannte + * Freitext-Checkbox (z. B. "Erscheinung", "Wechselintervall", oder ein Empfänger bei "Kopie + * nachrichtlich an") - technisch identisch zur automatisch an JEDEN Block angehängten + * generischen Freitext-Option (s. DocumentationRepository.insertBlocksFromCatalog), nur mit + * eigenem Label statt "Freitext". Ein Block kann beliebig viele solcher Einträge definieren. + * + * [isStaticHint] = true: reiner, nicht editierbarer Hinweistext (z. B. "+ bei Bedarf" oder eine + * optische Zwischenüberschrift wie "Reinigen mit:") - [label] ist hier direkt der anzuzeigende + * Text selbst, keine Checkbox-Beschriftung. + * + * [isFreeText] und [isStaticHint] schließen sich fachlich gegenseitig aus (ein Eintrag ist + * entweder normale Checkbox, Freitext-Checkbox oder Hinweistext) - es gibt aber bewusst keine + * Laufzeitprüfung dafür, um die Katalogdefinition einfach zu halten; beide gleichzeitig auf + * `true` zu setzen ergibt schlicht keinen sinnvollen Katalogeintrag. + */ data class CheckItemDefinition( val id: String, val label: String, val order: Int, + val isFreeText: Boolean = false, + val isStaticHint: Boolean = false, ) diff --git a/app/src/main/java/de/medapp/wunddoku/domain/model/CheckItem.kt b/app/src/main/java/de/medapp/wunddoku/domain/model/CheckItem.kt index 8526ce7..83a9df5 100644 --- a/app/src/main/java/de/medapp/wunddoku/domain/model/CheckItem.kt +++ b/app/src/main/java/de/medapp/wunddoku/domain/model/CheckItem.kt @@ -9,12 +9,17 @@ package de.medapp.wunddoku.domain.model * historische Dokumentationen unabhängig von späteren Katalog-Änderungen unverändert korrekt * dargestellt werden. * - * [isFreeText]/[freeTextValue]: Der Freitext eines Blocks wird NICHT mehr als separates Feld am - * Block geführt, sondern als eine weitere, besondere Checkbox-Option behandelt - jeder Block - * bekommt automatisch genau ein CheckItem mit `isFreeText = true`. Ist [isFreeText] `false`, - * ist [freeTextValue] stets leer und ohne Bedeutung. Diese Struktur erlaubt es dem späteren - * PDF-Export, normale Optionen, die Freitext-Option und deren Inhalt eindeutig zu - * unterscheiden, ohne eine Sonderlogik außerhalb des bestehenden Checkbox-Modells zu benötigen. + * [isFreeText]/[freeTextValue]: Der Freitext eines Blocks wird NICHT als separates Feld am + * Block geführt, sondern als eine weitere, besondere Checkbox-Option behandelt. Jeder Block + * bekommt automatisch genau ein CheckItem mit `isFreeText = true`; zusätzlich kann der Katalog + * selbst beliebig viele weitere `isFreeText = true`-Optionen mit eigenem, fachlichem Label + * definieren (z. B. "Wundgrund", "Erscheinung", oder die vier Empfänger bei "Kopie + * nachrichtlich an") - technisch identisch, nur mit eigenem Label statt des generischen + * "Freitext". Ist [isFreeText] `false`, ist [freeTextValue] stets leer und ohne Bedeutung. + * + * [isStaticHint]: reiner, nicht editierbarer Hinweistext (z. B. "+ bei Bedarf") - keine + * Checkbox in der UI, [checked] ist bei solchen Zeilen technisch immer `true` (s. Entity- + * Kommentar), zählt aber NICHT als Bearbeitung (s. [de.medapp.wunddoku.domain.usecase.BlockEditedPolicy]). */ data class CheckItem( val checkItemStateId: String, @@ -24,4 +29,5 @@ data class CheckItem( val checked: Boolean, val isFreeText: Boolean = false, val freeTextValue: String = "", + val isStaticHint: Boolean = false, ) diff --git a/app/src/main/java/de/medapp/wunddoku/domain/usecase/BlockEditedPolicy.kt b/app/src/main/java/de/medapp/wunddoku/domain/usecase/BlockEditedPolicy.kt index 46fcef3..339d7ef 100644 --- a/app/src/main/java/de/medapp/wunddoku/domain/usecase/BlockEditedPolicy.kt +++ b/app/src/main/java/de/medapp/wunddoku/domain/usecase/BlockEditedPolicy.kt @@ -23,8 +23,15 @@ object BlockEditedPolicy { * nötig: Das Freitextfeld ist in der UI deaktiviert, solange seine Checkbox nicht angehakt * ist, ein Ankreuzen zählt also bereits als bewusste Bearbeitung, unabhängig davon, ob schon * Text eingegeben wurde. + * + * EINZIGE Ausnahme: Hinweistext-Zeilen (CheckItem.isStaticHint = true) werden hierbei + * IGNORIERT, obwohl ihr `checked` technisch immer `true` ist (s. Entity-Kommentar zu + * isStaticHint) - ein reiner, nicht editierbarer Hinweistext ist keine Nutzereingabe und + * darf einen Block nicht allein dadurch als "bearbeitet" erscheinen lassen (z. B. + * "Empfohlene Wundversorgung" enthält zwei feste Hinweiszeilen, soll aber nur dann als + * bearbeitet gelten, wenn tatsächlich eine Empfehlung eingetragen wurde). */ fun isEdited(block: DocumentationBlock): Boolean { - return block.items.any { it.checked } + return block.items.any { it.checked && !it.isStaticHint } } } 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 b1a2e72..07b4c78 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 @@ -59,6 +59,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext @@ -675,14 +676,14 @@ private fun BlockAccordionItem( verticalArrangement = Arrangement.spacedBy(12.dp), ) { for (item in block.items) { - if (item.isFreeText) { - FreeTextCheckItemRow( + when { + item.isStaticHint -> StaticHintRow(item = item) + item.isFreeText -> FreeTextCheckItemRow( item = item, onCheckedChanged = onCheckedChanged, onFreeTextChanged = onFreeTextChanged, ) - } else { - CheckItemRow(item = item, onCheckedChanged = onCheckedChanged) + else -> CheckItemRow(item = item, onCheckedChanged = onCheckedChanged) } } } @@ -735,3 +736,18 @@ private fun FreeTextCheckItemRow( ) } } + +/** + * Ein rein anzeigender, nicht editierbarer Hinweistext (z. B. "+ bei Bedarf" bei "Empfohlene + * Wundversorgung") - keine Checkbox, keine Interaktion, [item.label] ist direkt der + * anzuzeigende Text selbst. Optisch dezent (kursiv, gedämpfte Farbe) abgesetzt von echten, + * ausfüllbaren Optionen, damit auf den ersten Blick klar ist, dass hier nichts einzugeben ist. + */ +@Composable +private fun StaticHintRow(item: CheckItem) { + Text( + item.label, + style = MaterialTheme.typography.bodyMedium.copy(fontStyle = FontStyle.Italic), + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) +}