"""
Module containing classes that read, manipulate, and write transactions.
.. module:: transaction
:synopsis:
.. moduleauthor: Paul Bromwell Jr.
"""
import enum
import warnings
from collections.abc import Generator, Iterator
from datetime import datetime, timedelta
from decimal import Decimal
from typing import Any, Optional
from gnewcash.account import Account
from gnewcash.commodity import Commodity
from gnewcash.enums import AccountType
from gnewcash.guid_object import GuidObject
from gnewcash.search import Query
from gnewcash.slot import Slot, SlottableObject
class TransactionException(Exception):
"""Exception class used to handle transaction-related exceptions."""
[docs]
class Transaction(GuidObject, SlottableObject):
"""Represents a transaction in GnuCash."""
def __init__(
self,
guid: Optional[str] = None,
slots: Optional[list[Slot]] = None,
currency: Optional[Commodity] = None,
date_posted: Optional[datetime] = None,
date_entered: Optional[datetime] = None,
description: str = '',
splits: Optional[list['Split']] = None,
memo: Optional[str] = None,
) -> None:
GuidObject.__init__(self, guid)
SlottableObject.__init__(self, slots)
self.currency: Optional[Commodity] = currency
self.date_posted: Optional[datetime] = date_posted
self.date_entered: Optional[datetime] = date_entered
self.description: str = description
self.splits: list[Split] = splits or []
self.memo: Optional[str] = memo
def __str__(self) -> str:
if self.date_posted:
return f'{self.date_posted.strftime("%m/%d/%Y")} - {self.description}'
return self.description
def __repr__(self) -> str:
return str(self)
def __lt__(self, other: 'Transaction') -> bool:
if self.date_posted is not None and other.date_posted is not None:
return self.date_posted < other.date_posted
if self.date_posted is not None and other.date_posted is None:
return False
if self.date_posted is None and other.date_posted is not None:
return True
return False
def __eq__(self, other: object) -> bool:
if not isinstance(other, Transaction):
raise NotImplementedError
return self.date_posted == other.date_posted
@property
def cleared(self) -> bool:
"""
Checks if all splits in the transaction are cleared.
:return: Boolean indicating if all splits in the transaction are cleared.
:rtype: bool
"""
return sum(1 for split in self.splits if split.reconciled_state.lower() == 'c') > 0
[docs]
def mark_transaction_cleared(self) -> None:
"""Marks all splits in the transaction as cleared (reconciled_state = 'c')."""
for split in self.splits:
split.reconciled_state = 'c'
@property
def notes(self) -> str:
"""
Notes on the transaction.
:return: Notes tied to the transaction
:rtype: str
"""
return super().get_slot_value('notes')
@notes.setter
def notes(self, value: str) -> None:
super().set_slot_value('notes', value, 'string')
@property
def reversed_by(self) -> str:
"""
GUID of the transaction that reverses this transaction.
:return: Transaction GUID
:rtype: str
"""
return super().get_slot_value('reversed-by')
@reversed_by.setter
def reversed_by(self, value: str) -> None:
super().set_slot_value('reversed-by', value, 'guid')
@property
def voided(self) -> str:
"""
Void status.
:return: Void status
:rtype: str
"""
return super().get_slot_value('trans-read-only')
@voided.setter
def voided(self, value: str) -> None:
super().set_slot_value('trans-read-only', value, 'string')
@property
def void_time(self) -> str:
"""
Time that the transaction was voided.
:return: Time that the transaction was voided
:rtype: str
"""
return super().get_slot_value('void-time')
@void_time.setter
def void_time(self, value: str) -> None:
super().set_slot_value('void-time', value, 'string')
@property
def void_reason(self) -> str:
"""
Reason that the transaction was voided.
:return: Reason that the transaction was voided
:rtype: str
"""
return super().get_slot_value('void-reason')
@void_reason.setter
def void_reason(self, value: str) -> None:
super().set_slot_value('void-reason', value, 'string')
@property
def associated_uri(self) -> str:
"""
URI associated with the transaction.
:return: URI associated with the transaction
:rtype: str
"""
return super().get_slot_value('assoc_uri')
@associated_uri.setter
def associated_uri(self, value: str) -> None:
super().set_slot_value('assoc_uri', value, 'string')
@property
def from_splits(self) -> Generator['Split', None, None]:
"""
Retrieves the "from" splits in the transaction.
:return: Splits with a negative amount.
:rtype: collections.Iterable[Split]
"""
return (x for x in self.splits if x.amount is not None and x.amount < Decimal(0))
@property
def to_splits(self) -> Generator['Split', None, None]:
"""
Retrieves the "to" splits in the transaction.
:return: Splits with a positive amount.
:rtype: collections.Iterable[Split]
"""
return (x for x in self.splits if x.amount is not None and x.amount > Decimal(0))
@property
def split_accounts(self) -> Generator[Account, None, None]:
"""
Retrieves the accounts involved in the splits.
:return: Accounts involved in splits.
:rtype: collections.Iterable[Account]
"""
return (x.account for x in self.splits if x.account is not None)
@property
def split_account_names(self) -> Generator[str, None, None]:
"""
Retrieves the names of the accounts involved in the splits.
:return: Names of the accounts involved in the splits.
:rtype: collections.Iterable[str]
"""
return (x.account.name for x in self.splits if x.account is not None)
@property
def from_split_accounts(self) -> Generator[Account, None, None]:
"""
Retrieves the accounts that are associated with the "from" splits.
:return: Accounts associated with splits that have a negative amount.
:rtype: collections.Iterable[Account]
"""
return (x.account for x in self.from_splits if x.account is not None)
@property
def from_split_account_names(self) -> Generator[str, None, None]:
"""
Retrieves the names of accounts that are associated with the "from" splits.
:return: Names of accounts associated with splits that have a negative amount.
:rtype: collections.Iterable[Account]
"""
return (x.account.name for x in self.from_splits if x.account is not None)
@property
def to_split_accounts(self) -> Generator[Account, None, None]:
"""
Retrieves the accounts that are associated with the "to" splits.
:return: Accounts associated with splits that have a positive amount.
:rtype: collections.Iterable[Account]
"""
return (x.account for x in self.to_splits if x.account is not None)
@property
def to_split_account_names(self) -> Generator[str, None, None]:
"""
Retrieves the names of accounts that are associated with the "to" splits.
:return: Names of accounts associated with splits that have a positive amount.
:rtype: collections.Iterable[str]
"""
return (x.account.name for x in self.to_splits if x.account is not None)
@property
def splits_total(self) -> Decimal:
"""
Retrieves the sum of all positive split amounts.
:return: Sum of all positive split amounts.
:rtype: decimal.Decimal
"""
return sum((x.amount for x in self.to_splits if x.amount is not None), start=Decimal(0))
[docs]
class Split(GuidObject):
"""Represents a split in GnuCash."""
def __init__(
self,
account: Optional[Account],
amount: Optional[Decimal],
reconciled_state: str = 'n',
guid: Optional[str] = None,
action: Optional[str] = None,
memo: Optional[str] = None,
quantity_denominator: str = '100',
reconcile_date: Optional[datetime] = None,
quantity_num: Optional[int] = None,
lot_guid: Optional[str] = None,
value_num: Optional[int] = None,
value_denom: Optional[int] = None,
):
super().__init__(guid)
self.reconciled_state: str = reconciled_state
self.amount: Optional[Decimal] = amount
self.account: Optional[Account] = account
self.action: Optional[str] = action
self.memo: Optional[str] = memo
self.quantity_denominator: str = quantity_denominator
self.reconcile_date: Optional[datetime] = reconcile_date
self.quantity_num: Optional[int] = quantity_num
self.lot_guid: Optional[str] = lot_guid
self.value_num: Optional[int] = value_num
self.value_denom: Optional[int] = value_denom
def __str__(self) -> str:
return f'{self.account} - {self.amount}'
def __repr__(self) -> str:
return str(self)
[docs]
class TransactionManager:
"""Class used to add/remove transactions, maintaining a chronological order based on transaction posted date."""
def __init__(
self,
transactions: Optional[list[Transaction]] = None,
disable_sort: bool = False,
sort_method: Optional['SortingMethod'] = None,
) -> None:
self.transactions: list[Transaction] = transactions or []
self.disable_sort: bool = disable_sort
self.deleted_transaction_guids: list[str] = []
self.sort_method: SortingMethod = sort_method or StandardSort()
[docs]
def add(self, new_transaction: Transaction) -> None:
"""
Adds a transaction to the transaction manager.
:param new_transaction: Transaction to add
:type new_transaction: Transaction
"""
if self.disable_sort:
self.transactions.append(new_transaction)
else:
for index, transaction in enumerate(self.transactions):
compare_result = self.sort_method.compare(transaction, new_transaction)
if compare_result in (SortingResult.FIRST_GREATER, SortingResult.EQUAL):
self.transactions.insert(index, new_transaction)
break
else:
self.transactions.append(new_transaction)
[docs]
def delete(self, transaction: Transaction) -> None:
"""
Removes a transaction from the transaction manager.
:param transaction: Transaction to remove
:type transaction: Transaction
"""
# We're looking up by GUID here because a simple list remove doesn't work
for index, iter_transaction in enumerate(self.transactions):
if iter_transaction.guid == transaction.guid:
self.deleted_transaction_guids.append(transaction.guid)
del self.transactions[index]
break
[docs]
def get_transactions(self, account: Optional[Account] = None) -> Iterator[Transaction]:
"""
Generator function that gets transactions based on a from account and/or to account for the transaction.
:param account: Account to retrieve transactions for (default None, all transactions)
:type account: Account
:return: Generator that produces transactions based on the given from account and/or to account
:rtype: Iterator[Transaction]
"""
for transaction in self.transactions:
if account is None or account in list(map(lambda x: x.account, transaction.splits)):
yield transaction
[docs]
def get_account_starting_balance(self, account: Account) -> Decimal:
"""
Retrieves the starting balance for the current account, given the list of transactions.
:param account: Account to get starting balance of.
:type account: Account
:return: First transaction amount if the account has transactions, otherwise 0.
:rtype: decimal.Decimal
"""
return (self.query().select_many(lambda t, i: t.splits)
.where(lambda s: s.account == account and s.amount >= 0)
.select(lambda s, i: s.amount)
.first(default=Decimal(0)))
[docs]
def get_account_ending_balance(self, account: Account) -> Decimal:
"""
Retrieves the ending balance for the provided account given the list of transactions in the manager.
:param account: Account to get the ending balance for
:type account: Account
:return: Account starting balance
:rtype: decimal.Decimal
"""
return self.get_balance_at_date(account)
[docs]
def minimum_balance_past_date(self, account: Account, date: datetime) \
-> tuple[Optional[Decimal], Optional[datetime]]:
"""
Gets the minimum balance for the account after a certain date, given the list of transactions.
:param account: Account to get the minimum balance information of
:type account: Account
:param date: datetime object representing the date you want to find the minimum balance for.
:type date: datetime.datetime
:return: Tuple containing the minimum balance (element 0) and the date it's at that balance (element 1)
:rtype: tuple
"""
minimum_balance: Optional[Decimal] = None
minimum_balance_date: Optional[datetime] = None
iterator_date: datetime = date
end_date: Optional[datetime] = max(x.date_posted for x in self.transactions if x.date_posted is not None)
if end_date is None:
return None, None
while iterator_date < end_date:
iterator_date += timedelta(days=1)
current_balance: Decimal = self.get_balance_at_date(account, iterator_date)
if minimum_balance is None or current_balance < minimum_balance:
minimum_balance, minimum_balance_date = current_balance, iterator_date
if minimum_balance_date and minimum_balance_date > end_date:
minimum_balance_date = end_date
return minimum_balance, minimum_balance_date
[docs]
def get_balance_at_date(self, account: Account, date: Optional[datetime] = None) -> Decimal:
"""
Retrieves the account balance for the current account at a certain date, given the list of transactions.
If the provided date is None, it will retrieve the ending balance.
:param account: Account to get the balance of
:type account: Account
:param date: Last date to consider when determining the account balance.
:type date: datetime.datetime
:return: Account balance at specified date (or ending balance) or 0, if no applicable transactions were found.
:rtype: decimal.Decimal
"""
return Decimal(
self.query().where(lambda t: date is None or t.date_posted <= date)
.select_many(lambda t, i: t.splits)
.where(lambda s: s.account == account)
.select(lambda s, i: s.amount * -1 if s.account.type == AccountType.CREDIT else s.amount)
.sum_()
)
[docs]
def get_balance_at_transaction(self, account: Account, transaction: Transaction) -> Decimal:
"""
Retrieves the account balance for the specified account at a certain transaction.
:param account: Account to get the balance of
:type account: Account
:param transaction: Last transaction to consider when determining the account balance.
:type transaction: Transaction
:return: Account balance at specified transaction or 0, if no applicable transactions were found.
:rtype: decimal.Decimal
"""
return Decimal(
self.query().take_while(lambda t: t.guid != transaction.guid)
.select_many(lambda t, i: t.splits)
.where(lambda s: s.amount is not None and s.account == account)
.select(lambda s, i: s.amount)
.sum_()
)
[docs]
def get_cleared_balance(self, account: Account) -> Decimal:
"""
Retrieves the current cleared balance for the specified account.
:param account: Account to get the cleared balance of.
:type account: Account
:return: Current cleared balance for the account
:rtype: decimal.Decimal
"""
return Decimal(
self.query().select_many(lambda t, i: t.splits)
.where(lambda s: s.reconciled_state == 'c' and s.account == account and s.amount is not None)
.select(lambda s, i: s.amount)
.sum_()
)
[docs]
def create_reversing_transaction(
self,
transaction: Transaction,
reversed_date: Optional[datetime] = None,
) -> Transaction:
"""
Creates a new transaction that reverses another transaction.
:param transaction: Transaction to be reversed
:type transaction: Transaction
:param reversed_date: The date that the transaction was reversed (optional, default is transaction's date)
:type reversed_date: datetime
:return: New transaction that reverses the provided transaction
:rtype: Transaction
"""
reversed_splits = []
for split in transaction.splits:
reversed_splits.append(Split(
account=split.account,
amount=split.amount * -1 if split.amount is not None else None,
reconciled_state=split.reconciled_state,
action=split.action,
memo=split.memo,
quantity_denominator=split.quantity_denominator,
reconcile_date=split.reconcile_date,
quantity_num=split.quantity_num,
lot_guid=split.lot_guid,
value_num=split.value_num,
value_denom=split.value_denom
))
reversing_transaction = Transaction(
slots=transaction.slots,
currency=transaction.currency,
date_posted=reversed_date or transaction.date_posted,
date_entered=transaction.date_entered,
description=transaction.description,
splits=reversed_splits,
memo=transaction.memo
)
self.add(reversing_transaction)
transaction.slots.append(Slot(
key='reversed-by',
value=reversing_transaction.guid,
slot_type='guid'
))
return reversing_transaction
[docs]
def query(self) -> Query:
"""
Gets a new Query object to query transactions.
:return: New Query object
:rtype: Query
"""
return Query(self.transactions)
# Making TransactionManager iterable
def __getitem__(self, item: int) -> Transaction:
if item > len(self):
raise IndexError
return self.transactions[item]
def __len__(self) -> int:
return len(self.transactions)
def __eq__(self, other: object) -> bool:
if not isinstance(other, TransactionManager):
raise NotImplementedError
for my_transaction, other_transaction in zip(self.transactions, other.transactions):
if my_transaction != other_transaction:
return False
return True
def __iter__(self) -> Iterator[Transaction]:
yield from self.transactions
[docs]
class ScheduledTransaction(GuidObject):
"""Class that represents a scheduled transaction in Gnucash."""
def __init__(
self,
guid: Optional[str] = None,
name: Optional[str] = None,
enabled: Optional[bool] = False,
auto_create: Optional[bool] = False,
auto_create_notify: Optional[bool] = False,
advance_create_days: Optional[int] = -1,
advance_remind_days: Optional[int] = -1,
instance_count: Optional[int] = 0,
start_date: Optional[datetime] = None,
last_date: Optional[datetime] = None,
end_date: Optional[datetime] = None,
template_account: Optional[Account] = None,
recurrence_multiplier: Optional[int] = 0,
recurrence_period: Optional[str] = None,
recurrence_start: Optional[datetime] = None,
num_occur: Optional[int] = None,
rem_occur: Optional[int] = None,
recurrence_weekend_adjust: Optional[str] = None,
) -> None:
super().__init__(guid)
self.name: Optional[str] = name
self.enabled: Optional[bool] = enabled
self.auto_create: Optional[bool] = auto_create
self.auto_create_notify: Optional[bool] = auto_create_notify
self.advance_create_days: Optional[int] = advance_create_days
self.advance_remind_days: Optional[int] = advance_remind_days
self.instance_count: Optional[int] = instance_count
self.start_date: Optional[datetime] = start_date
self.last_date: Optional[datetime] = last_date
self.end_date: Optional[datetime] = end_date
self.template_account: Optional[Account] = template_account
self.recurrence_multiplier: Optional[int] = recurrence_multiplier
self.recurrence_period: Optional[str] = recurrence_period
self.recurrence_start: Optional[datetime] = recurrence_start
self.num_occur: Optional[int] = num_occur
self.rem_occur: Optional[int] = rem_occur
self.recurrence_weekend_adjust: Optional[str] = recurrence_weekend_adjust
[docs]
class SimpleTransaction(Transaction):
"""Class used to simplify creating and manipulating Transactions that only have 2 splits."""
def __init__(
self,
from_account: Optional[Account] = None,
to_account: Optional[Account] = None,
amount: Optional[Decimal] = None,
currency: Optional[Commodity] = None,
date_posted: Optional[datetime] = None,
date_entered: Optional[datetime] = None,
description: str = '',
memo: Optional[str] = None,
) -> None:
super().__init__(
currency=currency,
date_posted=date_posted,
date_entered=date_entered,
description=description,
memo=memo,
)
self.from_split: Split = Split(None, None)
self.to_split: Split = Split(None, None)
self.splits: list[Split] = [self.from_split, self.to_split]
if from_account is not None:
self.from_account = from_account
if to_account is not None:
self.to_account = to_account
if amount is not None:
self.amount = amount
@property
def from_account(self) -> Optional[Account]:
"""
Account which the transaction transfers funds from.
:return: Account which the transaction transfers funds from.
:rtype: Account
"""
return self.from_split.account
@from_account.setter
def from_account(self, value: 'Account') -> None:
self.from_split.account = value
@property
def to_account(self) -> Optional[Account]:
"""
Account which the transaction transfers funds to.
:return: Account which the transaction transfers funds to.
:rtype: Account
"""
return self.to_split.account
@to_account.setter
def to_account(self, value: Account) -> None:
self.to_split.account = value
@property
def amount(self) -> Optional[Decimal]:
"""
Dollar amount for funds transferred.
:return: Dollar amount for funds transferred.
:rtype: decimal.Decimal
"""
return self.to_split.amount
@amount.setter
def amount(self, value: Decimal) -> None:
self.from_split.amount = value * -1
self.to_split.amount = value
[docs]
@classmethod
def from_transaction(cls, other: Transaction) -> 'SimpleTransaction':
"""Creates a SimpleTransaction from a regular Transaction."""
simple: SimpleTransaction = cls()
simple.guid = other.guid
simple.currency = other.currency
simple.date_posted = other.date_posted
simple.date_entered = other.date_entered
simple.description = other.description
simple.splits = other.splits
simple.memo = other.memo
if len(simple.splits) > 2:
raise TransactionException(
'SimpleTransactions can only be created from transactions with 2 splits: ' +
f'{other} has {len(simple.splits)} splits - {", ".join([str(x) for x in other.splits])}'
)
first_split = simple.splits[0]
second_split = simple.splits[1] if len(simple.splits) > 1 else first_split
first_split_amount = first_split.amount
second_split_amount = second_split.amount
if any((first_split_amount is None, second_split_amount is None, first_split_amount == second_split_amount)):
warnings.warn(f'Could not determine to/from split on SimpleTransaction for {simple.guid}. ' +
'Assuming first split is "from" split, assuming second is "to" split.')
simple.from_split = first_split
simple.to_split = second_split
elif first_split_amount is not None and second_split_amount is not None:
if first_split_amount > second_split_amount:
simple.to_split = first_split
simple.from_split = second_split
elif first_split_amount < second_split_amount:
simple.to_split = second_split
simple.from_split = first_split
return simple
class SortingResult(enum.Enum):
"""Enumeration class that determines the result of the sort."""
FIRST_GREATER = 1
SECOND_GREATER = -1
EQUAL = 0
[docs]
class SortingMethod:
"""Base class for derivative sorting method classes."""
def __init__(self, reverse: bool = False):
self.reverse = reverse
[docs]
def compare(self, transaction1: Transaction, transaction2: Transaction) -> SortingResult:
"""
Compares one transaction with another and returns which one is greater, or if they're equal.
:param transaction1: First transaction in comparison.
:type transaction1: Transaction
:param transaction2: Second transaction in comparison.
:type transaction2: Transaction
:return: Enum result that contains if the first transaction is greater, second transaction is greater, or equal.
:rtype: SortingResult
"""
raise NotImplementedError
def _reverse_sort_result(self, sorting_result: SortingResult) -> SortingResult:
if not self.reverse:
return sorting_result
if sorting_result == SortingResult.FIRST_GREATER:
return SortingResult.SECOND_GREATER
if sorting_result == SortingResult.SECOND_GREATER:
return SortingResult.FIRST_GREATER
return SortingResult.EQUAL
@classmethod
def _get_compare_result(cls, first_value: Any, second_value: Any) -> SortingResult:
if first_value is not None and (second_value is None or first_value > second_value):
return SortingResult.FIRST_GREATER
if second_value is not None and (first_value is None or first_value < second_value):
return SortingResult.SECOND_GREATER
return SortingResult.EQUAL
[docs]
class StandardSort(SortingMethod):
"""Sort logic for GnuCash's standard sort."""
[docs]
def compare(self, transaction1: Transaction, transaction2: Transaction) -> SortingResult:
"""
Compares one transaction with another and returns which one is greater, or if they're equal.
:param transaction1: First transaction in comparison.
:type transaction1: Transaction
:param transaction2: Second transaction in comparison.
:type transaction2: Transaction
:return: Enum result that contains if the first transaction is greater, second transaction is greater, or equal.
:rtype: SortingResult
"""
result: SortingResult = SortingResult.EQUAL
transaction_attrs = ('date_posted', 'date_entered', 'description', 'guid')
for transaction_attr in transaction_attrs:
transaction1_value = getattr(transaction1, transaction_attr)
transaction2_value = getattr(transaction2, transaction_attr)
result = self._get_compare_result(transaction1_value, transaction2_value)
if result != SortingResult.EQUAL:
break
if result != SortingResult.EQUAL:
return self._reverse_sort_result(result)
split_attrs = ('memo', 'action', 'reconciled_state', 'amount', 'value_num', 'reconcile_date', 'guid')
for split_attr in split_attrs:
split1_value = getattr(transaction1.splits[0], split_attr)
split2_value = getattr(transaction2.splits[0], split_attr)
result = self._get_compare_result(split1_value, split2_value)
if result != SortingResult.EQUAL:
break
return self._reverse_sort_result(result)
[docs]
class InvalidSortFieldException(Exception):
"""Custom exception class for when the sort field isn't set."""
class SortBySingleTransactionFieldMethod(SortingMethod):
"""Abstract class for sorting methods that operate on a single transaction field."""
def __init__(self, reverse: bool = False) -> None:
super().__init__(reverse)
self.sort_field: Optional[str] = None
def compare(self, transaction1: Transaction, transaction2: Transaction) -> SortingResult:
"""
Compares one transaction with another and returns which one is greater, or if they're equal.
:param transaction1: First transaction in comparison.
:type transaction1: Transaction
:param transaction2: Second transaction in comparison.
:type transaction2: Transaction
:return: Enum result that contains if the first transaction is greater, second transaction is greater, or equal.
:rtype: SortingResult
"""
if self.sort_field is None:
raise InvalidSortFieldException('Sort field not set.')
transaction1_value = getattr(transaction1, self.sort_field)
transaction2_value = getattr(transaction2, self.sort_field)
result = self._get_compare_result(transaction1_value, transaction2_value)
return self._reverse_sort_result(result)
class SortBySingleSplitFieldMethod(SortingMethod):
"""Abstract class for sorting methods that operate on a single split field."""
def __init__(self, reverse: bool = False) -> None:
super().__init__(reverse)
self.sort_field: Optional[str] = None
def compare(self, transaction1: Transaction, transaction2: Transaction) -> SortingResult:
"""
Compares one transaction with another and returns which one is greater, or if they're equal.
:param transaction1: First transaction in comparison.
:type transaction1: Transaction
:param transaction2: Second transaction in comparison.
:type transaction2: Transaction
:return: Enum result that contains if the first transaction is greater, second transaction is greater, or equal.
:rtype: SortingResult
"""
if self.sort_field is None:
raise InvalidSortFieldException('Sort field not set.')
split1_value = getattr(transaction1.splits[0], self.sort_field)
split2_value = getattr(transaction2.splits[0], self.sort_field)
result = self._get_compare_result(split1_value, split2_value)
return self._reverse_sort_result(result)
[docs]
class DateSort(SortBySingleTransactionFieldMethod):
"""Sort logic for GnuCash's date sort."""
def __init__(self, reverse: bool = False) -> None:
super().__init__(reverse)
self.sort_field = 'date_posted'
[docs]
class DateOfEntrySort(SortBySingleTransactionFieldMethod):
"""Sort logic for GnuCash's date of entry sort."""
def __init__(self, reverse: bool = False) -> None:
super().__init__(reverse)
self.sort_field = 'date_entered'
[docs]
class TransactionNumberSort(SortBySingleTransactionFieldMethod):
"""Sort logic for GnuCash's transaction number sort."""
def __init__(self, reverse: bool = False) -> None:
super().__init__(reverse)
self.sort_field = 'guid'
[docs]
class DescriptionSort(SortBySingleTransactionFieldMethod):
"""Sort logic for GnuCash's description sort."""
def __init__(self, reverse: bool = False) -> None:
super().__init__(reverse)
self.sort_field = 'description'
[docs]
class AmountSort(SortBySingleSplitFieldMethod):
"""Sort logic for GnuCash's split amount sort."""
def __init__(self, reverse: bool = False) -> None:
super().__init__(reverse)
self.sort_field = 'amount'
[docs]
class NumberActionSort(SortBySingleSplitFieldMethod):
"""Sort logic for GnuCash's number/action sort."""
def __init__(self, reverse: bool = False) -> None:
super().__init__(reverse)
self.sort_field = 'action'
[docs]
class MemoSort(SortBySingleSplitFieldMethod):
"""Sort logic for GnuCash's memo sort."""
def __init__(self, reverse: bool = False) -> None:
super().__init__(reverse)
self.sort_field = 'memo'