{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NamedFieldPuns #-}
module Hledger.Data.JournalChecks (
journalCheckAccounts,
journalCheckCommodities,
journalCheckPayees,
journalCheckPairedConversionPostings,
journalCheckRecentAssertions,
journalCheckTags,
module Hledger.Data.JournalChecks.Ordereddates,
module Hledger.Data.JournalChecks.Uniqueleafnames,
)
where
import Data.Char (isSpace)
import Data.List.Extra
import Data.Maybe
import qualified Data.Map.Strict as M
import qualified Data.Text as T
import Safe (atMay, lastMay, headMay)
import Text.Printf (printf)
import Hledger.Data.Errors
import Hledger.Data.Journal
import Hledger.Data.JournalChecks.Ordereddates
import Hledger.Data.JournalChecks.Uniqueleafnames
import Hledger.Data.Posting (isVirtual, postingDate, transactionAllTags)
import Hledger.Data.Types
import Hledger.Data.Amount (amountIsZero, amountsRaw, missingamt, amounts)
import Hledger.Data.Transaction (transactionPayee, showTransactionLineFirstPart, partitionAndCheckConversionPostings)
import Data.Time (Day, diffDays)
import Hledger.Utils
import Data.Ord
import Hledger.Data.Dates (showDate)
journalCheckAccounts :: Journal -> Either String ()
journalCheckAccounts :: Journal -> Either FilePath ()
journalCheckAccounts Journal
j = (Posting -> Either FilePath ()) -> [Posting] -> Either FilePath ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Posting -> Either FilePath ()
forall {a}. PrintfType a => Posting -> Either a ()
checkacct (Journal -> [Posting]
journalPostings Journal
j)
where
checkacct :: Posting -> Either a ()
checkacct p :: Posting
p@Posting{paccount :: Posting -> Text
paccount=Text
a}
| Text
a Text -> [Text] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` Journal -> [Text]
journalAccountNamesDeclared Journal
j = () -> Either a ()
forall a b. b -> Either a b
Right ()
| Bool
otherwise = a -> Either a ()
forall a b. a -> Either a b
Left (a -> Either a ()) -> a -> Either a ()
forall a b. (a -> b) -> a -> b
$ FilePath
-> FilePath -> Int -> Text -> FilePath -> Text -> Text -> a
forall r. PrintfType r => FilePath -> r
printf ([FilePath] -> FilePath
unlines [
FilePath
"%s:%d:"
,FilePath
"%s"
,FilePath
"Strict account checking is enabled, and"
,FilePath
"account %s has not been declared."
,FilePath
"Consider adding an account directive. Examples:"
,FilePath
""
,FilePath
"account %s"
,FilePath
"account %s ; type:A ; (L,E,R,X,C,V)"
]) FilePath
f Int
l Text
ex (Text -> FilePath
forall a. Show a => a -> FilePath
show Text
a) Text
a Text
a
where
(FilePath
f,Int
l,Maybe (Int, Maybe Int)
_mcols,Text
ex) = Posting -> (FilePath, Int, Maybe (Int, Maybe Int), Text)
makePostingAccountErrorExcerpt Posting
p
journalCheckCommodities :: Journal -> Either String ()
journalCheckCommodities :: Journal -> Either FilePath ()
journalCheckCommodities Journal
j = (Posting -> Either FilePath ()) -> [Posting] -> Either FilePath ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Posting -> Either FilePath ()
forall {a}. PrintfType a => Posting -> Either a ()
checkcommodities (Journal -> [Posting]
journalPostings Journal
j)
where
checkcommodities :: Posting -> Either a ()
checkcommodities Posting
p =
case Posting -> Maybe (Text, Bool)
findundeclaredcomm Posting
p of
Maybe (Text, Bool)
Nothing -> () -> Either a ()
forall a b. b -> Either a b
Right ()
Just (Text
comm, Bool
_) ->
a -> Either a ()
forall a b. a -> Either a b
Left (a -> Either a ()) -> a -> Either a ()
forall a b. (a -> b) -> a -> b
$ FilePath
-> FilePath -> Int -> Text -> FilePath -> Text -> Text -> a
forall r. PrintfType r => FilePath -> r
printf ([FilePath] -> FilePath
unlines [
FilePath
"%s:%d:"
,FilePath
"%s"
,FilePath
"Strict commodity checking is enabled, and"
,FilePath
"commodity %s has not been declared."
,FilePath
"Consider adding a commodity directive. Examples:"
,FilePath
""
,FilePath
"commodity %s1000.00"
,FilePath
"commodity 1.000,00 %s"
]) FilePath
f Int
l Text
ex (Text -> FilePath
forall a. Show a => a -> FilePath
show Text
comm) Text
comm Text
comm
where
(FilePath
f,Int
l,Maybe (Int, Maybe Int)
_mcols,Text
ex) = Posting
-> (Posting -> Transaction -> Text -> Maybe (Int, Maybe Int))
-> (FilePath, Int, Maybe (Int, Maybe Int), Text)
makePostingErrorExcerpt Posting
p Posting -> Transaction -> Text -> Maybe (Int, Maybe Int)
finderrcols
where
findundeclaredcomm :: Posting -> Maybe (CommoditySymbol, Bool)
findundeclaredcomm :: Posting -> Maybe (Text, Bool)
findundeclaredcomm Posting{pamount :: Posting -> MixedAmount
pamount=MixedAmount
amt,Maybe BalanceAssertion
pbalanceassertion :: Maybe BalanceAssertion
pbalanceassertion :: Posting -> Maybe BalanceAssertion
pbalanceassertion} =
case ([Text] -> Maybe Text
findundeclared [Text]
postingcomms, [Text] -> Maybe Text
findundeclared [Text]
assertioncomms) of
(Just Text
c, Maybe Text
_) -> (Text, Bool) -> Maybe (Text, Bool)
forall a. a -> Maybe a
Just (Text
c, Bool
True)
(Maybe Text
_, Just Text
c) -> (Text, Bool) -> Maybe (Text, Bool)
forall a. a -> Maybe a
Just (Text
c, Bool
False)
(Maybe Text, Maybe Text)
_ -> Maybe (Text, Bool)
forall a. Maybe a
Nothing
where
postingcomms :: [Text]
postingcomms = (Amount -> Text) -> [Amount] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Amount -> Text
acommodity ([Amount] -> [Text]) -> [Amount] -> [Text]
forall a b. (a -> b) -> a -> b
$ (Amount -> Bool) -> [Amount] -> [Amount]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (Amount -> Bool) -> Amount -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Amount -> Bool
isIgnorable) ([Amount] -> [Amount]) -> [Amount] -> [Amount]
forall a b. (a -> b) -> a -> b
$ MixedAmount -> [Amount]
amountsRaw MixedAmount
amt
where
isIgnorable :: Amount -> Bool
isIgnorable Amount
a = (Text -> Bool
T.null (Amount -> Text
acommodity Amount
a) Bool -> Bool -> Bool
&& Amount -> Bool
amountIsZero Amount
a) Bool -> Bool -> Bool
|| Amount
a Amount -> Amount -> Bool
forall a. Eq a => a -> a -> Bool
== Amount
missingamt
assertioncomms :: [Text]
assertioncomms = [Amount -> Text
acommodity Amount
a | Just Amount
a <- [BalanceAssertion -> Amount
baamount (BalanceAssertion -> Amount)
-> Maybe BalanceAssertion -> Maybe Amount
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe BalanceAssertion
pbalanceassertion]]
findundeclared :: [Text] -> Maybe Text
findundeclared = (Text -> Bool) -> [Text] -> Maybe Text
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (Text -> Map Text Commodity -> Bool
forall k a. Ord k => k -> Map k a -> Bool
`M.notMember` Journal -> Map Text Commodity
jcommodities Journal
j)
finderrcols :: Posting -> Transaction -> Text -> Maybe (Int, Maybe Int)
finderrcols Posting
p' Transaction
t Text
txntxt =
case (Posting -> Bool) -> Transaction -> Maybe Int
transactionFindPostingIndex (Posting -> Posting -> Bool
forall a. Eq a => a -> a -> Bool
==Posting
p') Transaction
t of
Maybe Int
Nothing -> Maybe (Int, Maybe Int)
forall a. Maybe a
Nothing
Just Int
pindex -> (Int, Maybe Int) -> Maybe (Int, Maybe Int)
forall a. a -> Maybe a
Just (Int
amtstart, Int -> Maybe Int
forall a. a -> Maybe a
Just Int
amtend)
where
tcommentlines :: Int
tcommentlines = Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
0 ([Text] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length (Text -> [Text]
T.lines (Text -> [Text]) -> Text -> [Text]
forall a b. (a -> b) -> a -> b
$ Transaction -> Text
tcomment Transaction
t) Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1)
errrelline :: Int
errrelline = Int
1 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
tcommentlines Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
pindex
errline :: Text
errline = Text -> Maybe Text -> Text
forall a. a -> Maybe a -> a
fromMaybe Text
"" (Text -> [Text]
T.lines Text
txntxt [Text] -> Int -> Maybe Text
forall a. [a] -> Int -> Maybe a
`atMay` (Int
errrellineInt -> Int -> Int
forall a. Num a => a -> a -> a
-Int
1))
acctend :: Int
acctend = Int
4 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Text -> Int
T.length (Posting -> Text
paccount Posting
p') Int -> Int -> Int
forall a. Num a => a -> a -> a
+ if Posting -> Bool
isVirtual Posting
p' then Int
2 else Int
0
amtstart :: Int
amtstart = Int
acctend Int -> Int -> Int
forall a. Num a => a -> a -> a
+ (Text -> Int
T.length (Text -> Int) -> Text -> Int
forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> Text -> Text
T.takeWhile Char -> Bool
isSpace (Text -> Text) -> Text -> Text
forall a b. (a -> b) -> a -> b
$ Int -> Text -> Text
T.drop Int
acctend Text
errline) Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1
amtend :: Int
amtend = Int
amtstart Int -> Int -> Int
forall a. Num a => a -> a -> a
+ (Text -> Int
T.length (Text -> Int) -> Text -> Int
forall a b. (a -> b) -> a -> b
$ Text -> Text
T.stripEnd (Text -> Text) -> Text -> Text
forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> Text -> Text
T.takeWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/=Char
';') (Text -> Text) -> Text -> Text
forall a b. (a -> b) -> a -> b
$ Int -> Text -> Text
T.drop Int
amtstart Text
errline)
journalCheckPayees :: Journal -> Either String ()
journalCheckPayees :: Journal -> Either FilePath ()
journalCheckPayees Journal
j = (Transaction -> Either FilePath ())
-> [Transaction] -> Either FilePath ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Transaction -> Either FilePath ()
forall {a}. PrintfType a => Transaction -> Either a ()
checkpayee (Journal -> [Transaction]
jtxns Journal
j)
where
checkpayee :: Transaction -> Either a ()
checkpayee Transaction
t
| Text
payee Text -> [Text] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` Journal -> [Text]
journalPayeesDeclared Journal
j = () -> Either a ()
forall a b. b -> Either a b
Right ()
| Bool
otherwise = a -> Either a ()
forall a b. a -> Either a b
Left (a -> Either a ()) -> a -> Either a ()
forall a b. (a -> b) -> a -> b
$
FilePath -> FilePath -> Int -> Text -> FilePath -> Text -> a
forall r. PrintfType r => FilePath -> r
printf ([FilePath] -> FilePath
unlines [
FilePath
"%s:%d:"
,FilePath
"%s"
,FilePath
"Strict payee checking is enabled, and"
,FilePath
"payee %s has not been declared."
,FilePath
"Consider adding a payee directive. Examples:"
,FilePath
""
,FilePath
"payee %s"
]) FilePath
f Int
l Text
ex (Text -> FilePath
forall a. Show a => a -> FilePath
show Text
payee) Text
payee
where
payee :: Text
payee = Transaction -> Text
transactionPayee Transaction
t
(FilePath
f,Int
l,Maybe (Int, Maybe Int)
_mcols,Text
ex) = Transaction
-> (Transaction -> Maybe (Int, Maybe Int))
-> (FilePath, Int, Maybe (Int, Maybe Int), Text)
makeTransactionErrorExcerpt Transaction
t Transaction -> Maybe (Int, Maybe Int)
finderrcols
finderrcols :: Transaction -> Maybe (Int, Maybe Int)
finderrcols Transaction
t' = (Int, Maybe Int) -> Maybe (Int, Maybe Int)
forall a. a -> Maybe a
Just (Int
col, Int -> Maybe Int
forall a. a -> Maybe a
Just Int
col2)
where
col :: Int
col = Text -> Int
T.length (Transaction -> Text
showTransactionLineFirstPart Transaction
t') Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
2
col2 :: Int
col2 = Int
col Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Text -> Int
T.length (Transaction -> Text
transactionPayee Transaction
t') Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1
journalCheckTags :: Journal -> Either String ()
journalCheckTags :: Journal -> Either FilePath ()
journalCheckTags Journal
j = do
((Text, AccountDeclarationInfo) -> Either FilePath ())
-> [(Text, AccountDeclarationInfo)] -> Either FilePath ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (Text, AccountDeclarationInfo) -> Either FilePath ()
forall {a}.
PrintfType a =>
(Text, AccountDeclarationInfo) -> Either a ()
checkaccttags ([(Text, AccountDeclarationInfo)] -> Either FilePath ())
-> [(Text, AccountDeclarationInfo)] -> Either FilePath ()
forall a b. (a -> b) -> a -> b
$ Journal -> [(Text, AccountDeclarationInfo)]
jdeclaredaccounts Journal
j
(Transaction -> Either FilePath ())
-> [Transaction] -> Either FilePath ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Transaction -> Either FilePath ()
forall {a}. PrintfType a => Transaction -> Either a ()
checktxntags ([Transaction] -> Either FilePath ())
-> [Transaction] -> Either FilePath ()
forall a b. (a -> b) -> a -> b
$ Journal -> [Transaction]
jtxns Journal
j
where
checkaccttags :: (Text, AccountDeclarationInfo) -> Either a ()
checkaccttags (Text
a, AccountDeclarationInfo
adi) = (Tag -> Either a ()) -> [Tag] -> Either a ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (Text -> Either a ()
forall {a}. PrintfType a => Text -> Either a ()
checkaccttag(Text -> Either a ()) -> (Tag -> Text) -> Tag -> Either a ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
.Tag -> Text
forall a b. (a, b) -> a
fst) ([Tag] -> Either a ()) -> [Tag] -> Either a ()
forall a b. (a -> b) -> a -> b
$ AccountDeclarationInfo -> [Tag]
aditags AccountDeclarationInfo
adi
where
checkaccttag :: Text -> Either a ()
checkaccttag Text
tagname
| Text
tagname Text -> [Text] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Text]
declaredtags = () -> Either a ()
forall a b. b -> Either a b
Right ()
| Bool
otherwise = a -> Either a ()
forall a b. a -> Either a b
Left (a -> Either a ()) -> a -> Either a ()
forall a b. (a -> b) -> a -> b
$ FilePath -> FilePath -> Int -> Text -> FilePath -> Text -> a
forall r. PrintfType r => FilePath -> r
printf FilePath
msg FilePath
f Int
l Text
ex (Text -> FilePath
forall a. Show a => a -> FilePath
show Text
tagname) Text
tagname
where (FilePath
f,Int
l,Maybe (Int, Maybe Int)
_mcols,Text
ex) = (Text, AccountDeclarationInfo)
-> Text -> (FilePath, Int, Maybe (Int, Maybe Int), Text)
makeAccountTagErrorExcerpt (Text
a, AccountDeclarationInfo
adi) Text
tagname
checktxntags :: Transaction -> Either a ()
checktxntags Transaction
txn = (Tag -> Either a ()) -> [Tag] -> Either a ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (Text -> Either a ()
forall {a}. PrintfType a => Text -> Either a ()
checktxntag (Text -> Either a ()) -> (Tag -> Text) -> Tag -> Either a ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Tag -> Text
forall a b. (a, b) -> a
fst) ([Tag] -> Either a ()) -> [Tag] -> Either a ()
forall a b. (a -> b) -> a -> b
$ Transaction -> [Tag]
transactionAllTags Transaction
txn
where
checktxntag :: Text -> Either a ()
checktxntag Text
tagname
| Text
tagname Text -> [Text] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Text]
declaredtags = () -> Either a ()
forall a b. b -> Either a b
Right ()
| Bool
otherwise = a -> Either a ()
forall a b. a -> Either a b
Left (a -> Either a ()) -> a -> Either a ()
forall a b. (a -> b) -> a -> b
$ FilePath -> FilePath -> Int -> Text -> FilePath -> Text -> a
forall r. PrintfType r => FilePath -> r
printf FilePath
msg FilePath
f Int
l Text
ex (Text -> FilePath
forall a. Show a => a -> FilePath
show Text
tagname) Text
tagname
where
(FilePath
f,Int
l,Maybe (Int, Maybe Int)
_mcols,Text
ex) = Transaction
-> (Transaction -> Maybe (Int, Maybe Int))
-> (FilePath, Int, Maybe (Int, Maybe Int), Text)
makeTransactionErrorExcerpt Transaction
txn Transaction -> Maybe (Int, Maybe Int)
forall {p} {a}. p -> Maybe a
finderrcols
where
finderrcols :: p -> Maybe a
finderrcols p
_txn' = Maybe a
forall a. Maybe a
Nothing
declaredtags :: [Text]
declaredtags = Journal -> [Text]
journalTagsDeclared Journal
j [Text] -> [Text] -> [Text]
forall a. [a] -> [a] -> [a]
++ [Text]
builtinTags
msg :: FilePath
msg = ([FilePath] -> FilePath
unlines [
FilePath
"%s:%d:"
,FilePath
"%s"
,FilePath
"Strict tag checking is enabled, and"
,FilePath
"tag %s has not been declared."
,FilePath
"Consider adding a tag directive. Examples:"
,FilePath
""
,FilePath
"tag %s"
])
builtinTags :: [Text]
builtinTags = [
Text
"date"
,Text
"date2"
,Text
"type"
,Text
"t"
,Text
"assert"
,Text
"retain"
,Text
"start"
,Text
"generated-transaction"
,Text
"generated-posting"
,Text
"modified"
,Text
"_generated-transaction"
,Text
"_generated-posting"
,Text
"_modified"
,Text
"_conversion-matched"
]
journalCheckPairedConversionPostings :: Journal -> Either String ()
journalCheckPairedConversionPostings :: Journal -> Either FilePath ()
journalCheckPairedConversionPostings Journal
j =
(Transaction -> Either FilePath ())
-> [Transaction] -> Either FilePath ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ ([Text] -> Transaction -> Either FilePath ()
transactionCheckPairedConversionPostings [Text]
conversionaccts) ([Transaction] -> Either FilePath ())
-> [Transaction] -> Either FilePath ()
forall a b. (a -> b) -> a -> b
$ Journal -> [Transaction]
jtxns Journal
j
where conversionaccts :: [Text]
conversionaccts = Journal -> [Text]
journalConversionAccounts Journal
j
transactionCheckPairedConversionPostings :: [AccountName] -> Transaction -> Either String ()
transactionCheckPairedConversionPostings :: [Text] -> Transaction -> Either FilePath ()
transactionCheckPairedConversionPostings [Text]
conversionaccts Transaction
t =
case Bool
-> [Text]
-> [IdxPosting]
-> Either
Text ([(IdxPosting, IdxPosting)], ([IdxPosting], [IdxPosting]))
partitionAndCheckConversionPostings Bool
True [Text]
conversionaccts ([Int] -> [Posting] -> [IdxPosting]
forall a b. [a] -> [b] -> [(a, b)]
zip [Int
0..] ([Posting] -> [IdxPosting]) -> [Posting] -> [IdxPosting]
forall a b. (a -> b) -> a -> b
$ Transaction -> [Posting]
tpostings Transaction
t) of
Left Text
err -> FilePath -> Either FilePath ()
forall a b. a -> Either a b
Left (FilePath -> Either FilePath ()) -> FilePath -> Either FilePath ()
forall a b. (a -> b) -> a -> b
$ Text -> FilePath
T.unpack Text
err
Right ([(IdxPosting, IdxPosting)], ([IdxPosting], [IdxPosting]))
_ -> () -> Either FilePath ()
forall a b. b -> Either a b
Right ()
maxlag :: Integer
maxlag = Integer
7
journalCheckRecentAssertions :: Day -> Journal -> Either String ()
journalCheckRecentAssertions :: Day -> Journal -> Either FilePath ()
journalCheckRecentAssertions Day
today Journal
j =
let acctps :: [[Posting]]
acctps = (Posting -> Text) -> [Posting] -> [[Posting]]
forall k a. Eq k => (a -> k) -> [a] -> [[a]]
groupOn Posting -> Text
paccount ([Posting] -> [[Posting]]) -> [Posting] -> [[Posting]]
forall a b. (a -> b) -> a -> b
$ (Posting -> Text) -> [Posting] -> [Posting]
forall b a. Ord b => (a -> b) -> [a] -> [a]
sortOn Posting -> Text
paccount ([Posting] -> [Posting]) -> [Posting] -> [Posting]
forall a b. (a -> b) -> a -> b
$ Journal -> [Posting]
journalPostings Journal
j
in case ([Posting] -> Maybe FilePath) -> [[Posting]] -> [FilePath]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (Day -> [Posting] -> Maybe FilePath
findRecentAssertionError Day
today) [[Posting]]
acctps of
[] -> () -> Either FilePath ()
forall a b. b -> Either a b
Right ()
FilePath
firsterr:[FilePath]
_ -> FilePath -> Either FilePath ()
forall a b. a -> Either a b
Left FilePath
firsterr
findRecentAssertionError :: Day -> [Posting] -> Maybe String
findRecentAssertionError :: Day -> [Posting] -> Maybe FilePath
findRecentAssertionError Day
today [Posting]
ps = do
let rps :: [Posting]
rps = (Posting -> Down Day) -> [Posting] -> [Posting]
forall b a. Ord b => (a -> b) -> [a] -> [a]
sortOn (Day -> Down Day
forall a. a -> Down a
Data.Ord.Down (Day -> Down Day) -> (Posting -> Day) -> Posting -> Down Day
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Posting -> Day
postingDate) [Posting]
ps
let ([Posting]
afterlatestassertrps, [Posting]
untillatestassertrps) = (Posting -> Bool) -> [Posting] -> ([Posting], [Posting])
forall a. (a -> Bool) -> [a] -> ([a], [a])
span (Maybe BalanceAssertion -> Bool
forall a. Maybe a -> Bool
isNothing(Maybe BalanceAssertion -> Bool)
-> (Posting -> Maybe BalanceAssertion) -> Posting -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
.Posting -> Maybe BalanceAssertion
pbalanceassertion) [Posting]
rps
Day
latestassertdate <- Posting -> Day
postingDate (Posting -> Day) -> Maybe Posting -> Maybe Day
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [Posting] -> Maybe Posting
forall a. [a] -> Maybe a
headMay [Posting]
untillatestassertrps
let withinlimit :: Day -> Bool
withinlimit Day
date = Day -> Day -> Integer
diffDays Day
date Day
latestassertdate Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
<= Integer
maxlag
Posting
firsterrorp <- [Posting] -> Maybe Posting
forall a. [a] -> Maybe a
lastMay ([Posting] -> Maybe Posting) -> [Posting] -> Maybe Posting
forall a b. (a -> b) -> a -> b
$ (Posting -> Bool) -> [Posting] -> [Posting]
forall a. (a -> Bool) -> [a] -> [a]
dropWhileEnd (Day -> Bool
withinlimit(Day -> Bool) -> (Posting -> Day) -> Posting -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
.Posting -> Day
postingDate) [Posting]
afterlatestassertrps
let lag :: Integer
lag = Day -> Day -> Integer
diffDays (Posting -> Day
postingDate Posting
firsterrorp) Day
latestassertdate
let acct :: Text
acct = Posting -> Text
paccount Posting
firsterrorp
let (FilePath
f,Int
l,Maybe (Int, Maybe Int)
_mcols,Text
ex) = Posting -> (FilePath, Int, Maybe (Int, Maybe Int), Text)
makePostingAccountErrorExcerpt Posting
firsterrorp
let comm :: Text
comm =
case (Amount -> Text) -> [Amount] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Amount -> Text
acommodity ([Amount] -> [Text]) -> [Amount] -> [Text]
forall a b. (a -> b) -> a -> b
$ MixedAmount -> [Amount]
amounts (MixedAmount -> [Amount]) -> MixedAmount -> [Amount]
forall a b. (a -> b) -> a -> b
$ Posting -> MixedAmount
pamount Posting
firsterrorp of
[] -> Text
""
(Text
t:[Text]
_) | Text -> Int
T.length Text
t Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
1 -> Text
t
(Text
t:[Text]
_) -> Text
t Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" "
FilePath -> Maybe FilePath
forall a. a -> Maybe a
Just (FilePath -> Maybe FilePath) -> FilePath -> Maybe FilePath
forall a b. (a -> b) -> a -> b
$ FilePath -> FilePath
chomp (FilePath -> FilePath) -> FilePath -> FilePath
forall a b. (a -> b) -> a -> b
$ FilePath
-> FilePath
-> Int
-> Text
-> Integer
-> Text
-> Integer
-> Text
-> FilePath
-> Text
-> Text
-> Text
-> FilePath
forall r. PrintfType r => FilePath -> r
printf
([FilePath] -> FilePath
unlines [
FilePath
"%s:%d:",
FilePath
"%s\n",
FilePath
"The recentassertions check is enabled, so accounts with balance assertions must",
FilePath
"have a balance assertion within %d days of their latest posting.",
FilePath
"In account \"%s\", this posting is %d days later",
FilePath
"than the last balance assertion, which was on %s.",
FilePath
"",
FilePath
"Consider adding a more recent balance assertion for this account. Eg:",
FilePath
"",
FilePath
"%s\n %s %s0 = %s0 ; (adjust asserted amount)"
])
FilePath
f
Int
l
(Text -> Text
textChomp Text
ex)
Integer
maxlag
Text
acct
Integer
lag
(Day -> Text
showDate Day
latestassertdate)
(Day -> FilePath
forall a. Show a => a -> FilePath
show Day
today)
Text
acct
Text
comm
Text
comm