diff --git a/app/schemas/de.krisenvorrat.app.data.db.KrisenvorratDatabase/5.json b/app/schemas/de.krisenvorrat.app.data.db.KrisenvorratDatabase/5.json new file mode 100644 index 0000000..20c3cf2 --- /dev/null +++ b/app/schemas/de.krisenvorrat.app.data.db.KrisenvorratDatabase/5.json @@ -0,0 +1,314 @@ +{ + "formatVersion": 1, + "database": { + "version": 5, + "identityHash": "94ca0ddef5eb5333c781b3f97eff9c85", + "entities": [ + { + "tableName": "categories", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "locations", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "items", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `category_id` INTEGER NOT NULL, `quantity` REAL NOT NULL, `unit` TEXT NOT NULL, `unit_price` REAL NOT NULL, `kcal_per_unit` INTEGER, `expiry_date` TEXT, `location_id` INTEGER NOT NULL, `notes` TEXT NOT NULL, `last_updated` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`category_id`) REFERENCES `categories`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`location_id`) REFERENCES `locations`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryId", + "columnName": "category_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "quantity", + "columnName": "quantity", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "unit", + "columnName": "unit", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "unitPrice", + "columnName": "unit_price", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "kcalPerUnit", + "columnName": "kcal_per_unit", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "expiryDate", + "columnName": "expiry_date", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "locationId", + "columnName": "location_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notes", + "columnName": "notes", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "last_updated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_items_category_id", + "unique": false, + "columnNames": [ + "category_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_items_category_id` ON `${TABLE_NAME}` (`category_id`)" + }, + { + "name": "index_items_location_id", + "unique": false, + "columnNames": [ + "location_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_items_location_id` ON `${TABLE_NAME}` (`location_id`)" + } + ], + "foreignKeys": [ + { + "table": "categories", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "category_id" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "locations", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "location_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "settings", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT NOT NULL, PRIMARY KEY(`key`))", + "fields": [ + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "key" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "pending_sync_ops", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `item_id` TEXT NOT NULL, `operation` TEXT NOT NULL, `payload` TEXT NOT NULL, `created_at` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "itemId", + "columnName": "item_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "operation", + "columnName": "operation", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "payload", + "columnName": "payload", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `sender_id` TEXT NOT NULL, `sender_username` TEXT NOT NULL, `receiver_id` TEXT NOT NULL, `body` TEXT NOT NULL, `sent_at` INTEGER NOT NULL, `is_pending` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderUsername", + "columnName": "sender_username", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "receiverId", + "columnName": "receiver_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "body", + "columnName": "body", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sentAt", + "columnName": "sent_at", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPending", + "columnName": "is_pending", + "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, '94ca0ddef5eb5333c781b3f97eff9c85')" + ] + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/de/krisenvorrat/app/data/db/KrisenvorratDatabaseMigrationTest.kt b/app/src/androidTest/java/de/krisenvorrat/app/data/db/KrisenvorratDatabaseMigrationTest.kt index 28d7c3f..935643c 100644 --- a/app/src/androidTest/java/de/krisenvorrat/app/data/db/KrisenvorratDatabaseMigrationTest.kt +++ b/app/src/androidTest/java/de/krisenvorrat/app/data/db/KrisenvorratDatabaseMigrationTest.kt @@ -107,7 +107,7 @@ internal class KrisenvorratDatabaseMigrationTest { context, KrisenvorratDatabase::class.java, dbName - ).addMigrations(Migrations.MIGRATION_1_2, Migrations.MIGRATION_2_3, Migrations.MIGRATION_3_4).build() + ).addMigrations(Migrations.MIGRATION_1_2, Migrations.MIGRATION_2_3, Migrations.MIGRATION_3_4, Migrations.MIGRATION_4_5).build() private fun createV2Database() { val dbFile = context.getDatabasePath(dbName).also { it.parentFile?.mkdirs() } @@ -150,13 +150,13 @@ internal class KrisenvorratDatabaseMigrationTest { context, KrisenvorratDatabase::class.java, dbName - ).addMigrations(Migrations.MIGRATION_2_3, Migrations.MIGRATION_3_4).build() + ).addMigrations(Migrations.MIGRATION_2_3, Migrations.MIGRATION_3_4, Migrations.MIGRATION_4_5).build() private fun openMigratedDbV4() = Room.databaseBuilder( context, KrisenvorratDatabase::class.java, dbName - ).addMigrations(Migrations.MIGRATION_3_4).build() + ).addMigrations(Migrations.MIGRATION_3_4, Migrations.MIGRATION_4_5).build() private fun createV3Database() { val dbFile = context.getDatabasePath(dbName).also { it.parentFile?.mkdirs() } @@ -223,7 +223,7 @@ internal class KrisenvorratDatabaseMigrationTest { val item = items[0] assertEquals("item-uuid-1", item.id) assertEquals("Apfel", item.name) - assertEquals(52, item.kcalPerKg) + assertEquals(52, item.kcalPerUnit) assertEquals("Testnotiz", item.notes) assertEquals(5.0, item.quantity, 0.0) } finally { @@ -246,7 +246,7 @@ internal class KrisenvorratDatabaseMigrationTest { assertFalse("min_stock darf nicht mehr existieren", columns.contains("min_stock")) assertFalse("kcal_per_100g darf nicht mehr existieren", columns.contains("kcal_per_100g")) - assertTrue("kcal_per_kg muss existieren", columns.contains("kcal_per_kg")) + assertTrue("kcal_per_unit muss existieren", columns.contains("kcal_per_unit")) assertTrue("id muss existieren", columns.contains("id")) assertTrue("name muss existieren", columns.contains("name")) assertTrue("last_updated muss existieren", columns.contains("last_updated")) @@ -285,7 +285,7 @@ internal class KrisenvorratDatabaseMigrationTest { fun freshInstall_worksWithoutMigration() { // Fresh-Install: Room.onCreate() läuft direkt, keine Migration nötig val db = Room.inMemoryDatabaseBuilder(context, KrisenvorratDatabase::class.java) - .addMigrations(Migrations.MIGRATION_1_2, Migrations.MIGRATION_2_3, Migrations.MIGRATION_3_4) + .addMigrations(Migrations.MIGRATION_1_2, Migrations.MIGRATION_2_3, Migrations.MIGRATION_3_4, Migrations.MIGRATION_4_5) .build() try { // Tabellen anlegen und Basis-Operationen prüfen @@ -397,7 +397,7 @@ internal class KrisenvorratDatabaseMigrationTest { val item = items[0] assertEquals("item-uuid-1", item.id) assertEquals("Apfel", item.name) - assertEquals(52, item.kcalPerKg) + assertEquals(52, item.kcalPerUnit) // Alle Tabellen existieren val tables = mutableListOf() diff --git a/app/src/main/java/de/krisenvorrat/app/data/db/KrisenvorratDatabase.kt b/app/src/main/java/de/krisenvorrat/app/data/db/KrisenvorratDatabase.kt index ce291b8..d8b2bc4 100644 --- a/app/src/main/java/de/krisenvorrat/app/data/db/KrisenvorratDatabase.kt +++ b/app/src/main/java/de/krisenvorrat/app/data/db/KrisenvorratDatabase.kt @@ -18,7 +18,7 @@ import de.krisenvorrat.app.data.db.entity.SettingsEntity @Database( entities = [CategoryEntity::class, LocationEntity::class, ItemEntity::class, SettingsEntity::class, PendingSyncOpEntity::class, MessageEntity::class], - version = 4, + version = 5, exportSchema = true ) @TypeConverters(LocalDateConverter::class) diff --git a/app/src/main/java/de/krisenvorrat/app/data/db/Migrations.kt b/app/src/main/java/de/krisenvorrat/app/data/db/Migrations.kt index 4882b8f..9dee48c 100644 --- a/app/src/main/java/de/krisenvorrat/app/data/db/Migrations.kt +++ b/app/src/main/java/de/krisenvorrat/app/data/db/Migrations.kt @@ -112,4 +112,57 @@ internal object Migrations { ) } } + + /** + * V4 → V5: + * - `kcal_per_kg` umbenannt in `kcal_per_unit` + * + * SQLite unterstützt erst ab 3.25 (API 29) ALTER TABLE … RENAME COLUMN. + * Da minSdk = 26, wird die Tabelle neu erstellt und die Daten kopiert. + */ + val MIGRATION_4_5 = object : Migration(4, 5) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ + CREATE TABLE `items_new` ( + `id` TEXT NOT NULL, + `name` TEXT NOT NULL, + `category_id` INTEGER NOT NULL, + `quantity` REAL NOT NULL, + `unit` TEXT NOT NULL, + `unit_price` REAL NOT NULL, + `kcal_per_unit` INTEGER, + `expiry_date` TEXT, + `location_id` INTEGER NOT NULL, + `notes` TEXT NOT NULL, + `last_updated` INTEGER NOT NULL, + PRIMARY KEY(`id`), + FOREIGN KEY(`category_id`) REFERENCES `categories`(`id`) + ON UPDATE NO ACTION ON DELETE CASCADE, + FOREIGN KEY(`location_id`) REFERENCES `locations`(`id`) + ON UPDATE NO ACTION ON DELETE CASCADE + ) + """.trimIndent() + ) + db.execSQL( + """ + INSERT INTO `items_new` + (id, name, category_id, quantity, unit, unit_price, + kcal_per_unit, expiry_date, location_id, notes, last_updated) + SELECT + id, name, category_id, quantity, unit, unit_price, + kcal_per_kg, expiry_date, location_id, notes, last_updated + FROM `items` + """.trimIndent() + ) + db.execSQL("DROP TABLE `items`") + db.execSQL("ALTER TABLE `items_new` RENAME TO `items`") + db.execSQL( + "CREATE INDEX IF NOT EXISTS `index_items_category_id` ON `items` (`category_id`)" + ) + db.execSQL( + "CREATE INDEX IF NOT EXISTS `index_items_location_id` ON `items` (`location_id`)" + ) + } + } } diff --git a/app/src/main/java/de/krisenvorrat/app/data/db/entity/ItemEntity.kt b/app/src/main/java/de/krisenvorrat/app/data/db/entity/ItemEntity.kt index 68e254e..14d4439 100644 --- a/app/src/main/java/de/krisenvorrat/app/data/db/entity/ItemEntity.kt +++ b/app/src/main/java/de/krisenvorrat/app/data/db/entity/ItemEntity.kt @@ -32,7 +32,7 @@ internal data class ItemEntity( @ColumnInfo(name = "quantity") val quantity: Double, @ColumnInfo(name = "unit") val unit: String, @ColumnInfo(name = "unit_price") val unitPrice: Double, - @ColumnInfo(name = "kcal_per_kg") val kcalPerKg: Int?, + @ColumnInfo(name = "kcal_per_unit") val kcalPerUnit: Int?, @ColumnInfo(name = "expiry_date") val expiryDate: LocalDate?, @ColumnInfo(name = "location_id") val locationId: Int, @ColumnInfo(name = "notes") val notes: String, diff --git a/app/src/main/java/de/krisenvorrat/app/data/export/CsvExporter.kt b/app/src/main/java/de/krisenvorrat/app/data/export/CsvExporter.kt index 92cd0e4..0ba5037 100644 --- a/app/src/main/java/de/krisenvorrat/app/data/export/CsvExporter.kt +++ b/app/src/main/java/de/krisenvorrat/app/data/export/CsvExporter.kt @@ -14,7 +14,7 @@ internal object CsvExporter { private val HEADER = listOf( "Name", "Kategorie", "Menge", "Einheit", "Stückpreis", - "kcal/kg", "MHD", "Lagerort", "Notizen" + "kcal/Einheit", "MHD", "Lagerort", "Notizen" ) fun export( @@ -36,7 +36,7 @@ internal object CsvExporter { formatQuantity(item.quantity), escapeCsv(item.unit), formatPrice(item.unitPrice), - item.kcalPerKg?.toString() ?: "", + item.kcalPerUnit?.toString() ?: "", item.expiryDate?.format(DATE_FORMATTER) ?: "", escapeCsv(locationMap[item.locationId] ?: ""), escapeCsv(item.notes) diff --git a/app/src/main/java/de/krisenvorrat/app/data/export/ImportExportRepositoryImpl.kt b/app/src/main/java/de/krisenvorrat/app/data/export/ImportExportRepositoryImpl.kt index 835a69b..4739aab 100644 --- a/app/src/main/java/de/krisenvorrat/app/data/export/ImportExportRepositoryImpl.kt +++ b/app/src/main/java/de/krisenvorrat/app/data/export/ImportExportRepositoryImpl.kt @@ -67,7 +67,7 @@ internal class ImportExportRepositoryImpl @Inject constructor( quantity = item.quantity, unit = item.unit, unitPrice = item.unitPrice, - kcalPerKg = item.kcalPerKg, + kcalPerUnit = item.kcalPerUnit, expiryDate = item.expiryDate?.toString(), locationId = item.locationId, notes = item.notes, @@ -112,7 +112,7 @@ internal class ImportExportRepositoryImpl @Inject constructor( quantity = item.quantity, unit = item.unit, unitPrice = item.unitPrice, - kcalPerKg = item.kcalPerKg, + kcalPerUnit = item.kcalPerUnit, expiryDate = item.expiryDate?.let { LocalDate.parse(it) }, locationId = item.locationId, notes = item.notes, diff --git a/app/src/main/java/de/krisenvorrat/app/data/remote/OpenAiVisionService.kt b/app/src/main/java/de/krisenvorrat/app/data/remote/OpenAiVisionService.kt index 6d960ce..0270d77 100644 --- a/app/src/main/java/de/krisenvorrat/app/data/remote/OpenAiVisionService.kt +++ b/app/src/main/java/de/krisenvorrat/app/data/remote/OpenAiVisionService.kt @@ -92,7 +92,7 @@ internal class OpenAiVisionServiceImpl @Inject constructor( "Erkenne alle Nahrungsmittel und Produkte in diesem Foto. " + "Antworte ausschließlich im JSON-Format: " + "{\"items\": [{\"name\": \"Produktname\", \"suggestedCategoryName\": \"Kategoriename\", " + - "\"unit\": \"Einheit\", \"kcalPerKg\": , \"notes\": \"Zusatzinfos\"}]}. " + + "\"unit\": \"Einheit\", \"kcalPerUnit\": , \"notes\": \"Zusatzinfos\"}]}. " + "Verwende deutsche Produktnamen." } } diff --git a/app/src/main/java/de/krisenvorrat/app/data/repository/ItemRepositoryImpl.kt b/app/src/main/java/de/krisenvorrat/app/data/repository/ItemRepositoryImpl.kt index 279809b..303d44b 100644 --- a/app/src/main/java/de/krisenvorrat/app/data/repository/ItemRepositoryImpl.kt +++ b/app/src/main/java/de/krisenvorrat/app/data/repository/ItemRepositoryImpl.kt @@ -155,7 +155,7 @@ internal class ItemRepositoryImpl @Inject constructor( quantity = quantity, unit = unit, unitPrice = unitPrice, - kcalPerKg = kcalPerKg, + kcalPerUnit = kcalPerUnit, expiryDate = expiryDate?.toString(), locationId = locationId, notes = notes, diff --git a/app/src/main/java/de/krisenvorrat/app/di/DatabaseModule.kt b/app/src/main/java/de/krisenvorrat/app/di/DatabaseModule.kt index b53c85a..3003fdf 100644 --- a/app/src/main/java/de/krisenvorrat/app/di/DatabaseModule.kt +++ b/app/src/main/java/de/krisenvorrat/app/di/DatabaseModule.kt @@ -30,7 +30,7 @@ internal object DatabaseModule { fun provideDatabase(@ApplicationContext context: Context): KrisenvorratDatabase = Room.databaseBuilder(context, KrisenvorratDatabase::class.java, "krisenvorrat.db") .addCallback(DefaultDataCallback) - .addMigrations(Migrations.MIGRATION_1_2, Migrations.MIGRATION_2_3, Migrations.MIGRATION_3_4) + .addMigrations(Migrations.MIGRATION_1_2, Migrations.MIGRATION_2_3, Migrations.MIGRATION_3_4, Migrations.MIGRATION_4_5) .build() private object DefaultDataCallback : RoomDatabase.Callback() { diff --git a/app/src/main/java/de/krisenvorrat/app/domain/model/ItemFormPrefill.kt b/app/src/main/java/de/krisenvorrat/app/domain/model/ItemFormPrefill.kt index 3dd0eec..4c59759 100644 --- a/app/src/main/java/de/krisenvorrat/app/domain/model/ItemFormPrefill.kt +++ b/app/src/main/java/de/krisenvorrat/app/domain/model/ItemFormPrefill.kt @@ -7,6 +7,6 @@ internal data class ItemFormPrefill( val name: String = "", val suggestedCategoryName: String = "", val unit: String = "", - val kcalPerKg: Int? = null, + val kcalPerUnit: Int? = null, val notes: String = "" ) diff --git a/app/src/main/java/de/krisenvorrat/app/domain/usecase/CalculateSupplyRangeUseCase.kt b/app/src/main/java/de/krisenvorrat/app/domain/usecase/CalculateSupplyRangeUseCase.kt index 3db6fa7..190df97 100644 --- a/app/src/main/java/de/krisenvorrat/app/domain/usecase/CalculateSupplyRangeUseCase.kt +++ b/app/src/main/java/de/krisenvorrat/app/domain/usecase/CalculateSupplyRangeUseCase.kt @@ -19,20 +19,10 @@ internal class CalculateSupplyRangeUseCase @Inject constructor() { if (dailyNeed <= 0) return 0.0 val totalKcal = items.sumOf { item -> - val kcalPerKg = item.kcalPerKg ?: return@sumOf 0.0 - val grams = convertToGrams(item.quantity, item.unit) ?: return@sumOf 0.0 - (grams / 1000.0) * kcalPerKg + val kcalPerUnit = item.kcalPerUnit ?: return@sumOf 0.0 + item.quantity * kcalPerUnit } return totalKcal / dailyNeed } - - private fun convertToGrams(quantity: Double, unit: String): Double? { - return when (unit.lowercase().trim()) { - "g" -> quantity - "kg" -> quantity * 1000.0 - "mg" -> quantity / 1000.0 - else -> null - } - } } diff --git a/app/src/main/java/de/krisenvorrat/app/ui/item/ItemFormScreen.kt b/app/src/main/java/de/krisenvorrat/app/ui/item/ItemFormScreen.kt index 4716fc5..9efa1f8 100644 --- a/app/src/main/java/de/krisenvorrat/app/ui/item/ItemFormScreen.kt +++ b/app/src/main/java/de/krisenvorrat/app/ui/item/ItemFormScreen.kt @@ -179,9 +179,9 @@ internal fun ItemFormScreen( // kcal/kg OutlinedTextField( - value = uiState.kcalPerKg, - onValueChange = viewModel::updateKcalPerKg, - label = { Text("kcal / kg") }, + value = uiState.kcalPerUnit, + onValueChange = viewModel::updateKcalPerUnit, + label = { Text("kcal / Einheit") }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), singleLine = true, modifier = Modifier.fillMaxWidth() diff --git a/app/src/main/java/de/krisenvorrat/app/ui/item/ItemFormViewModel.kt b/app/src/main/java/de/krisenvorrat/app/ui/item/ItemFormViewModel.kt index 3e36773..dd24e23 100644 --- a/app/src/main/java/de/krisenvorrat/app/ui/item/ItemFormViewModel.kt +++ b/app/src/main/java/de/krisenvorrat/app/ui/item/ItemFormViewModel.kt @@ -29,7 +29,7 @@ internal data class ItemFormUiState( val quantity: String = "", val unit: String = "", val unitPrice: String = "", - val kcalPerKg: String = "", + val kcalPerUnit: String = "", val expiryDate: LocalDate? = null, val locationId: Int? = null, val notes: String = "", @@ -115,7 +115,7 @@ internal class ItemFormViewModel @Inject constructor( state.copy( name = prefill.name, unit = prefill.unit, - kcalPerKg = prefill.kcalPerKg?.toString() ?: "", + kcalPerUnit = prefill.kcalPerUnit?.toString() ?: "", notes = prefill.notes ) } @@ -142,7 +142,7 @@ internal class ItemFormViewModel @Inject constructor( quantity = item.quantity.toBigDecimal().stripTrailingZeros().toPlainString(), unit = item.unit, unitPrice = item.unitPrice.toBigDecimal().stripTrailingZeros().toPlainString(), - kcalPerKg = item.kcalPerKg?.toString() ?: "", + kcalPerUnit = item.kcalPerUnit?.toString() ?: "", expiryDate = item.expiryDate, locationId = item.locationId, notes = item.notes, @@ -178,8 +178,8 @@ internal class ItemFormViewModel @Inject constructor( _uiState.update { it.copy(unitPrice = value) } } - fun updateKcalPerKg(value: String) { - _uiState.update { it.copy(kcalPerKg = value) } + fun updateKcalPerUnit(value: String) { + _uiState.update { it.copy(kcalPerUnit = value) } } fun updateExpiryDate(value: LocalDate?) { @@ -211,7 +211,7 @@ internal class ItemFormViewModel @Inject constructor( quantity = state.quantity.toDouble(), unit = state.unit.trim(), unitPrice = state.unitPrice.toDoubleOrNull() ?: 0.0, - kcalPerKg = state.kcalPerKg.toIntOrNull(), + kcalPerUnit = state.kcalPerUnit.toIntOrNull(), expiryDate = state.expiryDate, locationId = state.locationId!!, notes = state.notes.trim(), diff --git a/app/src/main/java/de/krisenvorrat/app/ui/item/ItemListViewModel.kt b/app/src/main/java/de/krisenvorrat/app/ui/item/ItemListViewModel.kt index 9f6323a..efaf88c 100644 --- a/app/src/main/java/de/krisenvorrat/app/ui/item/ItemListViewModel.kt +++ b/app/src/main/java/de/krisenvorrat/app/ui/item/ItemListViewModel.kt @@ -178,7 +178,7 @@ internal class ItemListViewModel @Inject constructor( quantity = item.quantity, unit = item.unit, unitPrice = 0.0, - kcalPerKg = null, + kcalPerUnit = null, expiryDate = item.expiryDate, locationId = 0, notes = "", diff --git a/app/src/test/java/de/krisenvorrat/app/data/export/CsvExporterTest.kt b/app/src/test/java/de/krisenvorrat/app/data/export/CsvExporterTest.kt index f728102..4196feb 100644 --- a/app/src/test/java/de/krisenvorrat/app/data/export/CsvExporterTest.kt +++ b/app/src/test/java/de/krisenvorrat/app/data/export/CsvExporterTest.kt @@ -20,7 +20,7 @@ class CsvExporterTest { quantity = 5.0, unit = "Stk", unitPrice = 1.50, - kcalPerKg = 800, + kcalPerUnit = 800, expiryDate = LocalDate.of(2027, 3, 15), locationId = 1 ) @@ -33,7 +33,7 @@ class CsvExporterTest { assertTrue(csv.startsWith("\uFEFF")) val lines = csv.lines().filter { it.isNotBlank() } assertEquals(2, lines.size) - assertEquals("\uFEFFName;Kategorie;Menge;Einheit;Stückpreis;kcal/kg;MHD;Lagerort;Notizen", lines[0]) + assertEquals("\uFEFFName;Kategorie;Menge;Einheit;Stückpreis;kcal/Einheit;MHD;Lagerort;Notizen", lines[0]) assertEquals("Konserve;Lebensmittel;5;Stk;1,50;800;15.03.2027;Keller;", lines[1]) } diff --git a/app/src/test/java/de/krisenvorrat/app/data/export/ImportExportRepositoryImplTest.kt b/app/src/test/java/de/krisenvorrat/app/data/export/ImportExportRepositoryImplTest.kt index 0869396..6ed309c 100644 --- a/app/src/test/java/de/krisenvorrat/app/data/export/ImportExportRepositoryImplTest.kt +++ b/app/src/test/java/de/krisenvorrat/app/data/export/ImportExportRepositoryImplTest.kt @@ -68,7 +68,7 @@ class ImportExportRepositoryImplTest { @Test fun test_importFromJson_withValidJson_upsertAllEntities() = runBlocking { // Given - val validJson = """{"version":1,"categories":[{"id":1,"name":"Lebensmittel"}],"locations":[{"id":1,"name":"Keller"}],"items":[{"id":"item1","name":"Konserve","categoryId":1,"quantity":2.0,"unit":"Stk","unitPrice":1.5,"kcalPerKg":null,"expiryDate":null,"locationId":1,"notes":"","lastUpdated":0}],"settings":[{"key":"theme","value":"dark"}]}""" + val validJson = """{"version":1,"categories":[{"id":1,"name":"Lebensmittel"}],"locations":[{"id":1,"name":"Keller"}],"items":[{"id":"item1","name":"Konserve","categoryId":1,"quantity":2.0,"unit":"Stk","unitPrice":1.5,"kcalPerUnit":null,"expiryDate":null,"locationId":1,"notes":"","lastUpdated":0}],"settings":[{"key":"theme","value":"dark"}]}""" val categoryDao = FakeCategoryDao() val locationDao = FakeLocationDao() val itemDao = FakeItemDao() @@ -249,7 +249,7 @@ class ImportExportRepositoryImplTest { val itemDao = FakeItemDao() itemDao.upsertAll(listOf(buildItemEntity("item1").copy(name = "Lokal", lastUpdated = 1000L))) val repository = buildRepository(itemDao = itemDao) - val json = """{"version":1,"categories":[],"locations":[],"items":[{"id":"item1","name":"VomServer","categoryId":1,"quantity":2.0,"unit":"Stk","unitPrice":1.5,"kcalPerKg":null,"expiryDate":null,"locationId":1,"notes":"","lastUpdated":500}],"settings":[]}""" + val json = """{"version":1,"categories":[],"locations":[],"items":[{"id":"item1","name":"VomServer","categoryId":1,"quantity":2.0,"unit":"Stk","unitPrice":1.5,"kcalPerUnit":null,"expiryDate":null,"locationId":1,"notes":"","lastUpdated":500}],"settings":[]}""" // When repository.importFromJson(json) @@ -264,7 +264,7 @@ class ImportExportRepositoryImplTest { val itemDao = FakeItemDao() itemDao.upsertAll(listOf(buildItemEntity("item2").copy(name = "Lokal", lastUpdated = 100L))) val repository = buildRepository(itemDao = itemDao) - val json = """{"version":1,"categories":[],"locations":[],"items":[{"id":"item2","name":"VomServer","categoryId":1,"quantity":2.0,"unit":"Stk","unitPrice":1.5,"kcalPerKg":null,"expiryDate":null,"locationId":1,"notes":"","lastUpdated":2000}],"settings":[]}""" + val json = """{"version":1,"categories":[],"locations":[],"items":[{"id":"item2","name":"VomServer","categoryId":1,"quantity":2.0,"unit":"Stk","unitPrice":1.5,"kcalPerUnit":null,"expiryDate":null,"locationId":1,"notes":"","lastUpdated":2000}],"settings":[]}""" // When repository.importFromJson(json) @@ -300,7 +300,7 @@ class ImportExportRepositoryImplTest { // Then assertTrue(csv.startsWith("\uFEFF")) - assertTrue(csv.contains("Name;Kategorie;Menge;Einheit;Stückpreis;kcal/kg;MHD;Lagerort;Notizen")) + assertTrue(csv.contains("Name;Kategorie;Menge;Einheit;Stückpreis;kcal/Einheit;MHD;Lagerort;Notizen")) assertTrue(csv.contains("Konserve;Lebensmittel;5;Stk;1,50;;15.03.2027;Keller;")) } @@ -360,7 +360,7 @@ class ImportExportRepositoryImplTest { } @Test - fun test_exportToCsv_withKcalPerKg_includesValue() = runBlocking { + fun test_exportToCsv_withkcalPerUnit_includesValue() = runBlocking { // Given val categoryDao = FakeCategoryDao() val locationDao = FakeLocationDao() @@ -368,7 +368,7 @@ class ImportExportRepositoryImplTest { categoryDao.upsertAll(listOf(CategoryEntity(id = 1, name = "Lebensmittel"))) locationDao.upsertAll(listOf(LocationEntity(id = 1, name = "Keller"))) itemDao.upsertAll(listOf( - buildItemEntity("item1").copy(name = "Reis", kcalPerKg = 3500) + buildItemEntity("item1").copy(name = "Reis", kcalPerUnit = 3500) )) val repository = buildRepository(categoryDao, locationDao, itemDao) diff --git a/app/src/test/java/de/krisenvorrat/app/data/export/JsonRoundtripTest.kt b/app/src/test/java/de/krisenvorrat/app/data/export/JsonRoundtripTest.kt index 736fd4c..20bcef0 100644 --- a/app/src/test/java/de/krisenvorrat/app/data/export/JsonRoundtripTest.kt +++ b/app/src/test/java/de/krisenvorrat/app/data/export/JsonRoundtripTest.kt @@ -44,7 +44,7 @@ class JsonRoundtripTest { quantity = 10.0, unit = "Stk", unitPrice = 2.49, - kcalPerKg = 180, + kcalPerUnit = 180, expiryDate = LocalDate.of(2027, 6, 15), locationId = 1, notes = "Ravioli", @@ -57,7 +57,7 @@ class JsonRoundtripTest { quantity = 3.0, unit = "Stk", unitPrice = 0.99, - kcalPerKg = null, + kcalPerUnit = null, expiryDate = null, locationId = 2, notes = "", @@ -119,7 +119,7 @@ class JsonRoundtripTest { assertEquals(original.quantity, imported?.quantity) assertEquals(original.unit, imported?.unit) assertEquals(original.unitPrice, imported?.unitPrice) - assertEquals(original.kcalPerKg, imported?.kcalPerKg) + assertEquals(original.kcalPerUnit, imported?.kcalPerUnit) assertEquals(original.expiryDate, imported?.expiryDate) assertEquals(original.locationId, imported?.locationId) assertEquals(original.notes, imported?.notes) @@ -151,7 +151,7 @@ class JsonRoundtripTest { quantity = 1.0, unit = "Stk", unitPrice = 0.0, - kcalPerKg = null, + kcalPerUnit = null, expiryDate = null, locationId = 1, notes = "", @@ -172,7 +172,7 @@ class JsonRoundtripTest { // Then assertTrue(result.isSuccess) val imported = importItemDao.getItems().first() - assertEquals(null, imported.kcalPerKg) + assertEquals(null, imported.kcalPerUnit) assertEquals(null, imported.expiryDate) } diff --git a/app/src/test/java/de/krisenvorrat/app/data/export/TestFakes.kt b/app/src/test/java/de/krisenvorrat/app/data/export/TestFakes.kt index 0a0a0cd..2e0033b 100644 --- a/app/src/test/java/de/krisenvorrat/app/data/export/TestFakes.kt +++ b/app/src/test/java/de/krisenvorrat/app/data/export/TestFakes.kt @@ -126,7 +126,7 @@ internal fun buildItemEntity(id: String = "item1") = ItemEntity( quantity = 2.0, unit = "Stk", unitPrice = 1.5, - kcalPerKg = null, + kcalPerUnit = null, expiryDate = null, locationId = 1, notes = "", diff --git a/app/src/test/java/de/krisenvorrat/app/data/repository/ItemRepositoryImplTest.kt b/app/src/test/java/de/krisenvorrat/app/data/repository/ItemRepositoryImplTest.kt index f22ff61..c8b87a7 100644 --- a/app/src/test/java/de/krisenvorrat/app/data/repository/ItemRepositoryImplTest.kt +++ b/app/src/test/java/de/krisenvorrat/app/data/repository/ItemRepositoryImplTest.kt @@ -186,7 +186,7 @@ private fun buildItem( quantity = 2.0, unit = "Stk", unitPrice = 1.5, - kcalPerKg = null, + kcalPerUnit = null, expiryDate = null, locationId = locationId, notes = "", @@ -405,7 +405,7 @@ class ItemRepositoryImplTest { id = "op1", itemId = "drain1", operation = "PATCH", - payload = """{"id":"drain1","name":"Konserve","categoryId":1,"quantity":2.0,"unit":"Stk","unitPrice":1.5,"kcalPerKg":null,"expiryDate":null,"locationId":1,"notes":"","lastUpdated":0}""", + payload = """{"id":"drain1","name":"Konserve","categoryId":1,"quantity":2.0,"unit":"Stk","unitPrice":1.5,"kcalPerUnit":null,"expiryDate":null,"locationId":1,"notes":"","lastUpdated":0}""", createdAt = 1000L ) ) diff --git a/app/src/test/java/de/krisenvorrat/app/data/sync/SyncServiceImplTest.kt b/app/src/test/java/de/krisenvorrat/app/data/sync/SyncServiceImplTest.kt index 2f38ff9..ce68f0d 100644 --- a/app/src/test/java/de/krisenvorrat/app/data/sync/SyncServiceImplTest.kt +++ b/app/src/test/java/de/krisenvorrat/app/data/sync/SyncServiceImplTest.kt @@ -48,7 +48,7 @@ class SyncServiceImplTest { quantity = 5.0, unit = "Dose", unitPrice = 1.29, - kcalPerKg = 100, + kcalPerUnit = 100, expiryDate = "2027-06-01", locationId = 1, notes = "", diff --git a/app/src/test/java/de/krisenvorrat/app/domain/usecase/CalculateSupplyRangeUseCaseTest.kt b/app/src/test/java/de/krisenvorrat/app/domain/usecase/CalculateSupplyRangeUseCaseTest.kt index df8e820..15f27fe 100644 --- a/app/src/test/java/de/krisenvorrat/app/domain/usecase/CalculateSupplyRangeUseCaseTest.kt +++ b/app/src/test/java/de/krisenvorrat/app/domain/usecase/CalculateSupplyRangeUseCaseTest.kt @@ -9,10 +9,10 @@ class CalculateSupplyRangeUseCaseTest { @Test fun test_invoke_withKgItems_returnsCorrectDays() { - // Given – 2 kg Reis à 3500 kcal/kg = 7000 kcal + // Given – 2 Packungen à 3500 kcal/Packung = 7000 kcal // 4000 kcal/Tag (2 × 2000) → 1.75 Tage val items = listOf( - buildTestItem(id = "1", quantity = 2.0, unit = "kg", kcalPerKg = 3500) + buildTestItem(id = "1", quantity = 2.0, unit = "Packung", kcalPerUnit = 3500) ) // When @@ -24,10 +24,10 @@ class CalculateSupplyRangeUseCaseTest { @Test fun test_invoke_withGramItems_returnsCorrectDays() { - // Given – 500 g Nudeln à 3600 kcal/kg = 1800 kcal + // Given – 5 Stk à 360 kcal/Stk = 1800 kcal // 2000 kcal/Tag → 0.9 Tage val items = listOf( - buildTestItem(id = "1", quantity = 500.0, unit = "g", kcalPerKg = 3600) + buildTestItem(id = "1", quantity = 5.0, unit = "Stk", kcalPerUnit = 360) ) // When @@ -39,11 +39,11 @@ class CalculateSupplyRangeUseCaseTest { @Test fun test_invoke_withMultipleItems_sumsTotalKcal() { - // Given – 1 kg Reis (3500 kcal/kg = 3500 kcal) + 500 g Nudeln (3600 kcal/kg = 1800 kcal) = 5300 kcal + // Given – 1 Packung (3500 kcal/Packung) + 5 Stk (360 kcal/Stk = 1800 kcal) = 5300 kcal // 4000 kcal/Tag → 1.325 Tage val items = listOf( - buildTestItem(id = "1", quantity = 1.0, unit = "kg", kcalPerKg = 3500), - buildTestItem(id = "2", quantity = 500.0, unit = "g", kcalPerKg = 3600) + buildTestItem(id = "1", quantity = 1.0, unit = "Packung", kcalPerUnit = 3500), + buildTestItem(id = "2", quantity = 5.0, unit = "Stk", kcalPerUnit = 360) ) // When @@ -64,13 +64,13 @@ class CalculateSupplyRangeUseCaseTest { @Test fun test_invoke_withNullKcal_skipsItem() { - // Given – Item ohne kcalPerKg wird ignoriert + // Given – Item ohne kcalPerUnit wird ignoriert val items = listOf( - buildTestItem(id = "1", quantity = 1.0, unit = "kg", kcalPerKg = null), - buildTestItem(id = "2", quantity = 1.0, unit = "kg", kcalPerKg = 2000) + buildTestItem(id = "1", quantity = 1.0, unit = "Stk", kcalPerUnit = null), + buildTestItem(id = "2", quantity = 1.0, unit = "Stk", kcalPerUnit = 2000) ) - // When – Nur 1 kg à 2000 kcal/kg = 2000 kcal, 2000 kcal/Tag = 1.0 Tage + // When – Nur 1 Stk à 2000 kcal/Stk = 2000 kcal, 2000 kcal/Tag = 1.0 Tage val result = useCase(items, totalDailyKcal = 2000) // Then @@ -78,24 +78,24 @@ class CalculateSupplyRangeUseCaseTest { } @Test - fun test_invoke_withNonWeightUnit_skipsItem() { - // Given – "Stk" ist keine Gewichtseinheit → wird ignoriert + fun test_invoke_withAnyUnit_isIncluded() { + // Given – Jede Einheit wird gewertet, kein Unit-Filter mehr val items = listOf( - buildTestItem(id = "1", quantity = 5.0, unit = "Stk", kcalPerKg = 2000) + buildTestItem(id = "1", quantity = 5.0, unit = "Stk", kcalPerUnit = 2000) ) - // When + // When – 5 × 2000 = 10000 kcal / 4000 = 2.5 Tage val result = useCase(items, totalDailyKcal = 4000) // Then - assertEquals(0.0, result, 0.001) + assertEquals(2.5, result, 0.001) } @Test fun test_invoke_withZeroTotalDailyKcal_returnsZero() { // Given val items = listOf( - buildTestItem(id = "1", quantity = 1.0, unit = "kg", kcalPerKg = 3500) + buildTestItem(id = "1", quantity = 1.0, unit = "Stk", kcalPerUnit = 3500) ) // When @@ -107,10 +107,10 @@ class CalculateSupplyRangeUseCaseTest { @Test fun test_invoke_withDefaultParameters_uses4000KcalPerDay() { - // Given – 4 kg Reis à 3500 kcal/kg = 14000 kcal + // Given – 4 Packungen à 3500 kcal/Packung = 14000 kcal // Default: 4000 kcal/Tag → 3.5 Tage val items = listOf( - buildTestItem(id = "1", quantity = 4.0, unit = "kg", kcalPerKg = 3500) + buildTestItem(id = "1", quantity = 4.0, unit = "Packung", kcalPerUnit = 3500) ) // When @@ -121,11 +121,11 @@ class CalculateSupplyRangeUseCaseTest { } @Test - fun test_invoke_withMgUnit_convertsCorrectly() { - // Given – 500000 mg = 500 g à 2000 kcal/kg = 1000 kcal + fun test_invoke_withVariousUnits_allCounted() { + // Given – Beliebige Einheit: 5 Stk à 200 kcal/Stk = 1000 kcal // 1000 kcal/Tag → 1.0 Tag val items = listOf( - buildTestItem(id = "1", quantity = 500000.0, unit = "mg", kcalPerKg = 2000) + buildTestItem(id = "1", quantity = 5.0, unit = "Stk", kcalPerUnit = 200) ) // When @@ -136,19 +136,18 @@ class CalculateSupplyRangeUseCaseTest { } @Test - fun test_invoke_withMixedUnits_onlyCountsWeightBased() { - // Given – 1 kg (3500 kcal/kg) + 5 Stk (ignored) + 2 L (ignored) - // Total: 3500 kcal, 2000 kcal/Tag → 1.75 Tage + fun test_invoke_withMixedUnits_allCounted() { + // Given – kg, Stk, L werden alle berücksichtigt val items = listOf( - buildTestItem(id = "1", quantity = 1.0, unit = "kg", kcalPerKg = 3500), - buildTestItem(id = "2", quantity = 5.0, unit = "Stk", kcalPerKg = 1000), - buildTestItem(id = "3", quantity = 2.0, unit = "L", kcalPerKg = 450) + buildTestItem(id = "1", quantity = 1.0, unit = "kg", kcalPerUnit = 3500), + buildTestItem(id = "2", quantity = 5.0, unit = "Stk", kcalPerUnit = 1000), + buildTestItem(id = "3", quantity = 2.0, unit = "L", kcalPerUnit = 450) ) - // When + // When – 3500 + 5000 + 900 = 9400 kcal / 2000 = 4.7 Tage val result = useCase(items, totalDailyKcal = 2000) // Then - assertEquals(1.75, result, 0.001) + assertEquals(4.7, result, 0.001) } } diff --git a/app/src/test/java/de/krisenvorrat/app/domain/usecase/TestHelpers.kt b/app/src/test/java/de/krisenvorrat/app/domain/usecase/TestHelpers.kt index 00e6e66..c186230 100644 --- a/app/src/test/java/de/krisenvorrat/app/domain/usecase/TestHelpers.kt +++ b/app/src/test/java/de/krisenvorrat/app/domain/usecase/TestHelpers.kt @@ -10,7 +10,7 @@ internal fun buildTestItem( quantity: Double = 1.0, unit: String = "Stk", unitPrice: Double = 0.0, - kcalPerKg: Int? = null, + kcalPerUnit: Int? = null, expiryDate: LocalDate? = null, locationId: Int = 1 ) = ItemEntity( @@ -20,7 +20,7 @@ internal fun buildTestItem( quantity = quantity, unit = unit, unitPrice = unitPrice, - kcalPerKg = kcalPerKg, + kcalPerUnit = kcalPerUnit, expiryDate = expiryDate, locationId = locationId, notes = "", diff --git a/app/src/test/java/de/krisenvorrat/app/ui/category/CategoryListViewModelTest.kt b/app/src/test/java/de/krisenvorrat/app/ui/category/CategoryListViewModelTest.kt index c172f47..a4d3a23 100644 --- a/app/src/test/java/de/krisenvorrat/app/ui/category/CategoryListViewModelTest.kt +++ b/app/src/test/java/de/krisenvorrat/app/ui/category/CategoryListViewModelTest.kt @@ -293,7 +293,7 @@ class CategoryListViewModelTest { fakeItemRepository.addItem( ItemEntity( id = "i1", name = "Reis", categoryId = 1, quantity = 1.0, - unit = "kg", unitPrice = 0.0, kcalPerKg = null, expiryDate = null, + unit = "kg", unitPrice = 0.0, kcalPerUnit = null, expiryDate = null, locationId = 1, notes = "", lastUpdated = 0L ) ) @@ -318,7 +318,7 @@ class CategoryListViewModelTest { fakeItemRepository.addItem( ItemEntity( id = "i1", name = "Reis", categoryId = 1, quantity = 1.0, - unit = "kg", unitPrice = 0.0, kcalPerKg = null, expiryDate = null, + unit = "kg", unitPrice = 0.0, kcalPerUnit = null, expiryDate = null, locationId = 1, notes = "", lastUpdated = 0L ) ) @@ -345,7 +345,7 @@ class CategoryListViewModelTest { fakeItemRepository.addItem( ItemEntity( id = "i1", name = "Reis", categoryId = 1, quantity = 1.0, - unit = "kg", unitPrice = 0.0, kcalPerKg = null, expiryDate = null, + unit = "kg", unitPrice = 0.0, kcalPerUnit = null, expiryDate = null, locationId = 1, notes = "", lastUpdated = 0L ) ) diff --git a/app/src/test/java/de/krisenvorrat/app/ui/dashboard/DashboardViewModelTest.kt b/app/src/test/java/de/krisenvorrat/app/ui/dashboard/DashboardViewModelTest.kt index 769b816..e5bed2d 100644 --- a/app/src/test/java/de/krisenvorrat/app/ui/dashboard/DashboardViewModelTest.kt +++ b/app/src/test/java/de/krisenvorrat/app/ui/dashboard/DashboardViewModelTest.kt @@ -149,7 +149,7 @@ class DashboardViewModelTest { ) fakeItemRepository.emit( listOf( - buildTestItem(id = "a", quantity = 1000.0, unit = "g", kcalPerKg = 2000) + buildTestItem(id = "a", quantity = 1.0, unit = "Stk", kcalPerUnit = 2000) ) ) viewModel = createViewModel() @@ -172,7 +172,7 @@ class DashboardViewModelTest { allZeroAgeGroups.toJson() ) fakeItemRepository.emit( - listOf(buildTestItem(id = "a", quantity = 1000.0, unit = "g", kcalPerKg = 4000)) + listOf(buildTestItem(id = "a", quantity = 1.0, unit = "Stk", kcalPerUnit = 4000)) ) viewModel = createViewModel() @@ -247,7 +247,7 @@ private fun buildTestItem( quantity: Double = 1.0, unit: String = "Stk", unitPrice: Double = 0.0, - kcalPerKg: Int? = null, + kcalPerUnit: Int? = null, expiryDate: LocalDate? = null, locationId: Int = 1 ) = ItemEntity( @@ -257,7 +257,7 @@ private fun buildTestItem( quantity = quantity, unit = unit, unitPrice = unitPrice, - kcalPerKg = kcalPerKg, + kcalPerUnit = kcalPerUnit, expiryDate = expiryDate, locationId = locationId, notes = "", diff --git a/app/src/test/java/de/krisenvorrat/app/ui/item/ItemFormViewModelTest.kt b/app/src/test/java/de/krisenvorrat/app/ui/item/ItemFormViewModelTest.kt index fd38932..abeaea4 100644 --- a/app/src/test/java/de/krisenvorrat/app/ui/item/ItemFormViewModelTest.kt +++ b/app/src/test/java/de/krisenvorrat/app/ui/item/ItemFormViewModelTest.kt @@ -152,7 +152,7 @@ class ItemFormViewModelTest { fakeItemRepository.addItem( ItemEntity( id = "prev-1", name = "Alt", categoryId = 1, quantity = 1.0, - unit = "kg", unitPrice = 0.0, kcalPerKg = null, expiryDate = null, + unit = "kg", unitPrice = 0.0, kcalPerUnit = null, expiryDate = null, locationId = 2, notes = "", lastUpdated = 1000L ) ) @@ -177,7 +177,7 @@ class ItemFormViewModelTest { fakeItemRepository.addItem( ItemEntity( id = "prev-1", name = "Alt", categoryId = 1, quantity = 1.0, - unit = "kg", unitPrice = 0.0, kcalPerKg = null, expiryDate = null, + unit = "kg", unitPrice = 0.0, kcalPerUnit = null, expiryDate = null, locationId = 99, notes = "", lastUpdated = 1000L ) ) @@ -208,7 +208,7 @@ class ItemFormViewModelTest { quantity = 5.0, unit = "Stk", unitPrice = 2.5, - kcalPerKg = 120, + kcalPerUnit = 120, expiryDate = LocalDate.of(2026, 12, 31), locationId = 2, notes = "Bohnen", @@ -229,7 +229,7 @@ class ItemFormViewModelTest { assertEquals("5", state.quantity) assertEquals("Stk", state.unit) assertEquals("2.5", state.unitPrice) - assertEquals("120", state.kcalPerKg) + assertEquals("120", state.kcalPerUnit) assertEquals(LocalDate.of(2026, 12, 31), state.expiryDate) assertEquals(2, state.locationId) assertEquals("Bohnen", state.notes) @@ -465,7 +465,7 @@ class ItemFormViewModelTest { quantity = 2.0, unit = "Stk", unitPrice = 0.0, - kcalPerKg = null, + kcalPerUnit = null, expiryDate = null, locationId = 1, notes = "", diff --git a/app/src/test/java/de/krisenvorrat/app/ui/item/ItemListViewModelTest.kt b/app/src/test/java/de/krisenvorrat/app/ui/item/ItemListViewModelTest.kt index b4f3ffc..c4d0e10 100644 --- a/app/src/test/java/de/krisenvorrat/app/ui/item/ItemListViewModelTest.kt +++ b/app/src/test/java/de/krisenvorrat/app/ui/item/ItemListViewModelTest.kt @@ -562,7 +562,7 @@ private fun buildItemEntity( quantity = 2.0, unit = "Stk", unitPrice = 1.5, - kcalPerKg = null, + kcalPerUnit = null, expiryDate = expiryDate, locationId = locationId, notes = notes, diff --git a/app/src/test/java/de/krisenvorrat/app/ui/location/LocationListViewModelTest.kt b/app/src/test/java/de/krisenvorrat/app/ui/location/LocationListViewModelTest.kt index af036ab..4015915 100644 --- a/app/src/test/java/de/krisenvorrat/app/ui/location/LocationListViewModelTest.kt +++ b/app/src/test/java/de/krisenvorrat/app/ui/location/LocationListViewModelTest.kt @@ -292,7 +292,7 @@ class LocationListViewModelTest { fakeItemRepository.addItem( ItemEntity( id = "i1", name = "Reis", categoryId = 1, quantity = 1.0, - unit = "kg", unitPrice = 0.0, kcalPerKg = null, expiryDate = null, + unit = "kg", unitPrice = 0.0, kcalPerUnit = null, expiryDate = null, locationId = 1, notes = "", lastUpdated = 0L ) ) @@ -317,7 +317,7 @@ class LocationListViewModelTest { fakeItemRepository.addItem( ItemEntity( id = "i1", name = "Reis", categoryId = 1, quantity = 1.0, - unit = "kg", unitPrice = 0.0, kcalPerKg = null, expiryDate = null, + unit = "kg", unitPrice = 0.0, kcalPerUnit = null, expiryDate = null, locationId = 1, notes = "", lastUpdated = 0L ) ) @@ -344,7 +344,7 @@ class LocationListViewModelTest { fakeItemRepository.addItem( ItemEntity( id = "i1", name = "Wasser", categoryId = 1, quantity = 1.0, - unit = "Flasche", unitPrice = 0.0, kcalPerKg = null, expiryDate = null, + unit = "Flasche", unitPrice = 0.0, kcalPerUnit = null, expiryDate = null, locationId = 1, notes = "", lastUpdated = 0L ) ) diff --git a/app/src/test/java/de/krisenvorrat/app/ui/settings/SettingsViewModelTest.kt b/app/src/test/java/de/krisenvorrat/app/ui/settings/SettingsViewModelTest.kt index 00bd31f..e4e5f80 100644 --- a/app/src/test/java/de/krisenvorrat/app/ui/settings/SettingsViewModelTest.kt +++ b/app/src/test/java/de/krisenvorrat/app/ui/settings/SettingsViewModelTest.kt @@ -811,7 +811,7 @@ private class FakeSettingsRepository : SettingsRepository { private class FakeImportExportRepository : ImportExportRepository { var jsonResult = """{"version":1,"categories":[],"locations":[],"items":[],"settings":[]}""" var markdownResult = "# Krisenvorrat Inventar\n" - var csvResult = "\uFEFFName;Kategorie;Menge;Einheit;Stückpreis;kcal/kg;MHD;Lagerort;Notizen\n" + var csvResult = "\uFEFFName;Kategorie;Menge;Einheit;Stückpreis;kcal/Einheit;MHD;Lagerort;Notizen\n" var shouldThrow = false var importShouldFail = false var inventoryDto = InventoryDto( diff --git a/app/src/test/java/de/krisenvorrat/app/ui/warnings/WarningsViewModelTest.kt b/app/src/test/java/de/krisenvorrat/app/ui/warnings/WarningsViewModelTest.kt index f037741..914c1d3 100644 --- a/app/src/test/java/de/krisenvorrat/app/ui/warnings/WarningsViewModelTest.kt +++ b/app/src/test/java/de/krisenvorrat/app/ui/warnings/WarningsViewModelTest.kt @@ -124,7 +124,7 @@ private fun buildTestItem( quantity: Double = 1.0, unit: String = "Stk", unitPrice: Double = 0.0, - kcalPerKg: Int? = null, + kcalPerUnit: Int? = null, expiryDate: LocalDate? = null, locationId: Int = 1 ) = ItemEntity( @@ -134,7 +134,7 @@ private fun buildTestItem( quantity = quantity, unit = unit, unitPrice = unitPrice, - kcalPerKg = kcalPerKg, + kcalPerUnit = kcalPerUnit, expiryDate = expiryDate, locationId = locationId, notes = "", diff --git a/run-integration-tests.ps1 b/run-integration-tests.ps1 index ee1143d..94d53cd 100644 --- a/run-integration-tests.ps1 +++ b/run-integration-tests.ps1 @@ -167,7 +167,7 @@ $testInventory = @{ quantity = 12.0 unit = "Stueck" unitPrice = 1.5 - kcalPerKg = $null + kcalPerUnit = $null expiryDate = "2027-12-31" locationId = 1 notes = "Integrationstest" @@ -200,7 +200,7 @@ if ($aliceTokens) { $patch = @{ id = $itemId; name = "Testkonserve"; categoryId = 1 quantity = 24.0; unit = "Stueck"; unitPrice = 1.5 - kcalPerKg = $null; expiryDate = "2027-12-31"; locationId = 1 + kcalPerUnit = $null; expiryDate = "2027-12-31"; locationId = 1 notes = "Nach PATCH"; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } Invoke-Api -Method PATCH -Path "/api/inventory/items/$itemId" -Body $patch -Token $aliceTokens.accessToken | Out-Null @@ -527,16 +527,16 @@ if ($bobTokens) { @{ id = 11; name = "Badezimmer" } ) items = @( - @{ id = $dosenBrotId; name = "Dosenbrot"; categoryId = 10; quantity = 5.0; unit = "Stueck"; unitPrice = 2.0; kcalPerKg = $null; expiryDate = "2028-12-31"; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } - @{ id = $wasserId; name = "Mineralwasser"; categoryId = 11; quantity = 24.0; unit = "Stueck"; unitPrice = 0.5; kcalPerKg = $null; expiryDate = "2028-12-31"; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } - @{ id = $thunfischId; name = "Thunfisch"; categoryId = 10; quantity = 8.0; unit = "Dose"; unitPrice = 1.2; kcalPerKg = $null; expiryDate = "2028-06-30"; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } - @{ id = $nudelnId; name = "Nudeln"; categoryId = 10; quantity = 3.0; unit = "Packung"; unitPrice = 0.9; kcalPerKg = $null; expiryDate = "2029-01-01"; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } - @{ id = $toilettenPapierId; name = "Toilettenpapier"; categoryId = 12; quantity = 12.0; unit = "Rollen"; unitPrice = 0.3; kcalPerKg = $null; expiryDate = $null; locationId = 11; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } - @{ id = $seifenId; name = "Seife"; categoryId = 12; quantity = 4.0; unit = "Stueck"; unitPrice = 1.5; kcalPerKg = $null; expiryDate = $null; locationId = 11; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } - @{ id = $muesliId; name = "Muesliegel"; categoryId = 10; quantity = 20.0; unit = "Stueck"; unitPrice = 0.8; kcalPerKg = $null; expiryDate = "2028-03-31"; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } - @{ id = $apfelsaftId; name = "Apfelsaft"; categoryId = 11; quantity = 6.0; unit = "Liter"; unitPrice = 1.0; kcalPerKg = $null; expiryDate = "2028-09-30"; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } - @{ id = $kerzenId; name = "Kerzen"; categoryId = 10; quantity = 10.0; unit = "Stueck"; unitPrice = 1.5; kcalPerKg = $null; expiryDate = $null; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } - @{ id = $pflasterId; name = "Erste-Hilfe-Pflaster"; categoryId = 12; quantity = 2.0; unit = "Packung"; unitPrice = 3.5; kcalPerKg = $null; expiryDate = "2029-06-30"; locationId = 11; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } + @{ id = $dosenBrotId; name = "Dosenbrot"; categoryId = 10; quantity = 5.0; unit = "Stueck"; unitPrice = 2.0; kcalPerUnit = $null; expiryDate = "2028-12-31"; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } + @{ id = $wasserId; name = "Mineralwasser"; categoryId = 11; quantity = 24.0; unit = "Stueck"; unitPrice = 0.5; kcalPerUnit = $null; expiryDate = "2028-12-31"; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } + @{ id = $thunfischId; name = "Thunfisch"; categoryId = 10; quantity = 8.0; unit = "Dose"; unitPrice = 1.2; kcalPerUnit = $null; expiryDate = "2028-06-30"; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } + @{ id = $nudelnId; name = "Nudeln"; categoryId = 10; quantity = 3.0; unit = "Packung"; unitPrice = 0.9; kcalPerUnit = $null; expiryDate = "2029-01-01"; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } + @{ id = $toilettenPapierId; name = "Toilettenpapier"; categoryId = 12; quantity = 12.0; unit = "Rollen"; unitPrice = 0.3; kcalPerUnit = $null; expiryDate = $null; locationId = 11; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } + @{ id = $seifenId; name = "Seife"; categoryId = 12; quantity = 4.0; unit = "Stueck"; unitPrice = 1.5; kcalPerUnit = $null; expiryDate = $null; locationId = 11; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } + @{ id = $muesliId; name = "Muesliegel"; categoryId = 10; quantity = 20.0; unit = "Stueck"; unitPrice = 0.8; kcalPerUnit = $null; expiryDate = "2028-03-31"; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } + @{ id = $apfelsaftId; name = "Apfelsaft"; categoryId = 11; quantity = 6.0; unit = "Liter"; unitPrice = 1.0; kcalPerUnit = $null; expiryDate = "2028-09-30"; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } + @{ id = $kerzenId; name = "Kerzen"; categoryId = 10; quantity = 10.0; unit = "Stueck"; unitPrice = 1.5; kcalPerUnit = $null; expiryDate = $null; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } + @{ id = $pflasterId; name = "Erste-Hilfe-Pflaster"; categoryId = 12; quantity = 2.0; unit = "Packung"; unitPrice = 3.5; kcalPerUnit = $null; expiryDate = "2029-06-30"; locationId = 11; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } ) settings = @() } @@ -567,7 +567,7 @@ if ($bobTokens) { $patchBody = @{ id = $dosenBrotId; name = "Dosenbrot"; categoryId = 10 quantity = 10.0; unit = "Stueck"; unitPrice = 2.0 - kcalPerKg = $null; expiryDate = "2028-12-31"; locationId = 10 + kcalPerUnit = $null; expiryDate = "2028-12-31"; locationId = 10 notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } Invoke-Api -Method PATCH -Path "/api/inventory/items/$dosenBrotId" -Body $patchBody -Token $bobTokens.accessToken | Out-Null @@ -589,7 +589,7 @@ if ($bobTokens) { categories = $bob10Items.categories locations = $bob10Items.locations items = $bob10Items.items + @( - @{ id = $salzcrackerId; name = "Salzcracker"; categoryId = 10; quantity = 5.0; unit = "Packung"; unitPrice = 1.2; kcalPerKg = $null; expiryDate = "2028-06-30"; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } + @{ id = $salzcrackerId; name = "Salzcracker"; categoryId = 10; quantity = 5.0; unit = "Packung"; unitPrice = 1.2; kcalPerUnit = $null; expiryDate = "2028-06-30"; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } ) settings = @() } @@ -616,7 +616,7 @@ if ($bobTokens) { $patchBody2 = @{ id = $dosenBrotId; name = "Dosenbrot"; categoryId = 10 quantity = 15.0; unit = "Stueck"; unitPrice = 2.0 - kcalPerKg = $null; expiryDate = "2028-12-31"; locationId = 10 + kcalPerUnit = $null; expiryDate = "2028-12-31"; locationId = 10 notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } Invoke-Api -Method PATCH -Path "/api/inventory/items/$dosenBrotId" -Body $patchBody2 -Token $bobTokens2.accessToken | Out-Null @@ -666,7 +666,7 @@ if ($bobTokens) { $patchT1 = @{ id = $unknownId; name = "Unbekannt"; categoryId = 10 quantity = 1.0; unit = "Stueck"; unitPrice = 0.0 - kcalPerKg = $null; expiryDate = $null; locationId = 10 + kcalPerUnit = $null; expiryDate = $null; locationId = 10 notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } try { @@ -710,7 +710,7 @@ if ($bobTokens) { else { Fail "T6 bob_invalidToken: Unerwarteter Fehler bei GET: $_" } } $t6PatchId = [System.Guid]::NewGuid().ToString() - $t6Body = @{ id = $t6PatchId; name = "X"; categoryId = 10; quantity = 1.0; unit = "x"; unitPrice = 0.0; kcalPerKg = $null; expiryDate = $null; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } + $t6Body = @{ id = $t6PatchId; name = "X"; categoryId = 10; quantity = 1.0; unit = "x"; unitPrice = 0.0; kcalPerUnit = $null; expiryDate = $null; locationId = 10; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } try { Invoke-Api -Method PATCH -Path "/api/inventory/items/$t6PatchId" -Body $t6Body -Token $invalidToken | Out-Null Fail "T6 bob_invalidToken: PATCH sollte 401 liefern" @@ -743,13 +743,13 @@ if ($bobTokens) { $firstPut = @{ version = 1 categories = @(@{ id = 20; name = "KatA" }); locations = @(@{ id = 20; name = "OrtA" }) - items = @(@{ id = $firstId; name = "ErstesPUT"; categoryId = 20; quantity = 1.0; unit = "Stueck"; unitPrice = 0.0; kcalPerKg = $null; expiryDate = $null; locationId = 20; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() }) + items = @(@{ id = $firstId; name = "ErstesPUT"; categoryId = 20; quantity = 1.0; unit = "Stueck"; unitPrice = 0.0; kcalPerUnit = $null; expiryDate = $null; locationId = 20; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() }) settings = @() } $secondPut = @{ version = 1 categories = @(@{ id = 21; name = "KatB" }); locations = @(@{ id = 21; name = "OrtB" }) - items = @(@{ id = $secondId; name = "ZweitesPUT"; categoryId = 21; quantity = 2.0; unit = "Stueck"; unitPrice = 0.0; kcalPerKg = $null; expiryDate = $null; locationId = 21; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() }) + items = @(@{ id = $secondId; name = "ZweitesPUT"; categoryId = 21; quantity = 2.0; unit = "Stueck"; unitPrice = 0.0; kcalPerUnit = $null; expiryDate = $null; locationId = 21; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() }) settings = @() } Invoke-Api -Method PUT -Path "/api/inventory" -Body $firstPut -Token $bobTokens.accessToken | Out-Null @@ -772,7 +772,7 @@ if ($bobTokens) { $t4ItemId = [System.Guid]::NewGuid().ToString() $t4Inv = @{ version = 1; categories = @(@{ id = 30; name = "KatT4" }); locations = @(@{ id = 30; name = "OrtT4" }) - items = @(@{ id = $t4ItemId; name = "T4-Item"; categoryId = 30; quantity = 1.0; unit = "Stueck"; unitPrice = 0.0; kcalPerKg = $null; expiryDate = $null; locationId = 30; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() }) + items = @(@{ id = $t4ItemId; name = "T4-Item"; categoryId = 30; quantity = 1.0; unit = "Stueck"; unitPrice = 0.0; kcalPerUnit = $null; expiryDate = $null; locationId = 30; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() }) settings = @() } Invoke-Api -Method PUT -Path "/api/inventory" -Body $t4Inv -Token $bobTokens.accessToken | Out-Null @@ -780,7 +780,7 @@ if ($bobTokens) { $t4WS = Open-WebSocket -token $bobTokens.accessToken Start-Sleep -Milliseconds 800 - $t4Patch = @{ id = $t4ItemId; name = "T4-Item"; categoryId = 30; quantity = 5.0; unit = "Stueck"; unitPrice = 0.0; kcalPerKg = $null; expiryDate = $null; locationId = 30; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } + $t4Patch = @{ id = $t4ItemId; name = "T4-Item"; categoryId = 30; quantity = 5.0; unit = "Stueck"; unitPrice = 0.0; kcalPerUnit = $null; expiryDate = $null; locationId = 30; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } Invoke-Api -Method PATCH -Path "/api/inventory/items/$t4ItemId" -Body $t4Patch -Token $bobTokens.accessToken | Out-Null $t4Events = Receive-WsMessages -ws $t4WS -waitSeconds 5 @@ -804,7 +804,7 @@ if ($bobTokens) { $t7ItemId = [System.Guid]::NewGuid().ToString() $t7Inv = @{ version = 1; categories = @(@{ id = 40; name = "KatT7" }); locations = @(@{ id = 40; name = "OrtT7" }) - items = @(@{ id = $t7ItemId; name = "T7-Item"; categoryId = 40; quantity = 1.0; unit = "Stueck"; unitPrice = 0.0; kcalPerKg = $null; expiryDate = $null; locationId = 40; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() }) + items = @(@{ id = $t7ItemId; name = "T7-Item"; categoryId = 40; quantity = 1.0; unit = "Stueck"; unitPrice = 0.0; kcalPerUnit = $null; expiryDate = $null; locationId = 40; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() }) settings = @() } Invoke-Api -Method PUT -Path "/api/inventory" -Body $t7Inv -Token $bobTokens.accessToken | Out-Null @@ -814,7 +814,7 @@ if ($bobTokens) { Start-Sleep -Milliseconds 1200 $bobTokens4 = Invoke-Api -Method POST -Path "/api/auth/login" -Body @{ username = $BobUser; password = $BobPassword } - $t7Patch = @{ id = $t7ItemId; name = "T7-Item"; categoryId = 40; quantity = 9.0; unit = "Stueck"; unitPrice = 0.0; kcalPerKg = $null; expiryDate = $null; locationId = 40; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } + $t7Patch = @{ id = $t7ItemId; name = "T7-Item"; categoryId = 40; quantity = 9.0; unit = "Stueck"; unitPrice = 0.0; kcalPerUnit = $null; expiryDate = $null; locationId = 40; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } Invoke-Api -Method PATCH -Path "/api/inventory/items/$t7ItemId" -Body $t7Patch -Token $bobTokens4.accessToken | Out-Null $t7ev1 = Receive-WsMessages -ws $t7ws1 -waitSeconds 5 @@ -841,7 +841,7 @@ if ($bobTokens) { $t8ItemId = [System.Guid]::NewGuid().ToString() $t8Inv = @{ version = 1; categories = @(@{ id = 50; name = "KatT8" }); locations = @(@{ id = 50; name = "OrtT8" }) - items = @(@{ id = $t8ItemId; name = "T8-Item"; categoryId = 50; quantity = 1.0; unit = "Stueck"; unitPrice = 0.0; kcalPerKg = $null; expiryDate = $null; locationId = 50; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() }) + items = @(@{ id = $t8ItemId; name = "T8-Item"; categoryId = 50; quantity = 1.0; unit = "Stueck"; unitPrice = 0.0; kcalPerUnit = $null; expiryDate = $null; locationId = 50; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() }) settings = @() } Invoke-Api -Method PUT -Path "/api/inventory" -Body $t8Inv -Token $bobTokens.accessToken | Out-Null @@ -851,7 +851,7 @@ if ($bobTokens) { Start-Sleep -Milliseconds 600 $bobTokens5 = Invoke-Api -Method POST -Path "/api/auth/login" -Body @{ username = $BobUser; password = $BobPassword } - $t8Patch = @{ id = $t8ItemId; name = "T8-Item"; categoryId = 50; quantity = 7.0; unit = "Stueck"; unitPrice = 0.0; kcalPerKg = $null; expiryDate = $null; locationId = 50; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } + $t8Patch = @{ id = $t8ItemId; name = "T8-Item"; categoryId = 50; quantity = 7.0; unit = "Stueck"; unitPrice = 0.0; kcalPerUnit = $null; expiryDate = $null; locationId = 50; notes = ""; lastUpdated = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } try { Invoke-Api -Method PATCH -Path "/api/inventory/items/$t8ItemId" -Body $t8Patch -Token $bobTokens5.accessToken | Out-Null Pass "T8 bob_patchAfterDisconnect: PATCH nach Bob-Disconnect liefert keinen Server-Fehler" diff --git a/server/src/main/kotlin/de/krisenvorrat/server/db/Tables.kt b/server/src/main/kotlin/de/krisenvorrat/server/db/Tables.kt index f88b7f2..37e6ee8 100644 --- a/server/src/main/kotlin/de/krisenvorrat/server/db/Tables.kt +++ b/server/src/main/kotlin/de/krisenvorrat/server/db/Tables.kt @@ -45,7 +45,7 @@ internal object Items : Table("items") { val quantity = double("quantity") val unit = varchar("unit", 50) val unitPrice = double("unit_price") - val kcalPerKg = integer("kcal_per_kg").nullable() + val kcalPerUnit = integer("kcal_per_unit").nullable() val expiryDate = varchar("expiry_date", 10).nullable() val locationId = integer("location_id") val notes = text("notes") diff --git a/server/src/main/kotlin/de/krisenvorrat/server/repository/InventoryRepository.kt b/server/src/main/kotlin/de/krisenvorrat/server/repository/InventoryRepository.kt index ab30b68..f4d55c7 100644 --- a/server/src/main/kotlin/de/krisenvorrat/server/repository/InventoryRepository.kt +++ b/server/src/main/kotlin/de/krisenvorrat/server/repository/InventoryRepository.kt @@ -181,7 +181,7 @@ internal class InventoryRepository { it[quantity] = item.quantity it[unit] = item.unit it[unitPrice] = item.unitPrice - it[kcalPerKg] = item.kcalPerKg + it[kcalPerUnit] = item.kcalPerUnit it[expiryDate] = item.expiryDate it[locationId] = item.locationId it[notes] = item.notes @@ -220,7 +220,7 @@ internal class InventoryRepository { quantity = it[Items.quantity], unit = it[Items.unit], unitPrice = it[Items.unitPrice], - kcalPerKg = it[Items.kcalPerKg], + kcalPerUnit = it[Items.kcalPerUnit], expiryDate = it[Items.expiryDate], locationId = it[Items.locationId], notes = it[Items.notes], @@ -246,7 +246,7 @@ internal class InventoryRepository { it[quantity] = item.quantity it[unit] = item.unit it[unitPrice] = item.unitPrice - it[kcalPerKg] = item.kcalPerKg + it[kcalPerUnit] = item.kcalPerUnit it[expiryDate] = item.expiryDate it[locationId] = item.locationId it[notes] = item.notes @@ -257,7 +257,7 @@ internal class InventoryRepository { } fun patchItemPartial(inventoryId: String, itemId: String, fields: JsonObject): Boolean { - val updatableKeys = setOf("name", "categoryId", "quantity", "unit", "unitPrice", "kcalPerKg", "expiryDate", "locationId", "notes", "lastUpdated") + val updatableKeys = setOf("name", "categoryId", "quantity", "unit", "unitPrice", "kcalPerUnit", "expiryDate", "locationId", "notes", "lastUpdated") return transaction { val exists = Items.selectAll() .where { (Items.id eq itemId) and (Items.inventoryId eq inventoryId) } @@ -275,7 +275,7 @@ internal class InventoryRepository { if ("quantity" in fields) stmt[quantity] = fields["quantity"]!!.jsonPrimitive.doubleOrNull ?: 0.0 if ("unit" in fields) stmt[unit] = fields["unit"]!!.jsonPrimitive.content if ("unitPrice" in fields) stmt[unitPrice] = fields["unitPrice"]!!.jsonPrimitive.doubleOrNull ?: 0.0 - if ("kcalPerKg" in fields) stmt[kcalPerKg] = fields["kcalPerKg"]!!.jsonPrimitive.intOrNull + if ("kcalPerUnit" in fields) stmt[kcalPerUnit] = fields["kcalPerUnit"]!!.jsonPrimitive.intOrNull if ("expiryDate" in fields) stmt[expiryDate] = fields["expiryDate"]!!.jsonPrimitive.contentOrNull if ("locationId" in fields) stmt[locationId] = fields["locationId"]!!.jsonPrimitive.intOrNull ?: 0 if ("notes" in fields) stmt[notes] = fields["notes"]!!.jsonPrimitive.content @@ -297,7 +297,7 @@ internal class InventoryRepository { quantity = it[Items.quantity], unit = it[Items.unit], unitPrice = it[Items.unitPrice], - kcalPerKg = it[Items.kcalPerKg], + kcalPerUnit = it[Items.kcalPerUnit], expiryDate = it[Items.expiryDate], locationId = it[Items.locationId], notes = it[Items.notes], @@ -344,7 +344,7 @@ internal class InventoryRepository { quantity = it[Items.quantity], unit = it[Items.unit], unitPrice = it[Items.unitPrice], - kcalPerKg = it[Items.kcalPerKg], + kcalPerUnit = it[Items.kcalPerUnit], expiryDate = it[Items.expiryDate], locationId = it[Items.locationId], notes = it[Items.notes], diff --git a/server/src/test/kotlin/de/krisenvorrat/server/ApplicationTest.kt b/server/src/test/kotlin/de/krisenvorrat/server/ApplicationTest.kt index 571937d..54fd0f9 100644 --- a/server/src/test/kotlin/de/krisenvorrat/server/ApplicationTest.kt +++ b/server/src/test/kotlin/de/krisenvorrat/server/ApplicationTest.kt @@ -200,7 +200,7 @@ class ApplicationTest { quantity = 5.0, unit = "Stück", unitPrice = 3.99, - kcalPerKg = 250, + kcalPerUnit = 250, expiryDate = "2027-06-15", locationId = 1, notes = "Vollkornbrot in der Dose", diff --git a/server/src/test/kotlin/de/krisenvorrat/server/DeltaSyncTest.kt b/server/src/test/kotlin/de/krisenvorrat/server/DeltaSyncTest.kt index a0dcd57..fd89654 100644 --- a/server/src/test/kotlin/de/krisenvorrat/server/DeltaSyncTest.kt +++ b/server/src/test/kotlin/de/krisenvorrat/server/DeltaSyncTest.kt @@ -156,7 +156,7 @@ class DeltaSyncTest { ItemDto( id = "item-1", name = "Dosenbrot", categoryId = 1, quantity = 5.0, unit = "Stück", unitPrice = 3.99, - kcalPerKg = 250, expiryDate = "2027-06-15", locationId = 1, + kcalPerUnit = 250, expiryDate = "2027-06-15", locationId = 1, notes = "", lastUpdated = 3000L ) ), @@ -205,13 +205,13 @@ class DeltaSyncTest { ItemDto( id = "item-1", name = "Dosenbrot", categoryId = 1, quantity = 5.0, unit = "Stück", unitPrice = 3.99, - kcalPerKg = 250, expiryDate = "2027-06-15", locationId = 1, + kcalPerUnit = 250, expiryDate = "2027-06-15", locationId = 1, notes = "", lastUpdated = lastUpdated ), ItemDto( id = "item-2", name = "Mineralwasser", categoryId = 1, quantity = 24.0, unit = "Liter", unitPrice = 0.49, - kcalPerKg = 0, expiryDate = "2028-01-01", locationId = 1, + kcalPerUnit = 0, expiryDate = "2028-01-01", locationId = 1, notes = "", lastUpdated = lastUpdated ) ), diff --git a/server/src/test/kotlin/de/krisenvorrat/server/EndToEndSyncTest.kt b/server/src/test/kotlin/de/krisenvorrat/server/EndToEndSyncTest.kt index 6ba71c4..d91d364 100644 --- a/server/src/test/kotlin/de/krisenvorrat/server/EndToEndSyncTest.kt +++ b/server/src/test/kotlin/de/krisenvorrat/server/EndToEndSyncTest.kt @@ -105,7 +105,7 @@ class EndToEndSyncTest { quantity = 3.0, unit = "Stück", unitPrice = 1.50, - kcalPerKg = null, + kcalPerUnit = null, expiryDate = null, locationId = 10, notes = "", @@ -176,7 +176,7 @@ class EndToEndSyncTest { quantity = 10.0, unit = "Stück", unitPrice = 3.99, - kcalPerKg = 250, + kcalPerUnit = 250, expiryDate = "2027-06-15", locationId = 1, notes = "Menge erhöht", @@ -246,7 +246,7 @@ class EndToEndSyncTest { quantity = 5.0, unit = "Stück", unitPrice = 3.99, - kcalPerKg = 250, + kcalPerUnit = 250, expiryDate = "2027-06-15", locationId = 1, notes = "Vollkornbrot in der Dose", @@ -259,7 +259,7 @@ class EndToEndSyncTest { quantity = 24.0, unit = "Liter", unitPrice = 0.49, - kcalPerKg = 0, + kcalPerUnit = 0, expiryDate = "2028-01-01", locationId = 2, notes = "Stilles Wasser", diff --git a/server/src/test/kotlin/de/krisenvorrat/server/InputValidationTest.kt b/server/src/test/kotlin/de/krisenvorrat/server/InputValidationTest.kt index c1c8d27..69641cd 100644 --- a/server/src/test/kotlin/de/krisenvorrat/server/InputValidationTest.kt +++ b/server/src/test/kotlin/de/krisenvorrat/server/InputValidationTest.kt @@ -49,7 +49,7 @@ class InputValidationTest { quantity = 10.0, unit = "L", unitPrice = 0.5, - kcalPerKg = null, + kcalPerUnit = null, expiryDate = "2027-12-31", locationId = 1, notes = "", diff --git a/server/src/test/kotlin/de/krisenvorrat/server/InventoryManagementTest.kt b/server/src/test/kotlin/de/krisenvorrat/server/InventoryManagementTest.kt index 2710447..2da8ac8 100644 --- a/server/src/test/kotlin/de/krisenvorrat/server/InventoryManagementTest.kt +++ b/server/src/test/kotlin/de/krisenvorrat/server/InventoryManagementTest.kt @@ -220,7 +220,7 @@ class InventoryManagementTest { de.krisenvorrat.shared.model.ItemDto( id = "item-1", name = "Water", categoryId = 1, quantity = 10.0, unit = "L", unitPrice = 0.5, - kcalPerKg = null, expiryDate = null, + kcalPerUnit = null, expiryDate = null, locationId = 1, notes = "", lastUpdated = 1000 ) ), diff --git a/server/src/test/kotlin/de/krisenvorrat/server/InventorySharingTest.kt b/server/src/test/kotlin/de/krisenvorrat/server/InventorySharingTest.kt index 9afd94f..a24c889 100644 --- a/server/src/test/kotlin/de/krisenvorrat/server/InventorySharingTest.kt +++ b/server/src/test/kotlin/de/krisenvorrat/server/InventorySharingTest.kt @@ -286,7 +286,7 @@ class InventorySharingTest { quantity = 1.0, unit = "Stück", unitPrice = 1.99, - kcalPerKg = null, + kcalPerUnit = null, expiryDate = null, locationId = 1, notes = "", diff --git a/server/src/test/kotlin/de/krisenvorrat/server/InventoryStatsTest.kt b/server/src/test/kotlin/de/krisenvorrat/server/InventoryStatsTest.kt index 93dee92..1ac25a7 100644 --- a/server/src/test/kotlin/de/krisenvorrat/server/InventoryStatsTest.kt +++ b/server/src/test/kotlin/de/krisenvorrat/server/InventoryStatsTest.kt @@ -95,10 +95,10 @@ class InventoryStatsTest { ), items = listOf( ItemDto(id = "i1", name = "Dosenbrot", categoryId = 1, quantity = 5.0, unit = "Stk", - unitPrice = 3.99, kcalPerKg = null, expiryDate = null, locationId = 1, notes = "", + unitPrice = 3.99, kcalPerUnit = null, expiryDate = null, locationId = 1, notes = "", lastUpdated = System.currentTimeMillis()), ItemDto(id = "i2", name = "Wasser", categoryId = 2, quantity = 10.0, unit = "L", - unitPrice = 0.5, kcalPerKg = null, expiryDate = null, locationId = 2, notes = "", + unitPrice = 0.5, kcalPerUnit = null, expiryDate = null, locationId = 2, notes = "", lastUpdated = System.currentTimeMillis()) ), settings = emptyList() diff --git a/server/src/test/kotlin/de/krisenvorrat/server/PatchItemTest.kt b/server/src/test/kotlin/de/krisenvorrat/server/PatchItemTest.kt index 7b6c3f8..23da120 100644 --- a/server/src/test/kotlin/de/krisenvorrat/server/PatchItemTest.kt +++ b/server/src/test/kotlin/de/krisenvorrat/server/PatchItemTest.kt @@ -42,7 +42,7 @@ class PatchItemTest { quantity = 5.0, unit = "Stück", unitPrice = 3.99, - kcalPerKg = 250, + kcalPerUnit = 250, expiryDate = "2027-06-15", locationId = 1, notes = "Vollkornbrot", @@ -83,7 +83,7 @@ class PatchItemTest { // Unchanged fields remain assertEquals("Stück", result.unit) assertEquals(3.99, result.unitPrice, 0.001) - assertEquals(250, result.kcalPerKg) + assertEquals(250, result.kcalPerUnit) assertEquals("2027-06-15", result.expiryDate) assertEquals(1, result.locationId) assertEquals("Vollkornbrot", result.notes) @@ -98,17 +98,17 @@ class PatchItemTest { setBody(seedInventory()) } - // When – only update kcalPerKg + // When – only update kcalPerUnit val response = client.patch("/api/inventory/items/item-patch-1") { bearerAuth(token) contentType(ContentType.Application.Json) - setBody("""{"kcalPerKg":3200}""") + setBody("""{"kcalPerUnit":3200}""") } // Then assertEquals(HttpStatusCode.OK, response.status) val result = json.decodeFromString(response.bodyAsText()) - assertEquals(3200, result.kcalPerKg) + assertEquals(3200, result.kcalPerUnit) assertEquals("Dosenbrot", result.name) assertEquals("item-patch-1", result.id) } diff --git a/server/src/test/kotlin/de/krisenvorrat/server/UserIsolationTest.kt b/server/src/test/kotlin/de/krisenvorrat/server/UserIsolationTest.kt index 2cab405..6c467aa 100644 --- a/server/src/test/kotlin/de/krisenvorrat/server/UserIsolationTest.kt +++ b/server/src/test/kotlin/de/krisenvorrat/server/UserIsolationTest.kt @@ -51,7 +51,7 @@ class UserIsolationTest { quantity = 5.0, unit = "Stück", unitPrice = 2.99, - kcalPerKg = null, + kcalPerUnit = null, expiryDate = null, locationId = 1, notes = "", @@ -87,7 +87,7 @@ class UserIsolationTest { ItemDto( id = "item-a1", name = "Dosenbrot", categoryId = 1, quantity = 5.0, unit = "Stück", unitPrice = 3.99, - kcalPerKg = null, expiryDate = null, locationId = 1, notes = "", lastUpdated = 100L + kcalPerUnit = null, expiryDate = null, locationId = 1, notes = "", lastUpdated = 100L ) ), settings = emptyList() @@ -99,7 +99,7 @@ class UserIsolationTest { ItemDto( id = "item-b1", name = "Seife", categoryId = 1, quantity = 3.0, unit = "Stück", unitPrice = 1.50, - kcalPerKg = null, expiryDate = null, locationId = 1, notes = "", lastUpdated = 200L + kcalPerUnit = null, expiryDate = null, locationId = 1, notes = "", lastUpdated = 200L ) ), settings = emptyList() diff --git a/server/src/test/kotlin/de/krisenvorrat/server/repository/InventoryRepositoryTest.kt b/server/src/test/kotlin/de/krisenvorrat/server/repository/InventoryRepositoryTest.kt index 46f9389..2602301 100644 --- a/server/src/test/kotlin/de/krisenvorrat/server/repository/InventoryRepositoryTest.kt +++ b/server/src/test/kotlin/de/krisenvorrat/server/repository/InventoryRepositoryTest.kt @@ -86,7 +86,7 @@ class InventoryRepositoryTest { assertEquals(5.0, item.quantity, 0.001) assertEquals("Stück", item.unit) assertEquals(3.99, item.unitPrice, 0.001) - assertEquals(250, item.kcalPerKg) + assertEquals(250, item.kcalPerUnit) assertEquals("2027-06-15", item.expiryDate) assertEquals(1, item.locationId) assertEquals("Vollkornbrot in der Dose", item.notes) @@ -137,7 +137,7 @@ class InventoryRepositoryTest { quantity = 10.0, unit = "Stück", unitPrice = 1.50, - kcalPerKg = null, + kcalPerUnit = null, expiryDate = null, locationId = 1, notes = "", @@ -153,7 +153,7 @@ class InventoryRepositoryTest { // Then val item = result.items[0] - assertNull(item.kcalPerKg) + assertNull(item.kcalPerUnit) assertNull(item.expiryDate) } @@ -174,7 +174,7 @@ class InventoryRepositoryTest { quantity = 5.0, unit = "Stück", unitPrice = 3.99, - kcalPerKg = 250, + kcalPerUnit = 250, expiryDate = "2027-06-15", locationId = 1, notes = "Vollkornbrot in der Dose", @@ -225,11 +225,11 @@ class InventoryRepositoryTest { locations = listOf(LocationDto(id = 1, name = "Test")), items = listOf( ItemDto(id = "old", name = "Old", categoryId = 1, quantity = 1.0, unit = "Stk", unitPrice = 0.0, - kcalPerKg = null, expiryDate = null, locationId = 1, notes = "", lastUpdated = oldTimestamp), + kcalPerUnit = null, expiryDate = null, locationId = 1, notes = "", lastUpdated = oldTimestamp), ItemDto(id = "recent1", name = "Recent1", categoryId = 1, quantity = 1.0, unit = "Stk", unitPrice = 0.0, - kcalPerKg = null, expiryDate = null, locationId = 1, notes = "", lastUpdated = recentTimestamp), + kcalPerUnit = null, expiryDate = null, locationId = 1, notes = "", lastUpdated = recentTimestamp), ItemDto(id = "recent2", name = "Recent2", categoryId = 1, quantity = 1.0, unit = "Stk", unitPrice = 0.0, - kcalPerKg = null, expiryDate = null, locationId = 1, notes = "", lastUpdated = now) + kcalPerUnit = null, expiryDate = null, locationId = 1, notes = "", lastUpdated = now) ), settings = emptyList() ) @@ -252,10 +252,10 @@ class InventoryRepositoryTest { locations = listOf(LocationDto(id = 1, name = "Test")), items = listOf( ItemDto(id = "old", name = "Old", categoryId = 1, quantity = 1.0, unit = "Stk", - unitPrice = 0.0, kcalPerKg = null, expiryDate = null, locationId = 1, + unitPrice = 0.0, kcalPerUnit = null, expiryDate = null, locationId = 1, notes = "", lastUpdated = 1000L), ItemDto(id = "new", name = "New", categoryId = 1, quantity = 1.0, unit = "Stk", - unitPrice = 0.0, kcalPerKg = null, expiryDate = null, locationId = 1, + unitPrice = 0.0, kcalPerUnit = null, expiryDate = null, locationId = 1, notes = "", lastUpdated = 3000L) ), settings = emptyList() @@ -319,7 +319,7 @@ class InventoryRepositoryTest { items = listOf( ItemDto( id = "inv-item-1", name = "Dosenbrot", categoryId = 1, quantity = 3.0, - unit = "Stk", unitPrice = 1.5, kcalPerKg = null, expiryDate = null, + unit = "Stk", unitPrice = 1.5, kcalPerUnit = null, expiryDate = null, locationId = 1, notes = "", lastUpdated = 1715000000L ) ), diff --git a/shared/src/main/kotlin/de/krisenvorrat/shared/model/ItemDto.kt b/shared/src/main/kotlin/de/krisenvorrat/shared/model/ItemDto.kt index da45ff1..3137c9c 100644 --- a/shared/src/main/kotlin/de/krisenvorrat/shared/model/ItemDto.kt +++ b/shared/src/main/kotlin/de/krisenvorrat/shared/model/ItemDto.kt @@ -10,7 +10,7 @@ data class ItemDto( val quantity: Double, val unit: String, val unitPrice: Double, - val kcalPerKg: Int?, + val kcalPerUnit: Int?, val expiryDate: String?, val locationId: Int, val notes: String,