class_name LedgerEntry extends RefCounted ## One immutable-in-practice double-entry ledger line. ## ## A debit increases the signed balance of debit_account and a credit decreases ## the signed balance of credit_account. EconomyService is the only component ## which may append entries or update its derived balance cache. var transaction_id: StringName = &"" var absolute_minute := 0 var debit_account: StringName = &"" var credit_account: StringName = &"" var amount := 0 var category: StringName = &"" var source_entity_id: StringName = &"" var related_transaction_id: StringName = &"" var memo_key: StringName = &"" func configure( p_transaction_id: StringName, p_absolute_minute: int, p_debit_account: StringName, p_credit_account: StringName, p_amount: int, p_category: StringName, p_source_entity_id: StringName = &"", p_related_transaction_id: StringName = &"", p_memo_key: StringName = &"" ) -> LedgerEntry: transaction_id = p_transaction_id absolute_minute = p_absolute_minute debit_account = p_debit_account credit_account = p_credit_account amount = p_amount category = p_category source_entity_id = p_source_entity_id related_transaction_id = p_related_transaction_id memo_key = p_memo_key return self func validation_errors() -> Array[StringName]: var errors: Array[StringName] = [] if transaction_id.is_empty(): errors.append(&"EMPTY_TRANSACTION_ID") if absolute_minute < 0: errors.append(&"INVALID_ABSOLUTE_MINUTE") if debit_account.is_empty(): errors.append(&"EMPTY_DEBIT_ACCOUNT") if credit_account.is_empty(): errors.append(&"EMPTY_CREDIT_ACCOUNT") if not debit_account.is_empty() and debit_account == credit_account: errors.append(&"IDENTICAL_ACCOUNTS") if amount <= 0: errors.append(&"INVALID_AMOUNT") if category.is_empty(): errors.append(&"EMPTY_CATEGORY") if not related_transaction_id.is_empty() and related_transaction_id == transaction_id: errors.append(&"SELF_RELATED_TRANSACTION") return errors func is_valid() -> bool: return validation_errors().is_empty() func reversed( p_transaction_id: StringName, p_absolute_minute: int, p_category: StringName = &"REVERSAL", p_source_entity_id: StringName = &"", p_memo_key: StringName = &"" ) -> LedgerEntry: return LedgerEntry.new().configure( p_transaction_id, p_absolute_minute, credit_account, debit_account, amount, p_category, p_source_entity_id if not p_source_entity_id.is_empty() else source_entity_id, transaction_id, p_memo_key ) func copy() -> LedgerEntry: return LedgerEntry.new().configure( transaction_id, absolute_minute, debit_account, credit_account, amount, category, source_entity_id, related_transaction_id, memo_key ) func to_dictionary() -> Dictionary: return { &"transaction_id": String(transaction_id), &"absolute_minute": absolute_minute, &"debit_account": String(debit_account), &"credit_account": String(credit_account), &"amount": amount, &"category": String(category), &"source_entity_id": String(source_entity_id), &"related_transaction_id": String(related_transaction_id), &"memo_key": String(memo_key), } static func from_dictionary(data: Dictionary) -> LedgerEntry: return LedgerEntry.new().configure( StringName(data.get(&"transaction_id", data.get("transaction_id", ""))), int(data.get(&"absolute_minute", data.get("absolute_minute", -1))), StringName(data.get(&"debit_account", data.get("debit_account", ""))), StringName(data.get(&"credit_account", data.get("credit_account", ""))), int(data.get(&"amount", data.get("amount", 0))), StringName(data.get(&"category", data.get("category", ""))), StringName(data.get(&"source_entity_id", data.get("source_entity_id", ""))), StringName(data.get( &"related_transaction_id", data.get("related_transaction_id", "") )), StringName(data.get(&"memo_key", data.get("memo_key", ""))) )