diff --git a/hledger-lib/Hledger/Data/Account.hs b/hledger-lib/Hledger/Data/Account.hs index d07301b6d..1041bd561 100644 --- a/hledger-lib/Hledger/Data/Account.hs +++ b/hledger-lib/Hledger/Data/Account.hs @@ -47,6 +47,7 @@ instance Eq Account where nullacct = Account { aname = "" + , acode = Nothing , aparent = Nothing , asubs = [] , anumpostings = 0 @@ -90,6 +91,10 @@ tieAccountParents = tie Nothing where a' = a{aparent=parent, asubs=map (tie (Just a')) asubs} +-- | Look up an account's numeric code, if any, from the Journal and set it. +accountSetCodeFrom :: Journal -> Account -> Account +accountSetCodeFrom j a = a{acode=fromMaybe Nothing $ (lookup (aname a) $ jaccounts j)} + -- | Get this account's parent accounts, from the nearest up to the root. parentAccounts :: Account -> [Account] parentAccounts Account{aparent=Nothing} = [] @@ -188,7 +193,9 @@ filterAccounts p a -- | Sort each level of an account tree by inclusive amount, -- so that the accounts with largest normal balances are listed first. -- The provided normal balance sign determines whether normal balances --- are negative or positive. +-- are negative or positive, affecting the sort order. Ie, +-- if balances are normally negative, then the most negative balances +-- sort first, and vice versa. sortAccountTreeByAmount :: NormalSign -> Account -> Account sortAccountTreeByAmount normalsign a | null $ asubs a = a @@ -199,6 +206,19 @@ sortAccountTreeByAmount normalsign a maybeflip | normalsign==NormallyNegative = id | otherwise = flip +-- | Sort each level of an account tree first by the account code +-- if any, with the empty account code sorting last, and then by +-- the account name. +sortAccountTreeByAccountCodeAndName :: Account -> Account +sortAccountTreeByAccountCodeAndName a + | null $ asubs a = a + | otherwise = a{asubs= + sortBy (comparing accountCodeAndNameForSort) $ map sortAccountTreeByAccountCodeAndName $ asubs a} + +accountCodeAndNameForSort a = (acode', aname a) + where + acode' = fromMaybe maxBound (acode a) + -- | Search an account list by name. lookupAccount :: AccountName -> [Account] -> Maybe Account lookupAccount a = find ((==a).aname) diff --git a/hledger-lib/Hledger/Data/Ledger.hs b/hledger-lib/Hledger/Data/Ledger.hs index 10d9d0c45..0ca7aac20 100644 --- a/hledger-lib/Hledger/Data/Ledger.hs +++ b/hledger-lib/Hledger/Data/Ledger.hs @@ -47,7 +47,7 @@ ledgerFromJournal q j = nullledger{ljournal=j'', laccounts=as} (q',depthq) = (filterQuery (not . queryIsDepth) q, filterQuery queryIsDepth q) j' = filterJournalAmounts (filterQuery queryIsSym q) $ -- remove amount parts which the query's sym: terms would exclude filterJournalPostings q' j - as = accountsFromPostings $ journalPostings j' + as = map (accountSetCodeFrom j) $ accountsFromPostings $ journalPostings j' j'' = filterJournalPostings depthq j' -- | List a ledger's account names. diff --git a/hledger-lib/Hledger/Data/Types.hs b/hledger-lib/Hledger/Data/Types.hs index 4328f97d0..2c012383f 100644 --- a/hledger-lib/Hledger/Data/Types.hs +++ b/hledger-lib/Hledger/Data/Types.hs @@ -354,6 +354,7 @@ instance Show Reader where show r = rFormat r ++ " reader" -- which let you walk up or down the account tree. data Account = Account { aname :: AccountName, -- ^ this account's full name + acode :: Maybe AccountCode, -- ^ this account's numeric code, if any (not always set) aebalance :: MixedAmount, -- ^ this account's balance, excluding subaccounts asubs :: [Account], -- ^ sub-accounts anumpostings :: Int, -- ^ number of postings to this account diff --git a/hledger-lib/Hledger/Reports/BalanceReport.hs b/hledger-lib/Hledger/Reports/BalanceReport.hs index 9044c8d5a..63d6b3e15 100644 --- a/hledger-lib/Hledger/Reports/BalanceReport.hs +++ b/hledger-lib/Hledger/Reports/BalanceReport.hs @@ -88,7 +88,7 @@ balanceReport opts q j = (items, total) dbg1 "accts" $ take 1 $ clipAccountsAndAggregate (queryDepth q) $ flattenAccounts accts | flat_ opts = dbg1 "accts" $ - maybesortflat $ + sortflat $ filterzeros $ filterempty $ drop 1 $ clipAccountsAndAggregate (queryDepth q) $ flattenAccounts accts @@ -97,7 +97,7 @@ balanceReport opts q j = (items, total) drop 1 $ flattenAccounts $ markboring $ prunezeros $ - maybesorttree $ + sorttree $ clipAccounts (queryDepth q) accts where balance = if flat_ opts then aebalance else aibalance @@ -105,12 +105,12 @@ balanceReport opts q j = (items, total) filterempty = filter (\a -> anumpostings a > 0 || not (isZeroMixedAmount (balance a))) prunezeros = if empty_ opts then id else fromMaybe nullacct . pruneAccounts (isZeroMixedAmount . balance) markboring = if no_elide_ opts then id else markBoringParentAccounts - maybesortflat | sort_amount_ opts = sortBy (maybeflip $ comparing balance) - | otherwise = id + sortflat | sort_amount_ opts = sortBy (maybeflip $ comparing balance) + | otherwise = sortBy (comparing accountCodeAndNameForSort) where maybeflip = if normalbalance_ opts == Just NormallyNegative then id else flip - maybesorttree | sort_amount_ opts = sortAccountTreeByAmount (fromMaybe NormallyPositive $ normalbalance_ opts) - | otherwise = id + sorttree | sort_amount_ opts = sortAccountTreeByAmount (fromMaybe NormallyPositive $ normalbalance_ opts) + | otherwise = sortAccountTreeByAccountCodeAndName items = dbg1 "items" $ map (balanceReportItem opts q) accts' total | not (flat_ opts) = dbg1 "total" $ sum [amt | (_,_,indent,amt) <- items, indent == 0] | otherwise = dbg1 "total" $ diff --git a/hledger-lib/Hledger/Reports/MultiBalanceReports.hs b/hledger-lib/Hledger/Reports/MultiBalanceReports.hs index eb01569c7..6444e5d88 100644 --- a/hledger-lib/Hledger/Reports/MultiBalanceReports.hs +++ b/hledger-lib/Hledger/Reports/MultiBalanceReports.hs @@ -172,12 +172,13 @@ multiBalanceReport opts q j = MultiBalanceReport (displayspans, sorteditems, tot sorteditems :: [MultiBalanceReportRow] = dbg1 "sorteditems" $ - maybesort items + sortitems items where - maybesort - | not $ sort_amount_ opts = id - | accountlistmode_ opts == ALTree = sortTreeMultiBalanceReportRowsByAmount - | otherwise = sortFlatMultiBalanceReportRowsByAmount + sortitems + | sort_amount_ opts && accountlistmode_ opts == ALTree = sortTreeMultiBalanceReportRowsByAmount + | sort_amount_ opts = sortFlatMultiBalanceReportRowsByAmount + | not (sort_amount_ opts) && accountlistmode_ opts == ALTree = sortTreeMultiBalanceReportRowsByAccountCodeAndName + | otherwise = sortFlatMultiBalanceReportRowsByAccountCodeAndName where -- Sort the report rows, representing a flat account list, by row total. sortFlatMultiBalanceReportRowsByAmount = sortBy (maybeflip $ comparing fifth6) @@ -201,11 +202,31 @@ multiBalanceReport opts q j = MultiBalanceReport (displayspans, sorteditems, tot setibalance a = a{aibalance=fromMaybe (error "sortTreeMultiBalanceReportRowsByAmount 1") $ lookup (aname a) atotals} sortedaccounttree = sortAccountTreeByAmount (fromMaybe NormallyPositive $ normalbalance_ opts) accounttreewithbals sortedaccounts = drop 1 $ flattenAccounts sortedaccounttree - sortedrows = [ - -- this error should not happen, but it's ugly TODO - fromMaybe (error "sortTreeMultiBalanceReportRowsByAmount 2") $ lookup (aname a) anamesandrows - | a <- sortedaccounts - ] + -- dropped the root account, also ignore any parent accounts not in rows + sortedrows = concatMap (\a -> maybe [] (:[]) $ lookup (aname a) anamesandrows) sortedaccounts + + -- Sort the report rows by account code if any, with the empty account code coming last, then account name. + sortFlatMultiBalanceReportRowsByAccountCodeAndName = sortBy (comparing acodeandname) + where + acodeandname r = (acode', aname) + where + aname = first6 r + macode = fromMaybe Nothing $ lookup aname $ jaccounts j + acode' = fromMaybe maxBound macode + + -- Sort the report rows, representing a tree of accounts, by account code and then account name at each level. + -- Convert a tree of account names, look up the account codes, sort and flatten the tree, reorder the rows. + sortTreeMultiBalanceReportRowsByAccountCodeAndName rows = sortedrows + where + anamesandrows = [(first6 r, r) | r <- rows] + anames = map fst anamesandrows + nametree = treeFromPaths $ map expandAccountName anames + accounttree = nameTreeToAccount "root" nametree + accounttreewithcodes = mapAccounts (accountSetCodeFrom j) accounttree + sortedaccounttree = sortAccountTreeByAccountCodeAndName accounttreewithcodes + sortedaccounts = drop 1 $ flattenAccounts sortedaccounttree + -- dropped the root account, also ignore any parent accounts not in rows + sortedrows = concatMap (\a -> maybe [] (:[]) $ lookup (aname a) anamesandrows) sortedaccounts totals :: [MixedAmount] = -- dbg1 "totals" $