# 01 — Data Model Models are scoped to their feature under `dev.achmad.ledgerr.domain..model`. No shared/common package — features import from each other's model package where needed. --- ## Feature: expense ```kotlin // DateRange.kt data class DateRange( val start: LocalDate, val end: LocalDate ) { companion object { fun thisMonth(): DateRange { val now = LocalDate.now() return DateRange(now.withDayOfMonth(1), now.withDayOfMonth(now.lengthOfMonth())) } fun thisWeek(): DateRange { val now = LocalDate.now() return DateRange(now.with(DayOfWeek.MONDAY), now.with(DayOfWeek.SUNDAY)) } } } // Expense.kt data class Expense( val id: Long = 0, val amount: Double, // always positive; represents outflow val categoryId: Long, val date: LocalDate, val note: String? = null, val recurringExpenseId: Long? = null, // set if auto-generated from a RecurringExpense val createdAt: Long = System.currentTimeMillis() ) // ExpenseWithCategory.kt data class ExpenseWithCategory( val expense: Expense, val category: Category // imported from domain.category.model ) // ExpenseSummary.kt data class ExpenseSummary( val totalAmount: Double, val byCategory: List>, // sorted by amount DESC val period: DateRange ) ``` --- ## Feature: category ```kotlin // Category.kt data class Category( val id: Long = 0, val name: String, val color: Int, // ARGB val iconName: String? = null, // Material icon name val isDefault: Boolean = false // non-deletable; "Other" is always true ) ``` Default categories seeded on first install: | Name | color (ARGB hex) | isDefault | |------|------------------|-----------| | Food & Drink | `0xFFFF9800` (orange) | false | | Transport | `0xFF2196F3` (blue) | false | | Housing | `0xFF795548` (brown) | false | | Health | `0xFFF44336` (red) | false | | Entertainment | `0xFF9C27B0` (purple) | false | | Shopping | `0xFFE91E63` (pink) | false | | Education | `0xFF4CAF50` (green) | false | | Other | `0xFF9E9E9E` (grey) | **true** | `Other` is the permanent fallback when a user-defined category is deleted. --- ## Feature: recurring ```kotlin // RecurringInterval.kt enum class RecurringInterval { DAILY, WEEKLY, MONTHLY, YEARLY; fun advance(from: LocalDate): LocalDate = when (this) { DAILY -> from.plusDays(1) WEEKLY -> from.plusWeeks(1) MONTHLY -> from.plusMonths(1) YEARLY -> from.plusYears(1) } } // RecurringExpense.kt data class RecurringExpense( val id: Long = 0, val amount: Double, val categoryId: Long, val note: String? = null, val interval: RecurringInterval, val startDate: LocalDate, val nextDueDate: LocalDate, // initialized = startDate; advances after each processing val isActive: Boolean = true ) // RecurringExpenseWithCategory.kt data class RecurringExpenseWithCategory( val recurring: RecurringExpense, val category: Category ) ``` --- ## Feature: bankstatement Each bank has its own raw model representing a single parsed row from the bank's PDF format. These exist so bank-specific parsing logic produces typed data before converting to the common `PendingImportExpense`. ```kotlin // PendingImportExpense.kt — common output for the review screen data class PendingImportExpense( val amount: Double, val date: LocalDate, val description: String, // raw text from PDF val suggestedCategoryId: Long? = null, val isSelected: Boolean = true // user can deselect before committing ) // BRIStatementEntry.kt data class BRIStatementEntry( val date: String, // raw string as it appears in PDF val description: String, val debit: String?, // raw amount string, null if not a debit val credit: String?, val balance: String? ) // JagoStatementEntry.kt data class JagoStatementEntry( val date: String, val description: String, val amount: String, val type: String // e.g. "DEBIT" / "KREDIT" ) // BNIStatementEntry.kt data class BNIStatementEntry( val date: String, val description: String, val debit: String?, val credit: String?, val balance: String? ) ``` --- ## Feature: export No domain models — uses `Expense`, `ExpenseWithCategory`, and `DateRange` from the expense feature. --- ## Room Entities (data layer only) Live in `data/local/entity/`. Mirror domain models with Room annotations; dates stored as `Long` (epoch day via `LocalDateConverter`). - `CategoryEntity` - `ExpenseEntity` - `RecurringExpenseEntity`