diff --git a/Anforderungen/design/server-tech/candidates.md b/Anforderungen/design/server-tech/candidates.md new file mode 100644 index 0000000..7d6afd7 --- /dev/null +++ b/Anforderungen/design/server-tech/candidates.md @@ -0,0 +1,125 @@ +# Technology Candidates – REST-Server für Geräte-Synchronisierung (Phase 2) + +Date: 2026-05-14 +Requirements file: requirements.md + +--- + +## Candidate Table + +| Name | Description | Use case | Age | Adoption | Last release | Update cadence | Req. coverage | Score | +| ----------------- | --------------------------------------------------- | -------------------------------------------- | ---------------- | --------------------------- | ------------- | -------------- | ------------------------ | ----- | +| Ktor | Kotlin-nativer async Web-Framework von JetBrains | Microservices, APIs, leichtgewichtige Server | ~8 Jahre (2018) | Moderate (JetBrains-backed) | 2025 (3.x) | Monatlich | Alle Must + alle Should | **9** | +| Spring Boot | Enterprise-Java/Kotlin-Framework, Industriestandard | Enterprise-Apps, REST-APIs, Microservices | ~12 Jahre (2014) | Widespread | 2025 (3.x) | Monatlich | Alle Must, teilw. Should | **6** | +| Node.js + Express | JavaScript-Runtime + minimalistisches Web-Framework | APIs, Prototyping, Full-Stack JS | ~15 Jahre (2010) | Widespread | 2025 (5.x) | Regelmäßig | Alle Must, wenig Should | **5** | +| Python + FastAPI | Modernes Python-Web-Framework mit Type Hints | Schnelle API-Entwicklung, ML-Backends | ~7 Jahre (2018) | Widespread | 2025 (0.115+) | Monatlich | Alle Must, wenig Should | **5** | + +--- + +## Candidate Details + +### 1. Ktor (JetBrains) + +**Homepage:** https://ktor.io +**Repository:** https://github.com/ktorio/ktor + +**Requirement coverage:** + +- ✅ Must: REST-API (JSON), einfaches Deployment (Fat JAR / Docker), CRUD-Unterstützung, JSON via kotlinx.serialization nativ, aktiv gewartet (JetBrains), sehr geringer Ressourcenverbrauch +- ✅ Should: **Kotlin-nativ** – gleiche Sprache wie Client, Datenmodelle direkt teilbar, kotlinx.serialization als First-Class-Citizen, Coroutines-basiert, kein Application-Server nötig (eingebetteter Netty/CIO), exzellente Dokumentation (ktor.io), einfache DB-Anbindung (Exposed ORM), einfaches Testing +- ✅ Nice-to-Have: WebSocket-Support eingebaut, Authentifizierungs-Plugins, Docker-ready (Fat JAR), OpenAPI-Plugin verfügbar, Hot-Reload via Auto-Reload-Feature, Type-safe Routing + +**Stärken:** + +- **Gleiche Sprache wie der Client** – Datenmodelle (`@Serializable`-Klassen) können in einem Shared-Modul zwischen Server und Client geteilt werden +- Koroutinen-basiert – konsistent mit dem Android-Client +- Sehr leichtgewichtig – Start in < 1 Sekunde, wenig RAM +- JetBrains-maintained – langfristige Pflege gesichert +- Modulares Plugin-System – nur einbinden was man braucht + +**Risiken / Schwächen:** + +- Kleinere Community als Spring Boot – weniger StackOverflow-Antworten +- Weniger Enterprise-Features out of the box (kein Spring-Ökosystem) +- Bei komplexen Anforderungen muss man mehr selbst bauen + +--- + +### 2. Spring Boot + +**Homepage:** https://spring.io/projects/spring-boot +**Repository:** https://github.com/spring-projects/spring-boot + +**Requirement coverage:** + +- ✅ Must: REST-API, Deployment, CRUD, JSON, aktiv gewartet, stabil +- ⚠️ Should: Kotlin-Support vorhanden aber nicht nativ (Java-First), kotlinx.serialization nicht Standard (Jackson wird bevorzugt), schwergewichtig (Tomcat eingebettet, hoher RAM-Verbrauch), Coroutines-Support nur via WebFlux +- ✅ Nice-to-Have: WebSocket, Spring Security, Docker, Swagger/OpenAPI, DevTools Hot-Reload + +**Stärken:** + +- Industriestandard mit riesiger Community und Ökosystem +- Extrem viele Tutorials, Bücher, StackOverflow-Antworten +- Battle-tested in Produktion bei Millionen von Projekten +- Spring Security für Authentication/Authorization + +**Risiken / Schwächen:** + +- **Overhead für ein kleines Projekt:** Hoher RAM-Verbrauch (200–500 MB), langsame Startzeit, viel Boilerplate +- **Java-First:** Kotlin ist Second-Class-Citizen – Spring-Idiome (Annotations, Bean-Wiring) fühlen sich in Kotlin weniger natürlich an +- **Keine geteilten Datenmodelle:** kotlinx.serialization wird nicht nativ unterstützt, Jackson-Annotationen nötig – das Client-Datenmodell kann nicht 1:1 wiederverwendet werden +- Für 2–10 Nutzer massiv überdimensioniert + +--- + +### 3. Node.js + Express + +**Homepage:** https://expressjs.com +**Repository:** https://github.com/expressjs/express + +**Requirement coverage:** + +- ✅ Must: REST-API, einfaches Deployment, CRUD, JSON nativ, aktiv gewartet, geringer Verbrauch +- ❌ Should: **Andere Sprache (JavaScript/TypeScript)** – keine Wiederverwendung von Kotlin-Datenmodellen, keine Coroutines, kein kotlinx.serialization, andere Toolchain (npm vs. Gradle) +- ⚠️ Nice-to-Have: WebSocket (via ws/socket.io), Passport.js für Auth, Docker einfach, OpenAPI-Generierung möglich + +**Stärken:** + +- Extrem leichtgewichtig und schnell aufzusetzen +- Riesige Community, npm-Ökosystem +- Sehr gute Performance für I/O-lastige Workloads +- Viele fertige Middleware-Pakete + +**Risiken / Schwächen:** + +- **Sprachwechsel:** Kotlin (Client) ↔ JavaScript (Server) – kein Code-Sharing, doppelte Modell-Definition +- **Typsicherheit:** JavaScript bietet weniger Typsicherheit als Kotlin (TypeScript hilft, ist aber ein separates Ökosystem) +- **Dependency-Overhead:** npm-Ökosystem hat bekannte Supply-Chain-Risiken +- Separates Tooling und Build-System nötig + +--- + +### 4. Python + FastAPI + +**Homepage:** https://fastapi.tiangolo.com +**Repository:** https://github.com/tiangolo/fastapi + +**Requirement coverage:** + +- ✅ Must: REST-API, einfaches Deployment, CRUD, JSON, aktiv gewartet, geringer Verbrauch +- ❌ Should: **Andere Sprache (Python)** – keine Wiederverwendung von Kotlin-Datenmodellen, kein kotlinx.serialization, andere Toolchain (pip vs. Gradle), kein Coroutines-Bezug +- ✅ Nice-to-Have: WebSocket-Support, OAuth2-Support, Docker einfach, **automatische OpenAPI/Swagger-Generierung** (Highlight!), Hot-Reload via uvicorn + +**Stärken:** + +- Extrem schnelle Entwicklung dank Auto-Dokumentation und Type Hints +- Automatische OpenAPI-Doku out of the box +- Async I/O via asyncio +- Gut für Prototyping + +**Risiken / Schwächen:** + +- **Sprachwechsel:** Kotlin ↔ Python – komplett andere Toolchain, kein Code-Sharing +- **GIL:** Python Global Interpreter Lock kann bei CPU-intensiven Operationen limitieren (hier aber irrelevant) +- **Deployment:** Python-Umgebungen können fragiler sein als JVM-Fat-JARs (venv, pip-Abhängigkeiten) +- Weniger natürliche Integration mit dem Kotlin/Gradle-basierten Projekt diff --git a/Anforderungen/design/server-tech/requirements.md b/Anforderungen/design/server-tech/requirements.md new file mode 100644 index 0000000..62708bd --- /dev/null +++ b/Anforderungen/design/server-tech/requirements.md @@ -0,0 +1,60 @@ +# Technology Requirements – REST-Server für Geräte-Synchronisierung (Phase 2) + +Date: 2026-05-14 +Issue: #10 +Author: Tech-Decision Workflow + +--- + +## Kontext + +Die Krisenvorrat-App (Android/Kotlin) speichert in v1.0 alle Daten lokal (Room/SQLite). In Phase 2 soll ein REST-Server hinzukommen, der die Synchronisierung und das Sharing des Inventars zwischen mehreren Geräten ermöglicht. + +**Einsatzszenario:** + +- Privater Gebrauch, kein kommerzieller Betrieb +- Wenige Geräte (2–10), keine hohe Last +- Während Entwicklung: Server lokal auf Laptop, Testgerät im LAN +- Später: kleiner Server im Internet (VPS / Homeserver) +- Datenformat: JSON (kotlinx.serialization, bereits im Client definiert) + +**Vom Issue genannte Optionen:** Ktor, Spring Boot, Node.js/Express, Python/FastAPI + +--- + +## Must-Have (Eliminatoren) + +- REST-API bereitstellen (JSON Request/Response) +- Einfaches Deployment auf einem kleinen Linux-VPS oder Homeserver +- Unterstützung für grundlegende CRUD-Operationen (Inventar-Items, Kategorien, Lagerorte) +- JSON als primäres Datenformat (kompatibel mit dem bestehenden Client-Datenmodell) +- Aktiv gewartet, stabile Release-Versionen verfügbar +- Geringe Betriebskosten (wenig RAM/CPU-Verbrauch für wenige Nutzer) + +## Should-Have (gewichtete Pluspunkte) + +- **Kotlin-Kompatibilität:** Gleiche Sprache wie der Android-Client (Wiederverwendung von Datenmodellen, kotlinx.serialization) +- Einfache Einrichtung und geringer Boilerplate +- Integrierte Unterstützung für Coroutines / async I/O +- Leichtgewichtig (kein Application-Server wie Tomcat nötig) +- Gute Dokumentation und Lernressourcen +- Einfache Datenbankanbindung (SQLite oder PostgreSQL) +- Einfaches Testen (Unit- und Integrationstests) + +## Nice-to-Have (Bonus) + +- WebSocket-Unterstützung (für Echtzeit-Sync in einer späteren Phase) +- Eingebaute Authentifizierung / Sicherheitsmechanismen +- Docker-Image oder einfache Containerisierung +- OpenAPI / Swagger-Generierung +- Hot-Reload / schnelle Entwicklungszyklen +- Type-safe Routing + +## Constraints + +- **Zielplattform Server:** Linux (VPS / Homeserver), optional auch Windows/macOS für lokale Entwicklung +- **Client-Sprache:** Kotlin (Android) – geteilte Modelle sind ein großer Vorteil +- **Serialisierung:** kotlinx.serialization (bereits im Client im Einsatz) +- **Lizenz:** Open Source (MIT, Apache 2.0 o.ä.) +- **Skalierung:** Nicht relevant – max. 10 gleichzeitige Nutzer +- **Budget:** Kein Budget für kommerzielle Lizenzen oder teure Hosting-Lösungen 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 a3b8e89..68e254e 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,10 +32,9 @@ 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_100g") val kcalPer100g: Int?, + @ColumnInfo(name = "kcal_per_kg") val kcalPerKg: Int?, @ColumnInfo(name = "expiry_date") val expiryDate: LocalDate?, @ColumnInfo(name = "location_id") val locationId: Int, - @ColumnInfo(name = "min_stock") val minStock: Double, @ColumnInfo(name = "notes") val notes: String, @ColumnInfo(name = "last_updated") val lastUpdated: Long ) 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 b673c99..6c1aa01 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 @@ -64,10 +64,9 @@ internal class ImportExportRepositoryImpl @Inject constructor( quantity = item.quantity, unit = item.unit, unitPrice = item.unitPrice, - kcalPer100g = item.kcalPer100g, + kcalPerKg = item.kcalPerKg, expiryDate = item.expiryDate?.toString(), locationId = item.locationId, - minStock = item.minStock, notes = item.notes, lastUpdated = item.lastUpdated ) @@ -102,10 +101,9 @@ internal class ImportExportRepositoryImpl @Inject constructor( quantity = item.quantity, unit = item.unit, unitPrice = item.unitPrice, - kcalPer100g = item.kcalPer100g, + kcalPerKg = item.kcalPerKg, expiryDate = item.expiryDate?.let { LocalDate.parse(it) }, locationId = item.locationId, - minStock = item.minStock, notes = item.notes, lastUpdated = item.lastUpdated ) 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 c614225..7e1c1e7 100644 --- a/app/src/main/java/de/krisenvorrat/app/di/DatabaseModule.kt +++ b/app/src/main/java/de/krisenvorrat/app/di/DatabaseModule.kt @@ -27,6 +27,7 @@ internal object DatabaseModule { fun provideDatabase(@ApplicationContext context: Context): KrisenvorratDatabase = Room.databaseBuilder(context, KrisenvorratDatabase::class.java, "krisenvorrat.db") .addCallback(DefaultDataCallback) + .fallbackToDestructiveMigration() .build() private object DefaultDataCallback : RoomDatabase.Callback() { diff --git a/app/src/main/java/de/krisenvorrat/app/domain/model/MinStockWarning.kt b/app/src/main/java/de/krisenvorrat/app/domain/model/MinStockWarning.kt deleted file mode 100644 index b71f1f0..0000000 --- a/app/src/main/java/de/krisenvorrat/app/domain/model/MinStockWarning.kt +++ /dev/null @@ -1,8 +0,0 @@ -package de.krisenvorrat.app.domain.model - -import de.krisenvorrat.app.data.db.entity.ItemEntity - -internal data class MinStockWarning( - val item: ItemEntity, - val deficit: Double -) 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 7a492a4..3db6fa7 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,9 +19,9 @@ internal class CalculateSupplyRangeUseCase @Inject constructor() { if (dailyNeed <= 0) return 0.0 val totalKcal = items.sumOf { item -> - val kcalPer100g = item.kcalPer100g ?: return@sumOf 0.0 + val kcalPerKg = item.kcalPerKg ?: return@sumOf 0.0 val grams = convertToGrams(item.quantity, item.unit) ?: return@sumOf 0.0 - (grams / 100.0) * kcalPer100g + (grams / 1000.0) * kcalPerKg } return totalKcal / dailyNeed diff --git a/app/src/main/java/de/krisenvorrat/app/domain/usecase/GetMinStockWarningsUseCase.kt b/app/src/main/java/de/krisenvorrat/app/domain/usecase/GetMinStockWarningsUseCase.kt deleted file mode 100644 index fa86447..0000000 --- a/app/src/main/java/de/krisenvorrat/app/domain/usecase/GetMinStockWarningsUseCase.kt +++ /dev/null @@ -1,20 +0,0 @@ -package de.krisenvorrat.app.domain.usecase - -import de.krisenvorrat.app.data.db.entity.ItemEntity -import de.krisenvorrat.app.domain.model.MinStockWarning -import javax.inject.Inject - -internal class GetMinStockWarningsUseCase @Inject constructor() { - - operator fun invoke(items: List): List { - return items - .filter { it.quantity < it.minStock } - .map { item -> - MinStockWarning( - item = item, - deficit = item.minStock - item.quantity - ) - } - .sortedByDescending { it.deficit } - } -} diff --git a/app/src/main/java/de/krisenvorrat/app/ui/dashboard/DashboardScreen.kt b/app/src/main/java/de/krisenvorrat/app/ui/dashboard/DashboardScreen.kt index 65b419c..24f07eb 100644 --- a/app/src/main/java/de/krisenvorrat/app/ui/dashboard/DashboardScreen.kt +++ b/app/src/main/java/de/krisenvorrat/app/ui/dashboard/DashboardScreen.kt @@ -60,11 +60,10 @@ internal fun DashboardScreen( item { SupplyRangeCard(supplyRangeDays = uiState.supplyRangeDays) } - if (uiState.hasExpiryWarnings || uiState.hasMinStockWarnings) { + if (uiState.hasExpiryWarnings) { item { WarningsSummaryCard( - expiryCount = uiState.expiryWarnings.size, - minStockCount = uiState.minStockWarnings.size + expiryCount = uiState.expiryWarnings.size ) } } @@ -176,8 +175,8 @@ private fun SupplyRangeCard(supplyRangeDays: Double) { } @Composable -private fun WarningsSummaryCard(expiryCount: Int, minStockCount: Int) { - val totalCount = expiryCount + minStockCount +private fun WarningsSummaryCard(expiryCount: Int) { + val totalCount = expiryCount Card( modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors( @@ -198,13 +197,6 @@ private fun WarningsSummaryCard(expiryCount: Int, minStockCount: Int) { color = MaterialTheme.colorScheme.onErrorContainer ) } - if (minStockCount > 0) { - Text( - text = "$minStockCount Mindestbestand-Warnungen", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onErrorContainer - ) - } } } } diff --git a/app/src/main/java/de/krisenvorrat/app/ui/dashboard/DashboardUiState.kt b/app/src/main/java/de/krisenvorrat/app/ui/dashboard/DashboardUiState.kt index e5661b5..48f45c0 100644 --- a/app/src/main/java/de/krisenvorrat/app/ui/dashboard/DashboardUiState.kt +++ b/app/src/main/java/de/krisenvorrat/app/ui/dashboard/DashboardUiState.kt @@ -2,14 +2,12 @@ package de.krisenvorrat.app.ui.dashboard import de.krisenvorrat.app.domain.model.CategorySummary import de.krisenvorrat.app.domain.model.ExpiryWarning -import de.krisenvorrat.app.domain.model.MinStockWarning internal data class DashboardUiState( val categorySummaries: List = emptyList(), val totalValue: Double = 0.0, val supplyRangeDays: Double = 0.0, val expiryWarnings: List = emptyList(), - val minStockWarnings: List = emptyList(), val isLoading: Boolean = true ) { val totalItemCount: Int @@ -17,7 +15,4 @@ internal data class DashboardUiState( val hasExpiryWarnings: Boolean get() = expiryWarnings.isNotEmpty() - - val hasMinStockWarnings: Boolean - get() = minStockWarnings.isNotEmpty() } diff --git a/app/src/main/java/de/krisenvorrat/app/ui/dashboard/DashboardViewModel.kt b/app/src/main/java/de/krisenvorrat/app/ui/dashboard/DashboardViewModel.kt index 81a110b..2c0e26a 100644 --- a/app/src/main/java/de/krisenvorrat/app/ui/dashboard/DashboardViewModel.kt +++ b/app/src/main/java/de/krisenvorrat/app/ui/dashboard/DashboardViewModel.kt @@ -12,7 +12,6 @@ import de.krisenvorrat.app.domain.usecase.CalculateCategorySummaryUseCase import de.krisenvorrat.app.domain.usecase.CalculateSupplyRangeUseCase import de.krisenvorrat.app.domain.usecase.CalculateTotalValueUseCase import de.krisenvorrat.app.domain.usecase.GetExpiryWarningsUseCase -import de.krisenvorrat.app.domain.usecase.GetMinStockWarningsUseCase import de.krisenvorrat.app.domain.repository.CategoryRepository import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -30,8 +29,7 @@ internal class DashboardViewModel @Inject constructor( private val calculateCategorySummary: CalculateCategorySummaryUseCase, private val calculateTotalValue: CalculateTotalValueUseCase, private val calculateSupplyRange: CalculateSupplyRangeUseCase, - private val getExpiryWarnings: GetExpiryWarningsUseCase, - private val getMinStockWarnings: GetMinStockWarningsUseCase + private val getExpiryWarnings: GetExpiryWarningsUseCase ) : ViewModel() { private val _uiState = MutableStateFlow(DashboardUiState()) @@ -54,7 +52,6 @@ internal class DashboardViewModel @Inject constructor( totalValue = calculateTotalValue(items), supplyRangeDays = calculateSupplyRange(items, totalDailyKcal), expiryWarnings = getExpiryWarnings(items), - minStockWarnings = getMinStockWarnings(items), isLoading = false ) }.collect { state -> 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 b63807b..b94fdfb 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 @@ -177,11 +177,11 @@ internal fun ItemFormScreen( Spacer(modifier = Modifier.height(8.dp)) - // kcal/100g + // kcal/kg OutlinedTextField( - value = uiState.kcalPer100g, - onValueChange = viewModel::updateKcalPer100g, - label = { Text("kcal / 100g") }, + value = uiState.kcalPerKg, + onValueChange = viewModel::updateKcalPerKg, + label = { Text("kcal / kg") }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), singleLine = true, modifier = Modifier.fillMaxWidth() @@ -189,18 +189,6 @@ internal fun ItemFormScreen( Spacer(modifier = Modifier.height(8.dp)) - // Mindestbestand - OutlinedTextField( - value = uiState.minStock, - onValueChange = viewModel::updateMinStock, - label = { Text("Mindestbestand") }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), - singleLine = true, - modifier = Modifier.fillMaxWidth() - ) - - Spacer(modifier = Modifier.height(8.dp)) - // Notizen OutlinedTextField( value = uiState.notes, 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 4ad4ac7..953158e 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 @@ -27,10 +27,9 @@ internal data class ItemFormUiState( val quantity: String = "", val unit: String = "", val unitPrice: String = "", - val kcalPer100g: String = "", + val kcalPerKg: String = "", val expiryDate: LocalDate? = null, val locationId: Int? = null, - val minStock: String = "", val notes: String = "", val categories: List = emptyList(), val locations: List = emptyList(), @@ -109,10 +108,9 @@ internal class ItemFormViewModel @Inject constructor( quantity = item.quantity.toBigDecimal().stripTrailingZeros().toPlainString(), unit = item.unit, unitPrice = item.unitPrice.toBigDecimal().stripTrailingZeros().toPlainString(), - kcalPer100g = item.kcalPer100g?.toString() ?: "", + kcalPerKg = item.kcalPerKg?.toString() ?: "", expiryDate = item.expiryDate, locationId = item.locationId, - minStock = item.minStock.toBigDecimal().stripTrailingZeros().toPlainString(), notes = item.notes, isLoading = false ) @@ -146,8 +144,8 @@ internal class ItemFormViewModel @Inject constructor( _uiState.update { it.copy(unitPrice = value) } } - fun updateKcalPer100g(value: String) { - _uiState.update { it.copy(kcalPer100g = value) } + fun updateKcalPerKg(value: String) { + _uiState.update { it.copy(kcalPerKg = value) } } fun updateExpiryDate(value: LocalDate?) { @@ -158,10 +156,6 @@ internal class ItemFormViewModel @Inject constructor( _uiState.update { it.copy(locationId = value, validationErrors = it.validationErrors - "locationId") } } - fun updateMinStock(value: String) { - _uiState.update { it.copy(minStock = value) } - } - fun updateNotes(value: String) { _uiState.update { it.copy(notes = value) } } @@ -183,10 +177,9 @@ internal class ItemFormViewModel @Inject constructor( quantity = state.quantity.toDouble(), unit = state.unit.trim(), unitPrice = state.unitPrice.toDoubleOrNull() ?: 0.0, - kcalPer100g = state.kcalPer100g.toIntOrNull(), + kcalPerKg = state.kcalPerKg.toIntOrNull(), expiryDate = state.expiryDate, locationId = state.locationId!!, - minStock = state.minStock.toDoubleOrNull() ?: 0.0, notes = state.notes.trim(), lastUpdated = System.currentTimeMillis() ) 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 e9bf7ac..71661db 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 @@ -82,10 +82,9 @@ internal class ItemListViewModel @Inject constructor( quantity = item.quantity, unit = item.unit, unitPrice = 0.0, - kcalPer100g = null, + kcalPerKg = null, expiryDate = item.expiryDate, locationId = 0, - minStock = 0.0, notes = "", lastUpdated = 0L ) diff --git a/app/src/main/java/de/krisenvorrat/app/ui/warnings/WarningsScreen.kt b/app/src/main/java/de/krisenvorrat/app/ui/warnings/WarningsScreen.kt index 8563d6b..a26ea7e 100644 --- a/app/src/main/java/de/krisenvorrat/app/ui/warnings/WarningsScreen.kt +++ b/app/src/main/java/de/krisenvorrat/app/ui/warnings/WarningsScreen.kt @@ -29,7 +29,6 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import de.krisenvorrat.app.domain.model.ExpiryUrgency import de.krisenvorrat.app.domain.model.ExpiryWarning -import de.krisenvorrat.app.domain.model.MinStockWarning import java.util.Locale @OptIn(ExperimentalMaterial3Api::class) @@ -84,19 +83,6 @@ internal fun WarningsScreen( } } - if (uiState.hasMinStockWarnings) { - item { - Text( - text = "Mindestbestand unterschritten (${uiState.minStockWarnings.size})", - style = MaterialTheme.typography.titleMedium, - modifier = Modifier.padding(top = 4.dp) - ) - } - items(uiState.minStockWarnings) { warning -> - MinStockWarningItem(warning) - } - } - item { Spacer(modifier = Modifier.height(8.dp)) } } } @@ -145,32 +131,4 @@ private fun ExpiryWarningItem(warning: ExpiryWarning) { } } -@Composable -private fun MinStockWarningItem(warning: MinStockWarning) { - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.errorContainer - ) - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = warning.item.name, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onErrorContainer - ) - Text( - text = "fehlen: ${String.format(Locale.GERMANY, "%.1f", warning.deficit)} ${warning.item.unit}", - style = MaterialTheme.typography.bodyMedium, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.error - ) - } - } -} + diff --git a/app/src/main/java/de/krisenvorrat/app/ui/warnings/WarningsUiState.kt b/app/src/main/java/de/krisenvorrat/app/ui/warnings/WarningsUiState.kt index 10841fe..91b3e90 100644 --- a/app/src/main/java/de/krisenvorrat/app/ui/warnings/WarningsUiState.kt +++ b/app/src/main/java/de/krisenvorrat/app/ui/warnings/WarningsUiState.kt @@ -1,22 +1,17 @@ package de.krisenvorrat.app.ui.warnings import de.krisenvorrat.app.domain.model.ExpiryWarning -import de.krisenvorrat.app.domain.model.MinStockWarning internal data class WarningsUiState( val expiryWarnings: List = emptyList(), - val minStockWarnings: List = emptyList(), val isLoading: Boolean = true ) { val hasExpiryWarnings: Boolean get() = expiryWarnings.isNotEmpty() - val hasMinStockWarnings: Boolean - get() = minStockWarnings.isNotEmpty() - val totalWarningCount: Int - get() = expiryWarnings.size + minStockWarnings.size + get() = expiryWarnings.size val hasWarnings: Boolean - get() = hasExpiryWarnings || hasMinStockWarnings + get() = hasExpiryWarnings } diff --git a/app/src/main/java/de/krisenvorrat/app/ui/warnings/WarningsViewModel.kt b/app/src/main/java/de/krisenvorrat/app/ui/warnings/WarningsViewModel.kt index c66ef95..c58c8e5 100644 --- a/app/src/main/java/de/krisenvorrat/app/ui/warnings/WarningsViewModel.kt +++ b/app/src/main/java/de/krisenvorrat/app/ui/warnings/WarningsViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import de.krisenvorrat.app.domain.repository.ItemRepository import de.krisenvorrat.app.domain.usecase.GetExpiryWarningsUseCase -import de.krisenvorrat.app.domain.usecase.GetMinStockWarningsUseCase import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -16,8 +15,7 @@ import javax.inject.Inject @HiltViewModel internal class WarningsViewModel @Inject constructor( private val itemRepository: ItemRepository, - private val getExpiryWarnings: GetExpiryWarningsUseCase, - private val getMinStockWarnings: GetMinStockWarningsUseCase + private val getExpiryWarnings: GetExpiryWarningsUseCase ) : ViewModel() { private val _uiState = MutableStateFlow(WarningsUiState()) @@ -29,7 +27,6 @@ internal class WarningsViewModel @Inject constructor( _uiState.update { WarningsUiState( expiryWarnings = getExpiryWarnings(items), - minStockWarnings = getMinStockWarnings(items), isLoading = false ) } 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 961a1c6..cd6ccd5 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,"kcalPer100g":null,"expiryDate":null,"locationId":1,"minStock":1.0,"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,"kcalPerKg":null,"expiryDate":null,"locationId":1,"notes":"","lastUpdated":0}],"settings":[{"key":"theme","value":"dark"}]}""" val categoryDao = FakeCategoryDao() val locationDao = FakeLocationDao() val itemDao = FakeItemDao() 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 a9ead2f..736fd4c 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,10 +44,9 @@ class JsonRoundtripTest { quantity = 10.0, unit = "Stk", unitPrice = 2.49, - kcalPer100g = 180, + kcalPerKg = 180, expiryDate = LocalDate.of(2027, 6, 15), locationId = 1, - minStock = 5.0, notes = "Ravioli", lastUpdated = 1700000000L ), @@ -58,10 +57,9 @@ class JsonRoundtripTest { quantity = 3.0, unit = "Stk", unitPrice = 0.99, - kcalPer100g = null, + kcalPerKg = null, expiryDate = null, locationId = 2, - minStock = 1.0, notes = "", lastUpdated = 1700000001L ) @@ -121,10 +119,9 @@ class JsonRoundtripTest { assertEquals(original.quantity, imported?.quantity) assertEquals(original.unit, imported?.unit) assertEquals(original.unitPrice, imported?.unitPrice) - assertEquals(original.kcalPer100g, imported?.kcalPer100g) + assertEquals(original.kcalPerKg, imported?.kcalPerKg) assertEquals(original.expiryDate, imported?.expiryDate) assertEquals(original.locationId, imported?.locationId) - assertEquals(original.minStock, imported?.minStock) assertEquals(original.notes, imported?.notes) assertEquals(original.lastUpdated, imported?.lastUpdated) } @@ -154,10 +151,9 @@ class JsonRoundtripTest { quantity = 1.0, unit = "Stk", unitPrice = 0.0, - kcalPer100g = null, + kcalPerKg = null, expiryDate = null, locationId = 1, - minStock = 0.0, notes = "", lastUpdated = 0L ) @@ -176,7 +172,7 @@ class JsonRoundtripTest { // Then assertTrue(result.isSuccess) val imported = importItemDao.getItems().first() - assertEquals(null, imported.kcalPer100g) + assertEquals(null, imported.kcalPerKg) 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 d37e921..f57516f 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 @@ -121,10 +121,9 @@ internal fun buildItemEntity(id: String = "item1") = ItemEntity( quantity = 2.0, unit = "Stk", unitPrice = 1.5, - kcalPer100g = null, + kcalPerKg = null, expiryDate = null, locationId = 1, - minStock = 1.0, notes = "", lastUpdated = 0L ) 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 89df0c6..940d9bb 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 @@ -92,10 +92,9 @@ private fun buildItem( quantity = 2.0, unit = "Stk", unitPrice = 1.5, - kcalPer100g = null, + kcalPerKg = null, expiryDate = null, locationId = locationId, - minStock = 1.0, notes = "", lastUpdated = 0L ) 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 1ad7642..4bba1cd 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 @@ -47,10 +47,9 @@ class SyncServiceImplTest { quantity = 5.0, unit = "Dose", unitPrice = 1.29, - kcalPer100g = 100, + kcalPerKg = 100, expiryDate = "2027-06-01", locationId = 1, - minStock = 2.0, notes = "", lastUpdated = System.currentTimeMillis() ) 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 0de8578..df8e820 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 à 350 kcal/100g = 7000 kcal + // Given – 2 kg Reis à 3500 kcal/kg = 7000 kcal // 4000 kcal/Tag (2 × 2000) → 1.75 Tage val items = listOf( - buildTestItem(id = "1", quantity = 2.0, unit = "kg", kcalPer100g = 350) + buildTestItem(id = "1", quantity = 2.0, unit = "kg", kcalPerKg = 3500) ) // When @@ -24,10 +24,10 @@ class CalculateSupplyRangeUseCaseTest { @Test fun test_invoke_withGramItems_returnsCorrectDays() { - // Given – 500 g Nudeln à 360 kcal/100g = 1800 kcal + // Given – 500 g Nudeln à 3600 kcal/kg = 1800 kcal // 2000 kcal/Tag → 0.9 Tage val items = listOf( - buildTestItem(id = "1", quantity = 500.0, unit = "g", kcalPer100g = 360) + buildTestItem(id = "1", quantity = 500.0, unit = "g", kcalPerKg = 3600) ) // When @@ -39,11 +39,11 @@ class CalculateSupplyRangeUseCaseTest { @Test fun test_invoke_withMultipleItems_sumsTotalKcal() { - // Given – 1 kg Reis (350 kcal/100g = 3500 kcal) + 500 g Nudeln (360 kcal/100g = 1800 kcal) = 5300 kcal + // Given – 1 kg Reis (3500 kcal/kg = 3500 kcal) + 500 g Nudeln (3600 kcal/kg = 1800 kcal) = 5300 kcal // 4000 kcal/Tag → 1.325 Tage val items = listOf( - buildTestItem(id = "1", quantity = 1.0, unit = "kg", kcalPer100g = 350), - buildTestItem(id = "2", quantity = 500.0, unit = "g", kcalPer100g = 360) + buildTestItem(id = "1", quantity = 1.0, unit = "kg", kcalPerKg = 3500), + buildTestItem(id = "2", quantity = 500.0, unit = "g", kcalPerKg = 3600) ) // When @@ -64,13 +64,13 @@ class CalculateSupplyRangeUseCaseTest { @Test fun test_invoke_withNullKcal_skipsItem() { - // Given – Item ohne kcalPer100g wird ignoriert + // Given – Item ohne kcalPerKg wird ignoriert val items = listOf( - buildTestItem(id = "1", quantity = 1.0, unit = "kg", kcalPer100g = null), - buildTestItem(id = "2", quantity = 1.0, unit = "kg", kcalPer100g = 200) + buildTestItem(id = "1", quantity = 1.0, unit = "kg", kcalPerKg = null), + buildTestItem(id = "2", quantity = 1.0, unit = "kg", kcalPerKg = 2000) ) - // When – Nur 1 kg à 200 kcal/100g = 2000 kcal, 2000 kcal/Tag = 1.0 Tage + // When – Nur 1 kg à 2000 kcal/kg = 2000 kcal, 2000 kcal/Tag = 1.0 Tage val result = useCase(items, totalDailyKcal = 2000) // Then @@ -81,7 +81,7 @@ class CalculateSupplyRangeUseCaseTest { fun test_invoke_withNonWeightUnit_skipsItem() { // Given – "Stk" ist keine Gewichtseinheit → wird ignoriert val items = listOf( - buildTestItem(id = "1", quantity = 5.0, unit = "Stk", kcalPer100g = 200) + buildTestItem(id = "1", quantity = 5.0, unit = "Stk", kcalPerKg = 2000) ) // When @@ -95,7 +95,7 @@ class CalculateSupplyRangeUseCaseTest { fun test_invoke_withZeroTotalDailyKcal_returnsZero() { // Given val items = listOf( - buildTestItem(id = "1", quantity = 1.0, unit = "kg", kcalPer100g = 350) + buildTestItem(id = "1", quantity = 1.0, unit = "kg", kcalPerKg = 3500) ) // When @@ -107,10 +107,10 @@ class CalculateSupplyRangeUseCaseTest { @Test fun test_invoke_withDefaultParameters_uses4000KcalPerDay() { - // Given – 4 kg Reis à 350 kcal/100g = 14000 kcal + // Given – 4 kg Reis à 3500 kcal/kg = 14000 kcal // Default: 4000 kcal/Tag → 3.5 Tage val items = listOf( - buildTestItem(id = "1", quantity = 4.0, unit = "kg", kcalPer100g = 350) + buildTestItem(id = "1", quantity = 4.0, unit = "kg", kcalPerKg = 3500) ) // When @@ -122,10 +122,10 @@ class CalculateSupplyRangeUseCaseTest { @Test fun test_invoke_withMgUnit_convertsCorrectly() { - // Given – 500000 mg = 500 g à 200 kcal/100g = 1000 kcal + // Given – 500000 mg = 500 g à 2000 kcal/kg = 1000 kcal // 1000 kcal/Tag → 1.0 Tag val items = listOf( - buildTestItem(id = "1", quantity = 500000.0, unit = "mg", kcalPer100g = 200) + buildTestItem(id = "1", quantity = 500000.0, unit = "mg", kcalPerKg = 2000) ) // When @@ -137,12 +137,12 @@ class CalculateSupplyRangeUseCaseTest { @Test fun test_invoke_withMixedUnits_onlyCountsWeightBased() { - // Given – 1 kg (350 kcal/100g) + 5 Stk (ignored) + 2 L (ignored) + // Given – 1 kg (3500 kcal/kg) + 5 Stk (ignored) + 2 L (ignored) // Total: 3500 kcal, 2000 kcal/Tag → 1.75 Tage val items = listOf( - buildTestItem(id = "1", quantity = 1.0, unit = "kg", kcalPer100g = 350), - buildTestItem(id = "2", quantity = 5.0, unit = "Stk", kcalPer100g = 100), - buildTestItem(id = "3", quantity = 2.0, unit = "L", kcalPer100g = 45) + 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) ) // When diff --git a/app/src/test/java/de/krisenvorrat/app/domain/usecase/GetMinStockWarningsUseCaseTest.kt b/app/src/test/java/de/krisenvorrat/app/domain/usecase/GetMinStockWarningsUseCaseTest.kt deleted file mode 100644 index eead21c..0000000 --- a/app/src/test/java/de/krisenvorrat/app/domain/usecase/GetMinStockWarningsUseCaseTest.kt +++ /dev/null @@ -1,128 +0,0 @@ -package de.krisenvorrat.app.domain.usecase - -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import org.junit.Test - -class GetMinStockWarningsUseCaseTest { - - private val useCase = GetMinStockWarningsUseCase() - - @Test - fun test_invoke_withItemBelowMinStock_returnsWarning() { - // Given - val items = listOf( - buildTestItem(id = "1", quantity = 2.0, minStock = 5.0) - ) - - // When - val result = useCase(items) - - // Then - assertEquals(1, result.size) - assertEquals(3.0, result[0].deficit, 0.001) - } - - @Test - fun test_invoke_withItemAtMinStock_returnsNoWarning() { - // Given - val items = listOf( - buildTestItem(id = "1", quantity = 5.0, minStock = 5.0) - ) - - // When - val result = useCase(items) - - // Then - assertTrue(result.isEmpty()) - } - - @Test - fun test_invoke_withItemAboveMinStock_returnsNoWarning() { - // Given - val items = listOf( - buildTestItem(id = "1", quantity = 10.0, minStock = 5.0) - ) - - // When - val result = useCase(items) - - // Then - assertTrue(result.isEmpty()) - } - - @Test - fun test_invoke_withEmptyList_returnsEmptyList() { - // Given / When - val result = useCase(emptyList()) - - // Then - assertTrue(result.isEmpty()) - } - - @Test - fun test_invoke_withZeroMinStock_returnsNoWarning() { - // Given - val items = listOf( - buildTestItem(id = "1", quantity = 0.0, minStock = 0.0) - ) - - // When - val result = useCase(items) - - // Then - assertTrue(result.isEmpty()) - } - - @Test - fun test_invoke_withZeroQuantity_returnsWarning() { - // Given - val items = listOf( - buildTestItem(id = "1", quantity = 0.0, minStock = 3.0) - ) - - // When - val result = useCase(items) - - // Then - assertEquals(1, result.size) - assertEquals(3.0, result[0].deficit, 0.001) - } - - @Test - fun test_invoke_resultIsSortedByDeficitDescending() { - // Given - val items = listOf( - buildTestItem(id = "1", quantity = 3.0, minStock = 5.0), // deficit 2 - buildTestItem(id = "2", quantity = 1.0, minStock = 10.0), // deficit 9 - buildTestItem(id = "3", quantity = 4.0, minStock = 7.0) // deficit 3 - ) - - // When - val result = useCase(items) - - // Then - assertEquals(3, result.size) - assertEquals("2", result[0].item.id) // deficit 9 - assertEquals("3", result[1].item.id) // deficit 3 - assertEquals("1", result[2].item.id) // deficit 2 - } - - @Test - fun test_invoke_withMixedItems_onlyReturnsBelowMinStock() { - // Given - val items = listOf( - buildTestItem(id = "1", quantity = 2.0, minStock = 5.0), // below - buildTestItem(id = "2", quantity = 10.0, minStock = 3.0), // above - buildTestItem(id = "3", quantity = 5.0, minStock = 5.0), // equal - buildTestItem(id = "4", quantity = 0.0, minStock = 1.0) // below - ) - - // When - val result = useCase(items) - - // Then - assertEquals(2, result.size) - assertTrue(result.all { it.item.id in listOf("1", "4") }) - } -} 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 0c9c849..00e6e66 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,10 +10,9 @@ internal fun buildTestItem( quantity: Double = 1.0, unit: String = "Stk", unitPrice: Double = 0.0, - kcalPer100g: Int? = null, + kcalPerKg: Int? = null, expiryDate: LocalDate? = null, - locationId: Int = 1, - minStock: Double = 0.0 + locationId: Int = 1 ) = ItemEntity( id = id, name = name, @@ -21,10 +20,9 @@ internal fun buildTestItem( quantity = quantity, unit = unit, unitPrice = unitPrice, - kcalPer100g = kcalPer100g, + kcalPerKg = kcalPerKg, expiryDate = expiryDate, locationId = locationId, - minStock = minStock, notes = "", lastUpdated = 0L ) 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 94d207f..c172f47 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,8 +293,8 @@ class CategoryListViewModelTest { fakeItemRepository.addItem( ItemEntity( id = "i1", name = "Reis", categoryId = 1, quantity = 1.0, - unit = "kg", unitPrice = 0.0, kcalPer100g = null, expiryDate = null, - locationId = 1, minStock = 0.0, notes = "", lastUpdated = 0L + unit = "kg", unitPrice = 0.0, kcalPerKg = null, expiryDate = null, + locationId = 1, notes = "", lastUpdated = 0L ) ) val category = CategoryEntity(id = 1, name = "Lebensmittel") @@ -318,8 +318,8 @@ class CategoryListViewModelTest { fakeItemRepository.addItem( ItemEntity( id = "i1", name = "Reis", categoryId = 1, quantity = 1.0, - unit = "kg", unitPrice = 0.0, kcalPer100g = null, expiryDate = null, - locationId = 1, minStock = 0.0, notes = "", lastUpdated = 0L + unit = "kg", unitPrice = 0.0, kcalPerKg = null, expiryDate = null, + locationId = 1, notes = "", lastUpdated = 0L ) ) val category = CategoryEntity(id = 1, name = "Lebensmittel") @@ -345,8 +345,8 @@ class CategoryListViewModelTest { fakeItemRepository.addItem( ItemEntity( id = "i1", name = "Reis", categoryId = 1, quantity = 1.0, - unit = "kg", unitPrice = 0.0, kcalPer100g = null, expiryDate = null, - locationId = 1, minStock = 0.0, notes = "", lastUpdated = 0L + unit = "kg", unitPrice = 0.0, kcalPerKg = null, expiryDate = null, + locationId = 1, notes = "", lastUpdated = 0L ) ) viewModel.showDeleteDialog(CategoryEntity(id = 1, name = "Lebensmittel")) 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 f483b2e..1c897dc 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 @@ -14,7 +14,6 @@ import de.krisenvorrat.app.domain.usecase.CalculateCategorySummaryUseCase import de.krisenvorrat.app.domain.usecase.CalculateSupplyRangeUseCase import de.krisenvorrat.app.domain.usecase.CalculateTotalValueUseCase import de.krisenvorrat.app.domain.usecase.GetExpiryWarningsUseCase -import de.krisenvorrat.app.domain.usecase.GetMinStockWarningsUseCase import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -61,8 +60,7 @@ class DashboardViewModelTest { calculateCategorySummary = CalculateCategorySummaryUseCase(), calculateTotalValue = CalculateTotalValueUseCase(), calculateSupplyRange = CalculateSupplyRangeUseCase(), - getExpiryWarnings = GetExpiryWarningsUseCase(), - getMinStockWarnings = GetMinStockWarningsUseCase() + getExpiryWarnings = GetExpiryWarningsUseCase() ) @Test @@ -80,7 +78,6 @@ class DashboardViewModelTest { assertEquals(0.0, state.totalValue, 0.001) assertEquals(0.0, state.supplyRangeDays, 0.001) assertFalse(state.hasExpiryWarnings) - assertFalse(state.hasMinStockWarnings) assertEquals(0, state.totalItemCount) } @@ -151,7 +148,7 @@ class DashboardViewModelTest { ) fakeItemRepository.emit( listOf( - buildTestItem(id = "a", quantity = 1000.0, unit = "g", kcalPer100g = 200) + buildTestItem(id = "a", quantity = 1000.0, unit = "g", kcalPerKg = 2000) ) ) viewModel = createViewModel() @@ -174,7 +171,7 @@ class DashboardViewModelTest { allZeroAgeGroups.toJson() ) fakeItemRepository.emit( - listOf(buildTestItem(id = "a", quantity = 1000.0, unit = "g", kcalPer100g = 400)) + listOf(buildTestItem(id = "a", quantity = 1000.0, unit = "g", kcalPerKg = 4000)) ) viewModel = createViewModel() @@ -203,39 +200,6 @@ class DashboardViewModelTest { assertEquals(1, state.expiryWarnings.size) } - @Test - fun test_init_withMinStockViolation_minStockWarningsArePresent() = runTest(testDispatcher) { - // Given – item with quantity 1 but minStock 5 - fakeItemRepository.emit( - listOf(buildTestItem(id = "a", quantity = 1.0, minStock = 5.0)) - ) - viewModel = createViewModel() - - // When - advanceUntilIdle() - - // Then - val state = viewModel.uiState.value - assertTrue(state.hasMinStockWarnings) - assertEquals(1, state.minStockWarnings.size) - assertEquals(4.0, state.minStockWarnings.first().deficit, 0.001) - } - - @Test - fun test_init_withSufficientStock_noMinStockWarnings() = runTest(testDispatcher) { - // Given – item with quantity above minStock - fakeItemRepository.emit( - listOf(buildTestItem(id = "a", quantity = 10.0, minStock = 5.0)) - ) - viewModel = createViewModel() - - // When - advanceUntilIdle() - - // Then - assertFalse(viewModel.uiState.value.hasMinStockWarnings) - } - @Test fun test_init_withFarExpiryDate_noExpiryWarnings() = runTest(testDispatcher) { // Given – item expiring in 2 years (beyond WARNING_MONTHS=12) @@ -282,10 +246,9 @@ private fun buildTestItem( quantity: Double = 1.0, unit: String = "Stk", unitPrice: Double = 0.0, - kcalPer100g: Int? = null, + kcalPerKg: Int? = null, expiryDate: LocalDate? = null, - locationId: Int = 1, - minStock: Double = 0.0 + locationId: Int = 1 ) = ItemEntity( id = id, name = name, @@ -293,10 +256,9 @@ private fun buildTestItem( quantity = quantity, unit = unit, unitPrice = unitPrice, - kcalPer100g = kcalPer100g, + kcalPerKg = kcalPerKg, expiryDate = expiryDate, locationId = locationId, - minStock = minStock, notes = "", lastUpdated = 0L ) 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 2971cd8..a793901 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 @@ -149,8 +149,8 @@ class ItemFormViewModelTest { fakeItemRepository.addItem( ItemEntity( id = "prev-1", name = "Alt", categoryId = 1, quantity = 1.0, - unit = "kg", unitPrice = 0.0, kcalPer100g = null, expiryDate = null, - locationId = 2, minStock = 0.0, notes = "", lastUpdated = 1000L + unit = "kg", unitPrice = 0.0, kcalPerKg = null, expiryDate = null, + locationId = 2, notes = "", lastUpdated = 1000L ) ) fakeLocationRepository.emit( @@ -174,8 +174,8 @@ class ItemFormViewModelTest { fakeItemRepository.addItem( ItemEntity( id = "prev-1", name = "Alt", categoryId = 1, quantity = 1.0, - unit = "kg", unitPrice = 0.0, kcalPer100g = null, expiryDate = null, - locationId = 99, minStock = 0.0, notes = "", lastUpdated = 1000L + unit = "kg", unitPrice = 0.0, kcalPerKg = null, expiryDate = null, + locationId = 99, notes = "", lastUpdated = 1000L ) ) fakeLocationRepository.emit( @@ -205,10 +205,9 @@ class ItemFormViewModelTest { quantity = 5.0, unit = "Stk", unitPrice = 2.5, - kcalPer100g = 120, + kcalPerKg = 120, expiryDate = LocalDate.of(2026, 12, 31), locationId = 2, - minStock = 2.0, notes = "Bohnen", lastUpdated = 1000L ) @@ -227,10 +226,9 @@ class ItemFormViewModelTest { assertEquals("5", state.quantity) assertEquals("Stk", state.unit) assertEquals("2.5", state.unitPrice) - assertEquals("120", state.kcalPer100g) + assertEquals("120", state.kcalPerKg) assertEquals(LocalDate.of(2026, 12, 31), state.expiryDate) assertEquals(2, state.locationId) - assertEquals("2", state.minStock) assertEquals("Bohnen", state.notes) } @@ -463,10 +461,9 @@ class ItemFormViewModelTest { quantity = 2.0, unit = "Stk", unitPrice = 0.0, - kcalPer100g = null, + kcalPerKg = null, expiryDate = null, locationId = 1, - minStock = 0.0, notes = "", lastUpdated = 0L ) 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 555ae14..6ce6ae2 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 @@ -230,10 +230,9 @@ private fun buildItemEntity( quantity = 2.0, unit = "Stk", unitPrice = 1.5, - kcalPer100g = null, + kcalPerKg = null, expiryDate = null, locationId = locationId, - minStock = 1.0, notes = "", lastUpdated = 0L ) 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 8d99745..af036ab 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,8 +292,8 @@ class LocationListViewModelTest { fakeItemRepository.addItem( ItemEntity( id = "i1", name = "Reis", categoryId = 1, quantity = 1.0, - unit = "kg", unitPrice = 0.0, kcalPer100g = null, expiryDate = null, - locationId = 1, minStock = 0.0, notes = "", lastUpdated = 0L + unit = "kg", unitPrice = 0.0, kcalPerKg = null, expiryDate = null, + locationId = 1, notes = "", lastUpdated = 0L ) ) val location = LocationEntity(id = 1, name = "Keller") @@ -317,8 +317,8 @@ class LocationListViewModelTest { fakeItemRepository.addItem( ItemEntity( id = "i1", name = "Reis", categoryId = 1, quantity = 1.0, - unit = "kg", unitPrice = 0.0, kcalPer100g = null, expiryDate = null, - locationId = 1, minStock = 0.0, notes = "", lastUpdated = 0L + unit = "kg", unitPrice = 0.0, kcalPerKg = null, expiryDate = null, + locationId = 1, notes = "", lastUpdated = 0L ) ) val location = LocationEntity(id = 1, name = "Keller") @@ -344,8 +344,8 @@ class LocationListViewModelTest { fakeItemRepository.addItem( ItemEntity( id = "i1", name = "Wasser", categoryId = 1, quantity = 1.0, - unit = "Flasche", unitPrice = 0.0, kcalPer100g = null, expiryDate = null, - locationId = 1, minStock = 0.0, notes = "", lastUpdated = 0L + unit = "Flasche", unitPrice = 0.0, kcalPerKg = null, expiryDate = null, + locationId = 1, notes = "", lastUpdated = 0L ) ) viewModel.showDeleteDialog(LocationEntity(id = 1, name = "Keller")) 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 11567bf..f037741 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 @@ -3,7 +3,6 @@ package de.krisenvorrat.app.ui.warnings import de.krisenvorrat.app.data.db.entity.ItemEntity import de.krisenvorrat.app.domain.repository.ItemRepository import de.krisenvorrat.app.domain.usecase.GetExpiryWarningsUseCase -import de.krisenvorrat.app.domain.usecase.GetMinStockWarningsUseCase import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -41,8 +40,7 @@ class WarningsViewModelTest { private fun createViewModel() = WarningsViewModel( itemRepository = fakeItemRepository, - getExpiryWarnings = GetExpiryWarningsUseCase(), - getMinStockWarnings = GetMinStockWarningsUseCase() + getExpiryWarnings = GetExpiryWarningsUseCase() ) @Test @@ -57,7 +55,6 @@ class WarningsViewModelTest { val state = viewModel.uiState.value assertFalse(state.isLoading) assertFalse(state.hasExpiryWarnings) - assertFalse(state.hasMinStockWarnings) assertFalse(state.hasWarnings) assertEquals(0, state.totalWarningCount) } @@ -82,48 +79,6 @@ class WarningsViewModelTest { assertEquals(1, state.totalWarningCount) } - @Test - fun test_init_withMinStockViolation_minStockWarningsArePresent() = runTest(testDispatcher) { - // Given – item with quantity 1 but minStock 5 - fakeItemRepository.emit( - listOf(buildTestItem(id = "a", quantity = 1.0, minStock = 5.0)) - ) - viewModel = createViewModel() - - // When - advanceUntilIdle() - - // Then - val state = viewModel.uiState.value - assertTrue(state.hasMinStockWarnings) - assertEquals(1, state.minStockWarnings.size) - assertEquals(4.0, state.minStockWarnings.first().deficit, 0.001) - assertTrue(state.hasWarnings) - assertEquals(1, state.totalWarningCount) - } - - @Test - fun test_init_withBothWarnings_totalCountIsCombined() = runTest(testDispatcher) { - // Given – one expiring item and one below min stock - val expiryDate = LocalDate.now().plusMonths(3) - fakeItemRepository.emit( - listOf( - buildTestItem(id = "a", expiryDate = expiryDate), - buildTestItem(id = "b", quantity = 1.0, minStock = 5.0) - ) - ) - viewModel = createViewModel() - - // When - advanceUntilIdle() - - // Then - val state = viewModel.uiState.value - assertTrue(state.hasExpiryWarnings) - assertTrue(state.hasMinStockWarnings) - assertEquals(2, state.totalWarningCount) - } - @Test fun test_init_withFarExpiryDate_noExpiryWarnings() = runTest(testDispatcher) { // Given – item expiring in 2 years (beyond WARNING_MONTHS=12) @@ -140,21 +95,6 @@ class WarningsViewModelTest { assertFalse(viewModel.uiState.value.hasExpiryWarnings) } - @Test - fun test_init_withSufficientStock_noMinStockWarnings() = runTest(testDispatcher) { - // Given – item with quantity above minStock - fakeItemRepository.emit( - listOf(buildTestItem(id = "a", quantity = 10.0, minStock = 5.0)) - ) - viewModel = createViewModel() - - // When - advanceUntilIdle() - - // Then - assertFalse(viewModel.uiState.value.hasMinStockWarnings) - } - @Test fun test_reactiveUpdate_whenItemsChange_stateUpdates() = runTest(testDispatcher) { // Given – start with no warnings @@ -162,14 +102,15 @@ class WarningsViewModelTest { advanceUntilIdle() assertFalse(viewModel.uiState.value.hasWarnings) - // When – an item below min stock appears + // When – an expiring item appears + val expiryDate = LocalDate.now().plusMonths(3) fakeItemRepository.emit( - listOf(buildTestItem(id = "a", quantity = 1.0, minStock = 5.0)) + listOf(buildTestItem(id = "a", expiryDate = expiryDate)) ) advanceUntilIdle() // Then - assertTrue(viewModel.uiState.value.hasMinStockWarnings) + assertTrue(viewModel.uiState.value.hasExpiryWarnings) assertEquals(1, viewModel.uiState.value.totalWarningCount) } } @@ -183,10 +124,9 @@ private fun buildTestItem( quantity: Double = 1.0, unit: String = "Stk", unitPrice: Double = 0.0, - kcalPer100g: Int? = null, + kcalPerKg: Int? = null, expiryDate: LocalDate? = null, - locationId: Int = 1, - minStock: Double = 0.0 + locationId: Int = 1 ) = ItemEntity( id = id, name = name, @@ -194,10 +134,9 @@ private fun buildTestItem( quantity = quantity, unit = unit, unitPrice = unitPrice, - kcalPer100g = kcalPer100g, + kcalPerKg = kcalPerKg, expiryDate = expiryDate, locationId = locationId, - minStock = minStock, notes = "", lastUpdated = 0L ) 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 6600d6f..8a71de0 100644 --- a/server/src/main/kotlin/de/krisenvorrat/server/db/Tables.kt +++ b/server/src/main/kotlin/de/krisenvorrat/server/db/Tables.kt @@ -23,10 +23,9 @@ internal object Items : Table("items") { val quantity = double("quantity") val unit = varchar("unit", 50) val unitPrice = double("unit_price") - val kcalPer100g = integer("kcal_per_100g").nullable() + val kcalPerKg = integer("kcal_per_kg").nullable() val expiryDate = varchar("expiry_date", 10).nullable() val locationId = integer("location_id").references(Locations.id) - val minStock = double("min_stock") val notes = text("notes") val lastUpdated = long("last_updated") 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 71d36ad..4b8b662 100644 --- a/server/src/main/kotlin/de/krisenvorrat/server/repository/InventoryRepository.kt +++ b/server/src/main/kotlin/de/krisenvorrat/server/repository/InventoryRepository.kt @@ -45,10 +45,9 @@ internal class InventoryRepository { it[quantity] = item.quantity it[unit] = item.unit it[unitPrice] = item.unitPrice - it[kcalPer100g] = item.kcalPer100g + it[kcalPerKg] = item.kcalPerKg it[expiryDate] = item.expiryDate it[locationId] = item.locationId - it[minStock] = item.minStock it[notes] = item.notes it[lastUpdated] = item.lastUpdated } @@ -87,10 +86,9 @@ internal class InventoryRepository { quantity = it[Items.quantity], unit = it[Items.unit], unitPrice = it[Items.unitPrice], - kcalPer100g = it[Items.kcalPer100g], + kcalPerKg = it[Items.kcalPerKg], expiryDate = it[Items.expiryDate], locationId = it[Items.locationId], - minStock = it[Items.minStock], notes = it[Items.notes], lastUpdated = it[Items.lastUpdated] ) diff --git a/server/src/test/kotlin/de/krisenvorrat/server/ApplicationTest.kt b/server/src/test/kotlin/de/krisenvorrat/server/ApplicationTest.kt index 076658e..91440fa 100644 --- a/server/src/test/kotlin/de/krisenvorrat/server/ApplicationTest.kt +++ b/server/src/test/kotlin/de/krisenvorrat/server/ApplicationTest.kt @@ -197,10 +197,9 @@ class ApplicationTest { quantity = 5.0, unit = "Stück", unitPrice = 3.99, - kcalPer100g = 250, + kcalPerKg = 250, expiryDate = "2027-06-15", locationId = 1, - minStock = 2.0, notes = "Vollkornbrot in der Dose", lastUpdated = 1715000000L ) diff --git a/server/src/test/kotlin/de/krisenvorrat/server/EndToEndSyncTest.kt b/server/src/test/kotlin/de/krisenvorrat/server/EndToEndSyncTest.kt index 1f81ee3..b161ecc 100644 --- a/server/src/test/kotlin/de/krisenvorrat/server/EndToEndSyncTest.kt +++ b/server/src/test/kotlin/de/krisenvorrat/server/EndToEndSyncTest.kt @@ -102,10 +102,9 @@ class EndToEndSyncTest { quantity = 3.0, unit = "Stück", unitPrice = 1.50, - kcalPer100g = null, + kcalPerKg = null, expiryDate = null, locationId = 10, - minStock = 1.0, notes = "", lastUpdated = 1715100000L ) @@ -174,10 +173,9 @@ class EndToEndSyncTest { quantity = 10.0, unit = "Stück", unitPrice = 3.99, - kcalPer100g = 250, + kcalPerKg = 250, expiryDate = "2027-06-15", locationId = 1, - minStock = 2.0, notes = "Menge erhöht", lastUpdated = 1715200000L ) @@ -245,10 +243,9 @@ class EndToEndSyncTest { quantity = 5.0, unit = "Stück", unitPrice = 3.99, - kcalPer100g = 250, + kcalPerKg = 250, expiryDate = "2027-06-15", locationId = 1, - minStock = 2.0, notes = "Vollkornbrot in der Dose", lastUpdated = 1715000000L ), @@ -259,10 +256,9 @@ class EndToEndSyncTest { quantity = 24.0, unit = "Liter", unitPrice = 0.49, - kcalPer100g = 0, + kcalPerKg = 0, expiryDate = "2028-01-01", locationId = 2, - minStock = 12.0, notes = "Stilles Wasser", lastUpdated = 1715000000L ) 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 73f5652..544f16b 100644 --- a/server/src/test/kotlin/de/krisenvorrat/server/repository/InventoryRepositoryTest.kt +++ b/server/src/test/kotlin/de/krisenvorrat/server/repository/InventoryRepositoryTest.kt @@ -66,10 +66,9 @@ class InventoryRepositoryTest { assertEquals(5.0, item.quantity, 0.001) assertEquals("Stück", item.unit) assertEquals(3.99, item.unitPrice, 0.001) - assertEquals(250, item.kcalPer100g) + assertEquals(250, item.kcalPerKg) assertEquals("2027-06-15", item.expiryDate) assertEquals(1, item.locationId) - assertEquals(2.0, item.minStock, 0.001) assertEquals("Vollkornbrot in der Dose", item.notes) assertEquals(1715000000L, item.lastUpdated) @@ -118,10 +117,9 @@ class InventoryRepositoryTest { quantity = 10.0, unit = "Stück", unitPrice = 1.50, - kcalPer100g = null, + kcalPerKg = null, expiryDate = null, locationId = 1, - minStock = 5.0, notes = "", lastUpdated = 1715000000L ) @@ -135,7 +133,7 @@ class InventoryRepositoryTest { // Then val item = result.items[0] - assertNull(item.kcalPer100g) + assertNull(item.kcalPerKg) assertNull(item.expiryDate) } @@ -156,10 +154,9 @@ class InventoryRepositoryTest { quantity = 5.0, unit = "Stück", unitPrice = 3.99, - kcalPer100g = 250, + kcalPerKg = 250, expiryDate = "2027-06-15", locationId = 1, - minStock = 2.0, notes = "Vollkornbrot in der Dose", 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 2833533..da45ff1 100644 --- a/shared/src/main/kotlin/de/krisenvorrat/shared/model/ItemDto.kt +++ b/shared/src/main/kotlin/de/krisenvorrat/shared/model/ItemDto.kt @@ -10,10 +10,9 @@ data class ItemDto( val quantity: Double, val unit: String, val unitPrice: Double, - val kcalPer100g: Int?, + val kcalPerKg: Int?, val expiryDate: String?, val locationId: Int, - val minStock: Double, val notes: String, val lastUpdated: Long )