// Package model holds the core domain types shared by every other package. package model import ( "fmt" "strings" ) // Account is one folder in the data root. type Account struct { ID int64 Slug string // folder name, unique Name string // human readable, from account.toml Currency string MinorDigits int } // Transaction is a single line extracted from a statement, plus whatever the // rules engine and the user have said about it. // // Amount is stored in minor units (cents) and signed: negative is an outflow. type Transaction struct { ID int64 AccountID int64 AccountSlug string Currency string MinorDigits int Fingerprint string Date string // YYYY-MM-DD Description string AmountMinor int64 SourceFileID int64 SourcePath string // Type is the bank's own classification, e.g. Revolut's CARD_PAYMENT. Type string // BalanceMinor is the running balance the statement reported after this // transaction, when it reports one. Used to verify the balance chain. BalanceMinor *int64 RuleTag string ManualTag string } // Tag is the effective category: a manual tag always beats a rule tag. func (t Transaction) Tag() string { if t.ManualTag != "" { return t.ManualTag } return t.RuleTag } // FormatAmount renders the amount using the account's minor-unit scale. func (t Transaction) FormatAmount() string { return FormatMinor(t.AmountMinor, t.MinorDigits) } // FormatMinor renders minor units as a decimal string, e.g. -4520/2 -> "-45.20". func FormatMinor(minor int64, digits int) string { if digits <= 0 { return fmt.Sprintf("%d", minor) } neg := minor < 0 if neg { minor = -minor } scale := int64(1) for i := 0; i < digits; i++ { scale *= 10 } whole, frac := minor/scale, minor%scale s := fmt.Sprintf("%d.%0*d", whole, digits, frac) if neg { s = "-" + s } return s } // NormalizeDescription collapses whitespace and upper-cases, so that // fingerprints and glob matching are insensitive to statement formatting noise. func NormalizeDescription(s string) string { return strings.ToUpper(strings.Join(strings.Fields(s), " ")) }