1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822
|
{-|
Most data types are defined here to avoid import cycles.
Here is an overview of the hledger data model:
> Journal -- a journal is read from one or more data files. It contains..
> [Transaction] -- journal transactions (aka entries), which have date, cleared status, code, description and..
> [Posting] -- multiple account postings, which have account name and amount
> [MarketPrice] -- historical market prices for commodities
>
> Ledger -- a ledger is derived from a journal, by applying a filter specification and doing some further processing. It contains..
> Journal -- a filtered copy of the original journal, containing only the transactions and postings we are interested in
> [Account] -- all accounts, in tree order beginning with a "root" account", with their balances and sub/parent accounts
For more detailed documentation on each type, see the corresponding modules.
-}
-- {-# LANGUAGE DeriveAnyClass #-} -- https://hackage.haskell.org/package/deepseq-1.4.4.0/docs/Control-DeepSeq.html#v:rnf
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE StrictData #-}
module Hledger.Data.Types (
module Hledger.Data.Types,
#if MIN_VERSION_time(1,11,0)
Year
#endif
)
where
import GHC.Generics (Generic)
import Control.DeepSeq (NFData(..))
import Data.Bifunctor (first)
import Data.Decimal (Decimal, DecimalRaw(..))
import Data.Default (Default(..))
import Data.Functor (($>))
import Data.List (intercalate, sortBy)
--XXX https://hackage.haskell.org/package/containers/docs/Data-Map.html
--Note: You should use Data.Map.Strict instead of this module if:
--You will eventually need all the values stored.
--The stored values don't represent large virtual data structures to be lazily computed.
import Data.Map qualified as M
import Data.Ord (comparing)
import Data.Semigroup (Min(..))
import Data.Text (Text)
import Data.Text qualified as T
import Data.Time.Calendar (Day)
import Data.Time.Clock.POSIX (POSIXTime)
import Data.Time.LocalTime (LocalTime)
import Data.Word (Word8)
import Text.Blaze (ToMarkup(..))
import Text.Megaparsec (SourcePos(SourcePos), mkPos)
import Hledger.Utils.Regex
-- synonyms for various date-related scalars
#if MIN_VERSION_time(1,11,0)
import Data.Time.Calendar (Year)
#else
type Year = Integer
#endif
type Month = Int -- 1-12
type Quarter = Int -- 1-4
type YearWeek = Int -- 1-52
type MonthWeek = Int -- 1-5
type YearDay = Int -- 1-366
type MonthDay = Int -- 1-31
type WeekDay = Int -- 1-7
-- | A possibly incomplete year-month-day date provided by the user, to be
-- interpreted as either a date or a date span depending on context. Missing
-- parts "on the left" will be filled from the provided reference date, e.g. if
-- the year and month are missing, the reference date's year and month are used.
-- Missing parts "on the right" are assumed, when interpreting as a date, to be
-- 1, (e.g. if the year and month are present but the day is missing, it means
-- first day of that month); or when interpreting as a date span, to be a
-- wildcard (so it would mean all days of that month). See the `smartdate`
-- parser for more examples.
--
-- Or, one of the standard periods and an offset relative to the reference date:
-- (last|this|next) (day|week|month|quarter|year), where "this" means the period
-- containing the reference date.
data SmartDate
= SmartCompleteDate Day
| SmartAssumeStart Year (Maybe Month) -- XXX improve these constructor names
| SmartFromReference (Maybe Month) MonthDay --
| SmartMonth Month
| SmartRelative Integer SmartInterval
deriving (Show)
data SmartInterval = Day | Week | Month | Quarter | Year deriving (Show)
data WhichDate = PrimaryDate | SecondaryDate deriving (Eq,Show)
-- | A date which is either exact or flexible.
-- Flexible dates are allowed to be adjusted in certain situations.
data EFDay = Exact Day | Flex Day deriving (Eq,Generic,Show)
-- EFDay's Ord instance treats them like ordinary dates, ignoring exact/flexible.
instance Ord EFDay where compare d1 d2 = compare (fromEFDay d1) (fromEFDay d2)
-- instance Ord EFDay where compare = maCompare
fromEFDay :: EFDay -> Day
fromEFDay (Exact d) = d
fromEFDay (Flex d) = d
modifyEFDay :: (Day -> Day) -> EFDay -> EFDay
modifyEFDay f (Exact d) = Exact $ f d
modifyEFDay f (Flex d) = Flex $ f d
-- | A possibly open-ended span of time, from an optional inclusive start date
-- to an optional exclusive end date. Each date can be either exact or flexible.
-- An "exact date span" is a Datepan with exact start and end dates.
data DateSpan = DateSpan (Maybe EFDay) (Maybe EFDay) deriving (Eq,Ord,Generic)
instance Default DateSpan where def = DateSpan Nothing Nothing
-- Some common report subperiods, both finite and open-ended.
-- A higher-level abstraction than DateSpan.
data Period =
DayPeriod Day
| WeekPeriod Day
| MonthPeriod Year Month
| QuarterPeriod Year Quarter
| YearPeriod Year
| PeriodBetween Day Day
| PeriodFrom Day
| PeriodTo Day
| PeriodAll
deriving (Eq,Ord,Show,Generic)
instance Default Period where def = PeriodAll
-- All the kinds of report interval allowed in a period expression
-- (to generate periodic reports or periodic transactions).
data Interval =
NoInterval
| Days Int
| Weeks Int
| Months Int
| Quarters Int
| Years Int
| NthWeekdayOfMonth Int Int -- n, weekday 1-7
| MonthDay Int -- 1-31
| MonthAndDay Int Int -- month 1-12, monthday 1-31
| DaysOfWeek [Int] -- [weekday 1-7]
deriving (Eq,Show,Ord,Generic)
instance Default Interval where def = NoInterval
type Payee = Text
type AccountName = Text
-- A specification indicating how to depth-limit
data DepthSpec = DepthSpec {
dsFlatDepth :: Maybe Int,
dsRegexpDepths :: [(Regexp, Int)]
} deriving (Eq,Show)
-- Semigroup instance consider all regular expressions, but take the minimum of the simple flat depths
instance Semigroup DepthSpec where
DepthSpec d1 l1 <> DepthSpec d2 l2 = DepthSpec (getMin <$> (Min <$> d1) <> (Min <$> d2)) (l1 ++ l2)
instance Monoid DepthSpec where
mempty = DepthSpec Nothing []
data AccountType =
Asset
| Liability
| Equity
| Revenue
| Expense
| Cash -- ^ a subtype of Asset - liquid assets to show in cashflow report
| Conversion -- ^ a subtype of Equity - account with which to balance commodity conversions
deriving (Eq,Ord,Generic)
instance Show AccountType where
show Asset = "A"
show Liability = "L"
show Equity = "E"
show Revenue = "R"
show Expense = "X"
show Cash = "C"
show Conversion = "V"
isBalanceSheetAccountType :: AccountType -> Bool
isBalanceSheetAccountType t = t `elem` [
Asset,
Liability,
Equity,
Cash,
Conversion
]
isIncomeStatementAccountType :: AccountType -> Bool
isIncomeStatementAccountType t = t `elem` [
Revenue,
Expense
]
-- | Check whether the first argument is a subtype of the second: either equal
-- or one of the defined subtypes.
isAccountSubtypeOf :: AccountType -> AccountType -> Bool
isAccountSubtypeOf Asset Asset = True
isAccountSubtypeOf Liability Liability = True
isAccountSubtypeOf Equity Equity = True
isAccountSubtypeOf Revenue Revenue = True
isAccountSubtypeOf Expense Expense = True
isAccountSubtypeOf Cash Cash = True
isAccountSubtypeOf Cash Asset = True
isAccountSubtypeOf Conversion Conversion = True
isAccountSubtypeOf Conversion Equity = True
isAccountSubtypeOf _ _ = False
-- not worth the trouble, letters defined in accountdirectivep for now
--instance Read AccountType
-- where
-- readsPrec _ ('A' : xs) = [(Asset, xs)]
-- readsPrec _ ('L' : xs) = [(Liability, xs)]
-- readsPrec _ ('E' : xs) = [(Equity, xs)]
-- readsPrec _ ('R' : xs) = [(Revenue, xs)]
-- readsPrec _ ('X' : xs) = [(Expense, xs)]
-- readsPrec _ _ = []
data AccountAlias = BasicAlias AccountName AccountName
| RegexAlias Regexp Replacement
deriving (Eq, Read, Show, Ord, Generic)
data Side = L | R deriving (Eq,Show,Read,Ord,Generic)
-- | One of the decimal marks we support: either period or comma.
type DecimalMark = Char
isDecimalMark :: Char -> Bool
isDecimalMark c = c == '.' || c == ','
-- | The basic numeric type used in amounts.
type Quantity = Decimal
-- The following is for hledger-web, and requires blaze-markup.
-- Doing it here avoids needing a matching flag on the hledger-web package.
instance ToMarkup Quantity
where
toMarkup = toMarkup . show
deriving instance Generic (DecimalRaw a)
-- | An amount's per-unit or total cost/selling price in another
-- commodity, as recorded in the journal entry eg with @ or @@.
-- "Cost", formerly AKA "transaction price". The amount is always positive.
data AmountCost = UnitCost !Amount | TotalCost !Amount
deriving (Eq,Ord,Generic,Show)
-- | Display styles for amounts - things which can be detected during parsing, such as
-- commodity side and spacing, digit group marks, decimal mark, number of decimal digits etc.
-- Every "Amount" has an AmountStyle.
-- After amounts are parsed from the input, for each "Commodity" a standard style is inferred
-- and then used when displaying amounts in that commodity.
-- Related to "AmountFormat" but higher level.
--
-- See also:
-- - hledger manual > Commodity styles
-- - hledger manual > Amounts
-- - hledger manual > Commodity display style
data AmountStyle = AmountStyle {
ascommodityside :: !Side, -- ^ show the symbol on the left or the right ?
ascommodityspaced :: !Bool, -- ^ show a space between symbol and quantity ?
asdigitgroups :: !(Maybe DigitGroupStyle), -- ^ show the integer part with these digit group marks, or not
asdecimalmark :: !(Maybe Char), -- ^ show this character (should be . or ,) as decimal mark, or use the default (.)
asprecision :: !AmountPrecision, -- ^ "display precision" - show this number of digits after the decimal point
asrounding :: !Rounding -- ^ "rounding strategy" - kept here for convenience, for now:
-- when displaying an amount, it is ignored,
-- but when applying this style to another amount, it determines
-- how hard we should try to adjust that amount's display precision.
} deriving (Eq,Ord,Read,Generic)
instance Show AmountStyle where
show AmountStyle{..} = unwords
[ "AmountStylePP"
, show ascommodityside
, show ascommodityspaced
, show asdigitgroups
, show asdecimalmark
, show asprecision
, show asrounding
]
-- | The "display precision" for a hledger amount, by which we mean
-- the number of decimal digits to display to the right of the decimal mark.
data AmountPrecision =
Precision !Word8 -- ^ show this many decimal digits (0..255)
| NaturalPrecision -- ^ show all significant decimal digits stored internally
deriving (Eq,Ord,Read,Show,Generic)
-- | "Rounding strategy" - how to apply an AmountStyle's display precision
-- to a posting amount (and its cost, if any).
-- Mainly used to customise print's output, with --round=none|soft|hard|all.
data Rounding =
NoRounding -- ^ keep display precisions unchanged in amt and cost
| SoftRounding -- ^ do soft rounding of amt and cost amounts (show more or fewer decimal zeros to approximate the target precision, but don't hide significant digits)
| HardRounding -- ^ do hard rounding of amt (use the exact target precision, possibly hiding significant digits), and soft rounding of cost
| AllRounding -- ^ do hard rounding of amt and cost
deriving (Eq,Ord,Read,Show,Generic)
-- | A style for displaying digit groups in the integer part of a
-- floating point number. It consists of the character used to
-- separate groups (comma or period, whichever is not used as decimal
-- point), and the size of each group, starting with the one nearest
-- the decimal point. The last group size is assumed to repeat. Eg,
-- comma between thousands is DigitGroups ',' [3].
data DigitGroupStyle = DigitGroups !Char ![Word8]
deriving (Eq,Ord,Read,Show,Generic)
type CommoditySymbol = Text
data Commodity = Commodity {
csymbol :: CommoditySymbol,
cformat :: Maybe AmountStyle
} deriving (Show,Eq,Generic) --,Ord)
data Amount = Amount {
acommodity :: !CommoditySymbol, -- commodity symbol, or special value "AUTO"
aquantity :: !Quantity, -- numeric quantity, or zero in case of "AUTO"
astyle :: !AmountStyle,
acost :: !(Maybe AmountCost) -- ^ the (fixed, transaction-specific) cost in another commodity of this amount, if any
} deriving (Eq,Ord,Generic,Show)
-- | Types with this class have one or more amounts,
-- which can have display styles applied to them.
class HasAmounts a where
styleAmounts :: M.Map CommoditySymbol AmountStyle -> a -> a
instance HasAmounts a =>
HasAmounts [a]
where styleAmounts styles = map (styleAmounts styles)
instance (HasAmounts a, HasAmounts b) =>
HasAmounts (a,b)
where styleAmounts styles (aa,bb) = (styleAmounts styles aa, styleAmounts styles bb)
instance HasAmounts a =>
HasAmounts (Maybe a)
where styleAmounts styles = fmap (styleAmounts styles)
newtype MixedAmount = Mixed (M.Map MixedAmountKey Amount) deriving (Generic,Show)
instance Eq MixedAmount where a == b = maCompare a b == EQ
instance Ord MixedAmount where compare = maCompare
-- | Compare two MixedAmounts, substituting 0 for the quantity of any missing
-- commodities in either.
maCompare :: MixedAmount -> MixedAmount -> Ordering
maCompare (Mixed a) (Mixed b) = go (M.toList a) (M.toList b)
where
go xss@((kx,x):xs) yss@((ky,y):ys) = case compare kx ky of
EQ -> compareQuantities (Just x) (Just y) <> go xs ys
LT -> compareQuantities (Just x) Nothing <> go xs yss
GT -> compareQuantities Nothing (Just y) <> go xss ys
go ((_,x):xs) [] = compareQuantities (Just x) Nothing <> go xs []
go [] ((_,y):ys) = compareQuantities Nothing (Just y) <> go [] ys
go [] [] = EQ
compareQuantities = comparing (maybe 0 aquantity) <> comparing (maybe 0 totalcost)
totalcost x = case acost x of
Just (TotalCost p) -> aquantity p
_ -> 0
-- | Stores the CommoditySymbol of the Amount, along with the CommoditySymbol of
-- the cost, and its unit cost if being used.
data MixedAmountKey
= MixedAmountKeyNoCost !CommoditySymbol
| MixedAmountKeyTotalCost !CommoditySymbol !CommoditySymbol
| MixedAmountKeyUnitCost !CommoditySymbol !CommoditySymbol !Quantity
deriving (Eq,Generic,Show)
-- | We don't auto-derive the Ord instance because it would give an undesired ordering.
-- We want the keys to be sorted lexicographically:
-- (1) By the primary commodity of the amount.
-- (2) By the commodity of the cost, with no cost being first.
-- (3) By the unit cost, from most negative to most positive, with total costs
-- before unit costs.
-- For example, we would like the ordering to give
-- MixedAmountKeyNoCost "X" < MixedAmountKeyTotalCost "X" "Z" < MixedAmountKeyNoCost "Y"
instance Ord MixedAmountKey where
compare = comparing commodity <> comparing pCommodity <> comparing pCost
where
commodity (MixedAmountKeyNoCost c) = c
commodity (MixedAmountKeyTotalCost c _) = c
commodity (MixedAmountKeyUnitCost c _ _) = c
pCommodity (MixedAmountKeyNoCost _) = Nothing
pCommodity (MixedAmountKeyTotalCost _ pc) = Just pc
pCommodity (MixedAmountKeyUnitCost _ pc _) = Just pc
pCost (MixedAmountKeyNoCost _) = Nothing
pCost (MixedAmountKeyTotalCost _ _) = Nothing
pCost (MixedAmountKeyUnitCost _ _ q) = Just q
data PostingType = RegularPosting | VirtualPosting | BalancedVirtualPosting
deriving (Eq,Show,Generic)
type TagName = Text
type TagValue = Text
type Tag = (TagName, TagValue) -- ^ A tag name and (possibly empty) value.
type HiddenTag = Tag -- ^ A tag whose name begins with _.
type DateTag = (TagName, Day)
-- | Add the _ prefix to a normal visible tag's name, making it a hidden tag.
toHiddenTag :: Tag -> HiddenTag
toHiddenTag = first toHiddenTagName
-- | Drop the _ prefix from a hidden tag's name, making it a normal visible tag.
toVisibleTag :: HiddenTag -> Tag
toVisibleTag = first toVisibleTagName
-- | Does this tag name begin with the hidden tag prefix (_) ?
isHiddenTagName :: TagName -> Bool
isHiddenTagName t =
case T.uncons t of
Just ('_',_) -> True
_ -> False
-- | Add the _ prefix to a normal visible tag's name, making it a hidden tag.
toHiddenTagName :: TagName -> TagName
toHiddenTagName = T.cons '_'
-- | Drop the _ prefix from a hidden tag's name, making it a normal visible tag.
toVisibleTagName :: TagName -> TagName
toVisibleTagName = T.drop 1
-- | The status of a transaction or posting, recorded with a status mark
-- (nothing, !, or *). What these mean is ultimately user defined.
data Status = Unmarked | Pending | Cleared
deriving (Eq,Ord,Bounded,Enum,Generic)
instance Show Status where -- custom show.. bad idea.. don't do it..
show Unmarked = ""
show Pending = "!"
show Cleared = "*"
nullsourcepos :: SourcePos
nullsourcepos = SourcePos "" (mkPos 1) (mkPos 1)
nullsourcepospair :: (SourcePos, SourcePos)
nullsourcepospair = (SourcePos "" (mkPos 1) (mkPos 1), SourcePos "" (mkPos 2) (mkPos 1))
-- | A balance assertion is a declaration about an account's expected balance
-- at a certain point (posting date and parse order). They provide additional
-- error checking and readability to a journal file.
--
-- A balance assignments is an instruction to hledger to adjust an
-- account's balance to a certain amount at a certain point.
--
-- The 'BalanceAssertion' type is used for representing both of these.
--
-- hledger supports multiple kinds of balance assertions/assignments,
-- which differ in whether they refer to a single commodity or all commodities,
-- and the (subaccount-)inclusive or exclusive account balance.
--
data BalanceAssertion = BalanceAssertion {
baamount :: Amount, -- ^ the expected balance in a particular commodity
batotal :: Bool, -- ^ disallow additional non-asserted commodities ?
bainclusive :: Bool, -- ^ include subaccounts when calculating the actual balance ?
baposition :: SourcePos -- ^ the assertion's file position, for error reporting
} deriving (Eq,Generic,Show)
data Posting = Posting {
pdate :: Maybe Day, -- ^ this posting's date, if different from the transaction's
pdate2 :: Maybe Day, -- ^ this posting's secondary date, if different from the transaction's
pstatus :: Status,
paccount :: AccountName,
pamount :: MixedAmount,
pcomment :: Text, -- ^ this posting's comment lines, as a single non-indented multi-line string
ptype :: PostingType,
ptags :: [Tag], -- ^ tag names and values, extracted from the posting comment
-- and (after finalisation) the posting account's directive if any
pbalanceassertion :: Maybe BalanceAssertion, -- ^ an expected balance in the account after this posting,
-- in a single commodity, excluding subaccounts.
ptransaction :: Maybe Transaction, -- ^ this posting's parent transaction (co-recursive types).
-- Tying this knot gets tedious, Maybe makes it easier/optional.
poriginal :: Maybe Posting -- ^ When this posting has been transformed in some way
-- (eg its amount or cost was inferred, or the account name was
-- changed by a pivot or budget report), this references the original
-- untransformed posting (which will have Nothing in this field).
} deriving (Generic)
-- The equality test for postings ignores the parent transaction's
-- identity, to avoid recurring ad infinitum.
-- XXX could check that it's Just or Nothing.
instance Eq Posting where
(==) (Posting a1 b1 c1 d1 e1 f1 g1 h1 i1 _ _) (Posting a2 b2 c2 d2 e2 f2 g2 h2 i2 _ _) = a1==a2 && b1==b2 && c1==c2 && d1==d2 && e1==e2 && f1==f2 && g1==g2 && h1==h2 && i1==i2
-- | Posting's show instance elides the parent transaction so as not to recurse forever.
instance Show Posting where
show Posting{..} = "PostingPP {" ++ intercalate ", " [
"pdate=" ++ show (show pdate)
,"pdate2=" ++ show (show pdate2)
,"pstatus=" ++ show (show pstatus)
,"paccount=" ++ show paccount
,"pamount=" ++ show pamount
,"pcomment=" ++ show pcomment
,"ptype=" ++ show ptype
,"ptags=" ++ show ptags
,"pbalanceassertion=" ++ show pbalanceassertion
,"ptransaction=" ++ show (ptransaction $> "txn")
,"poriginal=" ++ show poriginal
] ++ "}"
data Transaction = Transaction {
tindex :: Integer, -- ^ this transaction's 1-based position in the transaction stream, or 0 when not available
tprecedingcomment :: Text, -- ^ any comment lines immediately preceding this transaction
tsourcepos :: (SourcePos, SourcePos), -- ^ the file position where the date starts, and where the last posting ends
tdate :: Day,
tdate2 :: Maybe Day,
tstatus :: Status,
tcode :: Text,
tdescription :: Text,
tcomment :: Text, -- ^ this transaction's comment lines, as a single non-indented multi-line string
ttags :: [Tag], -- ^ tag names and values, extracted from the comment
tpostings :: [Posting] -- ^ this transaction's postings
} deriving (Eq,Generic,Show)
-- | A transaction modifier rule. This has a query which matches postings
-- in the journal, and a list of transformations to apply to those
-- postings or their transactions. Currently there is one kind of transformation:
-- the TMPostingRule, which adds a posting ("auto posting") to the transaction,
-- optionally setting its amount to the matched posting's amount multiplied by a constant.
data TransactionModifier = TransactionModifier {
tmquerytxt :: Text,
tmpostingrules :: [TMPostingRule]
} deriving (Eq,Generic,Show)
nulltransactionmodifier = TransactionModifier{
tmquerytxt = ""
,tmpostingrules = []
}
-- | A transaction modifier transformation, which adds an extra posting
-- to the matched posting's transaction.
-- Can be like a regular posting, or can have the tmprIsMultiplier flag set,
-- indicating that it's a multiplier for the matched posting's amount.
data TMPostingRule = TMPostingRule
{ tmprPosting :: Posting
, tmprIsMultiplier :: Bool
} deriving (Eq,Generic,Show)
-- | A periodic transaction rule, describing a transaction that recurs.
data PeriodicTransaction = PeriodicTransaction {
ptperiodexpr :: Text, -- ^ the period expression as written
ptinterval :: Interval, -- ^ the interval at which this transaction recurs
ptspan :: DateSpan, -- ^ the (possibly unbounded) period during which this transaction recurs. Contains a whole number of intervals.
--
ptsourcepos :: (SourcePos, SourcePos), -- ^ the file position where the period expression starts, and where the last posting ends
ptstatus :: Status, -- ^ some of Transaction's fields
ptcode :: Text,
ptdescription :: Text,
ptcomment :: Text,
pttags :: [Tag],
ptpostings :: [Posting]
} deriving (Eq,Generic) -- , Show in PeriodicTransaction.hs
nullperiodictransaction = PeriodicTransaction{
ptperiodexpr = ""
,ptinterval = def
,ptspan = def
,ptsourcepos = (SourcePos "" (mkPos 1) (mkPos 1), SourcePos "" (mkPos 1) (mkPos 1))
,ptstatus = Unmarked
,ptcode = ""
,ptdescription = ""
,ptcomment = ""
,pttags = []
,ptpostings = []
}
data TimeclockCode = SetBalance | SetRequiredHours | In | Out | FinalOut deriving (Eq,Ord,Generic)
data TimeclockEntry = TimeclockEntry {
tlsourcepos :: SourcePos,
tlcode :: TimeclockCode,
tldatetime :: LocalTime,
tlaccount :: AccountName,
tldescription :: Text,
tlcomment :: Text,
tltags :: [Tag]
} deriving (Eq,Ord,Generic)
-- | A market price declaration made by the journal format's P directive.
-- It declares two things: a historical exchange rate between two commodities,
-- and an amount display style for the second commodity.
data PriceDirective = PriceDirective {
pdsourcepos :: SourcePos
,pddate :: Day
,pdcommodity :: CommoditySymbol
,pdamount :: Amount
} deriving (Eq,Ord,Generic,Show)
-- | A historical market price (exchange rate) from one commodity to another.
-- A more concise form of a PriceDirective, without the amount display info.
data MarketPrice = MarketPrice {
mpdate :: Day -- ^ Date on which this price becomes effective.
,mpfrom :: CommoditySymbol -- ^ The commodity being converted from.
,mpto :: CommoditySymbol -- ^ The commodity being converted to.
,mprate :: Quantity -- ^ One unit of the "from" commodity is worth this quantity of the "to" commodity.
} deriving (Eq,Ord,Generic, Show)
showMarketPrice MarketPrice{..} = unwords [show mpdate, T.unpack mpfrom <> ">" <> T.unpack mpto, show mprate]
showMarketPrices = intercalate "\n" . map ((' ':).showMarketPrice) . sortBy (comparing mpdate)
-- additional valuation-related types in Valuation.hs
-- | A journal, containing general ledger transactions; also directives and various other things.
-- This is hledger's main data model.
--
-- During parsing, it is used as the type alias "ParsedJournal".
-- The jparse* fields are mainly used during parsing and included here for convenience.
-- The list fields described as "in parse order" are usually reversed for efficiency during parsing.
-- After parsing, "journalFinalise" converts ParsedJournal to a finalised "Journal",
-- which has all lists correctly ordered, and much data inference and validation applied.
--
data Journal = Journal {
-- parsing-related state
jparsedefaultyear :: Maybe Year -- ^ the current default year, specified by the most recent Y directive (or current date)
,jparsedefaultcommodity :: Maybe (CommoditySymbol,AmountStyle) -- ^ the current default commodity and its format, specified by the most recent D directive
,jparsedecimalmark :: Maybe DecimalMark -- ^ the character to always parse as decimal point, if set by CsvReader's decimal-mark (or a future journal directive)
,jparseparentaccounts :: [AccountName] -- ^ the current stack of parent account names, specified by apply account directives
,jparsealiases :: [AccountAlias] -- ^ the current account name aliases in effect, specified by alias directives (& options ?)
-- ,jparsetransactioncount :: Integer -- ^ the current count of transactions parsed so far (only journal format txns, currently)
,jparsetimeclockentries :: [TimeclockEntry] -- ^ timeclock sessions which have not been clocked out
,jincludefilestack :: [FilePath]
-- principal data
,jdeclaredpayees :: [(Payee,PayeeDeclarationInfo)] -- ^ Payees declared by payee directives, in parse order.
,jdeclaredtags :: [(TagName,TagDeclarationInfo)] -- ^ Tags declared by tag directives, in parse order.
,jdeclaredaccounts :: [(AccountName,AccountDeclarationInfo)] -- ^ Accounts declared by account directives, in parse order.
,jdeclaredaccounttags :: M.Map AccountName [Tag] -- ^ Accounts which were declared with tags, and those tags.
,jdeclaredaccounttypes :: M.Map AccountType [AccountName] -- ^ Accounts which were declared with a type: tag, grouped by the type.
,jaccounttypes :: M.Map AccountName AccountType -- ^ All the account types known, from account declarations or account names or parent accounts.
,jdeclaredcommodities :: M.Map CommoditySymbol Commodity -- ^ Commodities (and their display styles) declared by commodity directives, in parse order.
,jinferredcommoditystyles :: M.Map CommoditySymbol AmountStyle -- ^ Commodity display styles inferred from amounts in the journal.
,jglobalcommoditystyles :: M.Map CommoditySymbol AmountStyle -- ^ Commodity display styles declared by command line options (sometimes augmented, see the import command).
,jpricedirectives :: [PriceDirective] -- ^ P (market price) directives in the journal, in parse order.
,jinferredmarketprices :: [MarketPrice] -- ^ Market prices inferred from transactions in the journal, in parse order.
,jtxnmodifiers :: [TransactionModifier] -- ^ Auto posting rules declared in the journal.
,jperiodictxns :: [PeriodicTransaction] -- ^ Periodic transaction rules declared in the journal.
,jtxns :: [Transaction] -- ^ Transactions recorded in the journal. The important bit.
,jfinalcommentlines :: Text -- ^ any final trailing comments in the (main) journal file
,jfiles :: [(FilePath, Text)] -- ^ the file path and raw text of the main and
-- any included journal files. The main file is first,
-- followed by any included files in the order encountered.
-- TODO: FilePath is a sloppy type here, don't assume it's a
-- real file; values like "" or "-" can be seen
,jlastreadtime :: POSIXTime -- ^ when this journal was last read from its file(s)
-- NOTE: after adding new fields, eg involving account names, consider updating
-- the Anon instance in Hleger.Cli.Anon
} deriving (Eq, Generic)
-- | A journal in the process of being parsed, not yet finalised.
-- The data is partial, and list fields are in reverse order.
type ParsedJournal = Journal
-- | One of the standard *-separated value file types known by hledger,
data SepFormat
= Csv -- comma-separated
| Tsv -- tab-separated
| Ssv -- semicolon-separated
deriving (Eq, Ord)
-- XXX A little confusion, this is also used to name readers in splitReaderPrefix.
-- readers, input formats, and output formats overlap but are distinct concepts.
-- | The id of a data format understood by hledger, eg @journal@ or @csv@.
-- The --output-format option selects one of these for output.
data StorageFormat
= Rules
| Journal'
| Ledger'
| Timeclock
| Timedot
| Sep SepFormat
deriving (Eq, Ord)
instance Show SepFormat where
show Csv = "csv"
show Ssv = "ssv"
show Tsv = "tsv"
instance Show StorageFormat where
show Rules = "rules"
show Journal' = "journal"
show Ledger' = "ledger"
show Timeclock = "timeclock"
show Timedot = "timedot"
show (Sep Csv) = "csv"
show (Sep Ssv) = "ssv"
show (Sep Tsv) = "tsv"
-- | Extra information found in a payee directive.
data PayeeDeclarationInfo = PayeeDeclarationInfo {
pdicomment :: Text -- ^ any comment lines following the payee directive
,pditags :: [Tag] -- ^ tags extracted from the comment, if any
} deriving (Eq,Show,Generic)
nullpayeedeclarationinfo = PayeeDeclarationInfo {
pdicomment = ""
,pditags = []
}
-- | Extra information found in a tag directive.
newtype TagDeclarationInfo = TagDeclarationInfo {
tdicomment :: Text -- ^ any comment lines following the tag directive. No tags allowed here.
} deriving (Eq,Show,Generic)
nulltagdeclarationinfo = TagDeclarationInfo {
tdicomment = ""
}
-- | Extra information about an account that can be derived from
-- its account directive (and the other account directives).
data AccountDeclarationInfo = AccountDeclarationInfo {
adicomment :: Text -- ^ any comment lines following an account directive for this account
,aditags :: [Tag] -- ^ tags extracted from the account comment, if any
,adideclarationorder :: Int -- ^ the order in which this account was declared,
-- relative to other account declarations, during parsing (1..)
,adisourcepos :: SourcePos -- ^ source file and position
} deriving (Eq,Show,Generic)
nullaccountdeclarationinfo = AccountDeclarationInfo {
adicomment = ""
,aditags = []
,adideclarationorder = 0
,adisourcepos = SourcePos "" (mkPos 1) (mkPos 1)
}
-- | An account within a hierarchy, with references to its parent
-- and subaccounts if any, and with per-report-period data of type 'a'.
-- Only the name is required; the other fields may or may not be present.
data Account a = Account {
aname :: AccountName -- ^ full name
,adeclarationinfo :: Maybe AccountDeclarationInfo -- ^ optional extra info from account directives
-- relationships in the tree
,asubs :: [Account a] -- ^ subaccounts
,aparent :: Maybe (Account a) -- ^ parent account
,aboring :: Bool -- ^ used in some reports to indicate elidable accounts
,adata :: PeriodData a -- ^ associated data per report period
} deriving (Generic, Functor)
-- | A general container for storing data values associated with zero or more
-- contiguous report (sub)periods, and with the (open ended) pre-report period.
-- The report periods are typically all the same length, but need not be.
--
-- Report periods are represented only by their start dates, used as the keys of a Map.
data PeriodData a = PeriodData {
pdpre :: a -- ^ data for the period before the report
,pdperiods :: M.Map Day a -- ^ data for each period within the report
} deriving (Eq, Ord, Functor, Generic)
-- | Data that's useful in "balance" reports:
-- subaccount-exclusive and -inclusive amounts,
-- typically representing either a balance change or an end balance;
-- and a count of postings.
data BalanceData = BalanceData {
bdexcludingsubs :: MixedAmount -- ^ balance data excluding subaccounts
,bdincludingsubs :: MixedAmount -- ^ balance data including subaccounts
,bdnumpostings :: Int -- ^ the number of postings
} deriving (Eq, Generic)
-- | Whether an account's balance is normally a positive number (in
-- accounting terms, a debit balance) or a negative number (credit balance).
-- Assets and expenses are normally positive (debit), while liabilities, equity
-- and income are normally negative (credit).
-- https://en.wikipedia.org/wiki/Normal_balance
data NormalSign = NormallyPositive | NormallyNegative deriving (Show, Eq)
-- | A Ledger has the journal it derives from, and the accounts
-- derived from that. Accounts are accessible both list-wise and
-- tree-wise, since each one knows its parent and subs; the first
-- account is the root of the tree and always exists.
data Ledger = Ledger {
ljournal :: Journal
,laccounts :: [Account BalanceData]
} deriving (Generic)
instance NFData AccountAlias
instance NFData AccountDeclarationInfo
instance NFData AccountType
instance NFData Amount
instance NFData AmountCost
instance NFData AmountPrecision
instance NFData AmountStyle
instance NFData BalanceAssertion
instance NFData Commodity
instance NFData DateSpan
instance NFData DigitGroupStyle
instance NFData EFDay
instance NFData Interval
instance NFData Journal
instance NFData MarketPrice
instance NFData MixedAmount
instance NFData MixedAmountKey
instance NFData Rounding
instance NFData PayeeDeclarationInfo
instance NFData PeriodicTransaction
instance NFData PostingType
instance NFData PriceDirective
instance NFData Side
instance NFData Status
instance NFData TagDeclarationInfo
instance NFData TimeclockCode
instance NFData TimeclockEntry
instance NFData TMPostingRule
instance NFData Transaction
instance NFData TransactionModifier
instance NFData Posting where
-- Do not call rnf on the parent transaction to avoid recursive loops
rnf (Posting d d2 s n a c t ta b mt op) =
rnf d `seq` rnf d2 `seq` rnf s `seq` rnf n `seq` rnf a `seq` rnf c `seq` rnf t `seq` rnf ta `seq` rnf b `seq` mt `seq` rnf op `seq` ()
|