feat(#17): Room-Entities & LocalDateConverter

- Add CategoryEntity, LocationEntity, SettingsEntity, ItemEntity
- ItemEntity: FK to Category+Location with CASCADE, indices on FK columns
- LocalDateConverter: LocalDate? <-> String? (ISO-8601) via @TypeConverter
- Add LocalDateConverterTest: 4 unit tests (null/non-null round-trip)
This commit is contained in:
Jens Reinemann 2026-05-13 22:57:43 +02:00
parent 74bf5ef060
commit 645a110297
6 changed files with 146 additions and 0 deletions

View file

@ -0,0 +1,13 @@
package de.krisenvorrat.app.data.db
import androidx.room.TypeConverter
import java.time.LocalDate
internal class LocalDateConverter {
@TypeConverter
fun fromLocalDate(date: LocalDate?): String? = date?.toString()
@TypeConverter
fun toLocalDate(value: String?): LocalDate? = value?.let { LocalDate.parse(it) }
}

View file

@ -0,0 +1,11 @@
package de.krisenvorrat.app.data.db.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "categories")
internal data class CategoryEntity(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
@ColumnInfo(name = "name") val name: String
)

View file

@ -0,0 +1,41 @@
package de.krisenvorrat.app.data.db.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.PrimaryKey
import java.time.LocalDate
@Entity(
tableName = "items",
foreignKeys = [
ForeignKey(
entity = CategoryEntity::class,
parentColumns = ["id"],
childColumns = ["category_id"],
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = LocationEntity::class,
parentColumns = ["id"],
childColumns = ["location_id"],
onDelete = ForeignKey.CASCADE
)
],
indices = [Index("category_id"), Index("location_id")]
)
internal data class ItemEntity(
@PrimaryKey val id: String,
@ColumnInfo(name = "name") val name: String,
@ColumnInfo(name = "category_id") val categoryId: Int,
@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 = "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
)

View file

@ -0,0 +1,11 @@
package de.krisenvorrat.app.data.db.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "locations")
internal data class LocationEntity(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
@ColumnInfo(name = "name") val name: String
)

View file

@ -0,0 +1,11 @@
package de.krisenvorrat.app.data.db.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "settings")
internal data class SettingsEntity(
@PrimaryKey val key: String,
@ColumnInfo(name = "value") val value: String
)

View file

@ -0,0 +1,59 @@
package de.krisenvorrat.app.data.db
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
import java.time.LocalDate
class LocalDateConverterTest {
private val converter = LocalDateConverter()
@Test
fun test_fromLocalDate_withDate_returnsIsoString() {
// Given
val date = LocalDate.of(2025, 12, 31)
// When
val result = converter.fromLocalDate(date)
// Then
assertEquals("2025-12-31", result)
}
@Test
fun test_fromLocalDate_withNull_returnsNull() {
// Given
val date: LocalDate? = null
// When
val result = converter.fromLocalDate(date)
// Then
assertNull(result)
}
@Test
fun test_toLocalDate_withIsoString_returnsDate() {
// Given
val isoString = "2025-12-31"
// When
val result = converter.toLocalDate(isoString)
// Then
assertEquals(LocalDate.of(2025, 12, 31), result)
}
@Test
fun test_toLocalDate_withNull_returnsNull() {
// Given
val isoString: String? = null
// When
val result = converter.toLocalDate(isoString)
// Then
assertNull(result)
}
}