packages feed

haspara 0.0.0.3 → 0.0.0.4

raw patch · 12 files changed

+1012/−297 lines, 12 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Haspara.Accounting: Account :: !AccountKind -> !o -> Account o
- Haspara.Accounting: AccountKindAsset :: AccountKind
- Haspara.Accounting: AccountKindEquity :: AccountKind
- Haspara.Accounting: AccountKindExpense :: AccountKind
- Haspara.Accounting: AccountKindLiability :: AccountKind
- Haspara.Accounting: AccountKindRevenue :: AccountKind
- Haspara.Accounting: EntryCredit :: Day -> o -> UnsignedQuantity s -> Entry o (s :: Nat)
- Haspara.Accounting: EntryDebit :: Day -> o -> UnsignedQuantity s -> Entry o (s :: Nat)
- Haspara.Accounting: EventDecrement :: Day -> o -> UnsignedQuantity s -> Event o (s :: Nat)
- Haspara.Accounting: EventIncrement :: Day -> o -> UnsignedQuantity s -> Event o (s :: Nat)
- Haspara.Accounting: Ledger :: !Account a -> !Quantity s -> !Quantity s -> ![LedgerItem o s] -> Ledger a o (s :: Nat)
- Haspara.Accounting: LedgerItem :: !Entry o s -> !Quantity s -> LedgerItem o (s :: Nat)
- Haspara.Accounting: Posting :: NonEmpty (Event o s, Account a) -> Posting a o (s :: Nat)
- Haspara.Accounting: [accountKind] :: Account o -> !AccountKind
- Haspara.Accounting: [accountObject] :: Account o -> !o
- Haspara.Accounting: [ledgerAccount] :: Ledger a o (s :: Nat) -> !Account a
- Haspara.Accounting: [ledgerClosing] :: Ledger a o (s :: Nat) -> !Quantity s
- Haspara.Accounting: [ledgerItemBalance] :: LedgerItem o (s :: Nat) -> !Quantity s
- Haspara.Accounting: [ledgerItemEntry] :: LedgerItem o (s :: Nat) -> !Entry o s
- Haspara.Accounting: [ledgerOpening] :: Ledger a o (s :: Nat) -> !Quantity s
- Haspara.Accounting: [ledgerRunning] :: Ledger a o (s :: Nat) -> ![LedgerItem o s]
- Haspara.Accounting: accountKindText :: AccountKind -> Text
- Haspara.Accounting: addEntry :: KnownNat s => Ledger a o s -> Entry o s -> Ledger a o s
- Haspara.Accounting: buildEntry :: KnownNat s => Event o s -> AccountKind -> Entry o s
- Haspara.Accounting: data Account o
- Haspara.Accounting: data AccountKind
- Haspara.Accounting: data Entry o (s :: Nat)
- Haspara.Accounting: data Event o (s :: Nat)
- Haspara.Accounting: data Ledger a o (s :: Nat)
- Haspara.Accounting: data LedgerItem o (s :: Nat)
- Haspara.Accounting: entryCredit :: KnownNat s => Entry o s -> Maybe (UnsignedQuantity s)
- Haspara.Accounting: entryDate :: KnownNat s => Entry o s -> Day
- Haspara.Accounting: entryDebit :: KnownNat s => Entry o s -> Maybe (UnsignedQuantity s)
- Haspara.Accounting: entryObject :: KnownNat s => Entry o s -> o
- Haspara.Accounting: entryQuantity :: KnownNat s => Entry o s -> Quantity s
- Haspara.Accounting: eventDate :: KnownNat s => Event o s -> Day
- Haspara.Accounting: eventObject :: KnownNat s => Event o s -> o
- Haspara.Accounting: mkEvent :: MonadError String m => KnownNat s => Day -> o -> Quantity s -> m (Event o s)
- Haspara.Accounting: mkLedger :: KnownNat s => Account a -> Quantity s -> [Entry o s] -> Ledger a o s
- Haspara.Accounting: negateEvent :: KnownNat s => Event o s -> Event o s
- Haspara.Accounting: newtype Posting a o (s :: Nat)
- Haspara.Accounting: post :: KnownNat s => Posting a o s -> [(Account a, Entry o s)]
- Haspara.Accounting: postingEvents :: KnownNat s => Posting a o s -> [o]
- Haspara.Accounting.Event: EventDecrement :: Day -> o -> UnsignedQuantity s -> Event o (s :: Nat)
- Haspara.Accounting.Event: EventIncrement :: Day -> o -> UnsignedQuantity s -> Event o (s :: Nat)
- Haspara.Accounting.Event: data Event o (s :: Nat)
- Haspara.Accounting.Event: eventDate :: KnownNat s => Event o s -> Day
- Haspara.Accounting.Event: eventObject :: KnownNat s => Event o s -> o
- Haspara.Accounting.Event: instance (Data.Aeson.Types.FromJSON.FromJSON o, GHC.TypeNats.KnownNat s) => Data.Aeson.Types.FromJSON.FromJSON (Haspara.Accounting.Event.Event o s)
- Haspara.Accounting.Event: instance (Data.Aeson.Types.ToJSON.ToJSON o, GHC.TypeNats.KnownNat s) => Data.Aeson.Types.ToJSON.ToJSON (Haspara.Accounting.Event.Event o s)
- Haspara.Accounting.Event: instance (GHC.Show.Show o, GHC.TypeNats.KnownNat s) => GHC.Show.Show (Haspara.Accounting.Event.Event o s)
- Haspara.Accounting.Event: instance GHC.Classes.Eq o => GHC.Classes.Eq (Haspara.Accounting.Event.Event o s)
- Haspara.Accounting.Event: instance GHC.Classes.Ord o => GHC.Classes.Ord (Haspara.Accounting.Event.Event o s)
- Haspara.Accounting.Event: mkEvent :: MonadError String m => KnownNat s => Day -> o -> Quantity s -> m (Event o s)
- Haspara.Accounting.Event: negateEvent :: KnownNat s => Event o s -> Event o s
- Haspara.Accounting.Ledger: EntryCredit :: Day -> o -> UnsignedQuantity s -> Entry o (s :: Nat)
- Haspara.Accounting.Ledger: EntryDebit :: Day -> o -> UnsignedQuantity s -> Entry o (s :: Nat)
- Haspara.Accounting.Ledger: LedgerItem :: !Entry o s -> !Quantity s -> LedgerItem o (s :: Nat)
- Haspara.Accounting.Ledger: Posting :: NonEmpty (Event o s, Account a) -> Posting a o (s :: Nat)
- Haspara.Accounting.Ledger: [ledgerClosing] :: Ledger a o (s :: Nat) -> !Quantity s
- Haspara.Accounting.Ledger: [ledgerItemBalance] :: LedgerItem o (s :: Nat) -> !Quantity s
- Haspara.Accounting.Ledger: [ledgerItemEntry] :: LedgerItem o (s :: Nat) -> !Entry o s
- Haspara.Accounting.Ledger: addEntry :: KnownNat s => Ledger a o s -> Entry o s -> Ledger a o s
- Haspara.Accounting.Ledger: buildEntry :: KnownNat s => Event o s -> AccountKind -> Entry o s
- Haspara.Accounting.Ledger: data Entry o (s :: Nat)
- Haspara.Accounting.Ledger: data LedgerItem o (s :: Nat)
- Haspara.Accounting.Ledger: entryCredit :: KnownNat s => Entry o s -> Maybe (UnsignedQuantity s)
- Haspara.Accounting.Ledger: entryDate :: KnownNat s => Entry o s -> Day
- Haspara.Accounting.Ledger: entryDebit :: KnownNat s => Entry o s -> Maybe (UnsignedQuantity s)
- Haspara.Accounting.Ledger: entryObject :: KnownNat s => Entry o s -> o
- Haspara.Accounting.Ledger: entryQuantity :: KnownNat s => Entry o s -> Quantity s
- Haspara.Accounting.Ledger: instance (Data.Aeson.Types.FromJSON.FromJSON a, Data.Aeson.Types.FromJSON.FromJSON o, GHC.TypeNats.KnownNat s) => Data.Aeson.Types.FromJSON.FromJSON (Haspara.Accounting.Ledger.Ledger a o s)
- Haspara.Accounting.Ledger: instance (Data.Aeson.Types.FromJSON.FromJSON a, Data.Aeson.Types.FromJSON.FromJSON o, GHC.TypeNats.KnownNat s) => Data.Aeson.Types.FromJSON.FromJSON (Haspara.Accounting.Ledger.Posting a o s)
- Haspara.Accounting.Ledger: instance (Data.Aeson.Types.FromJSON.FromJSON o, GHC.TypeNats.KnownNat s) => Data.Aeson.Types.FromJSON.FromJSON (Haspara.Accounting.Ledger.Entry o s)
- Haspara.Accounting.Ledger: instance (Data.Aeson.Types.FromJSON.FromJSON o, GHC.TypeNats.KnownNat s) => Data.Aeson.Types.FromJSON.FromJSON (Haspara.Accounting.Ledger.LedgerItem o s)
- Haspara.Accounting.Ledger: instance (Data.Aeson.Types.ToJSON.ToJSON a, Data.Aeson.Types.ToJSON.ToJSON o, GHC.TypeNats.KnownNat s) => Data.Aeson.Types.ToJSON.ToJSON (Haspara.Accounting.Ledger.Ledger a o s)
- Haspara.Accounting.Ledger: instance (Data.Aeson.Types.ToJSON.ToJSON a, Data.Aeson.Types.ToJSON.ToJSON o, GHC.TypeNats.KnownNat s) => Data.Aeson.Types.ToJSON.ToJSON (Haspara.Accounting.Ledger.Posting a o s)
- Haspara.Accounting.Ledger: instance (Data.Aeson.Types.ToJSON.ToJSON o, GHC.TypeNats.KnownNat s) => Data.Aeson.Types.ToJSON.ToJSON (Haspara.Accounting.Ledger.Entry o s)
- Haspara.Accounting.Ledger: instance (Data.Aeson.Types.ToJSON.ToJSON o, GHC.TypeNats.KnownNat s) => Data.Aeson.Types.ToJSON.ToJSON (Haspara.Accounting.Ledger.LedgerItem o s)
- Haspara.Accounting.Ledger: instance (GHC.Classes.Eq a, GHC.Classes.Eq o) => GHC.Classes.Eq (Haspara.Accounting.Ledger.Ledger a o s)
- Haspara.Accounting.Ledger: instance (GHC.Classes.Eq o, GHC.Classes.Eq a) => GHC.Classes.Eq (Haspara.Accounting.Ledger.Posting a o s)
- Haspara.Accounting.Ledger: instance (GHC.Classes.Ord a, GHC.Classes.Ord o) => GHC.Classes.Ord (Haspara.Accounting.Ledger.Ledger a o s)
- Haspara.Accounting.Ledger: instance (GHC.Classes.Ord o, GHC.Classes.Ord a) => GHC.Classes.Ord (Haspara.Accounting.Ledger.Posting a o s)
- Haspara.Accounting.Ledger: instance (GHC.Show.Show o, GHC.TypeNats.KnownNat s) => GHC.Show.Show (Haspara.Accounting.Ledger.Entry o s)
- Haspara.Accounting.Ledger: instance (GHC.Show.Show o, GHC.TypeNats.KnownNat s) => GHC.Show.Show (Haspara.Accounting.Ledger.LedgerItem o s)
- Haspara.Accounting.Ledger: instance (GHC.TypeNats.KnownNat s, GHC.Show.Show a, GHC.Show.Show o) => GHC.Show.Show (Haspara.Accounting.Ledger.Ledger a o s)
- Haspara.Accounting.Ledger: instance (GHC.TypeNats.KnownNat s, GHC.Show.Show o, GHC.Show.Show a) => GHC.Show.Show (Haspara.Accounting.Ledger.Posting a o s)
- Haspara.Accounting.Ledger: instance GHC.Classes.Eq o => GHC.Classes.Eq (Haspara.Accounting.Ledger.Entry o s)
- Haspara.Accounting.Ledger: instance GHC.Classes.Eq o => GHC.Classes.Eq (Haspara.Accounting.Ledger.LedgerItem o s)
- Haspara.Accounting.Ledger: instance GHC.Classes.Ord o => GHC.Classes.Ord (Haspara.Accounting.Ledger.Entry o s)
- Haspara.Accounting.Ledger: instance GHC.Classes.Ord o => GHC.Classes.Ord (Haspara.Accounting.Ledger.LedgerItem o s)
- Haspara.Accounting.Ledger: instance GHC.Generics.Generic (Haspara.Accounting.Ledger.Ledger a o s)
- Haspara.Accounting.Ledger: instance GHC.Generics.Generic (Haspara.Accounting.Ledger.LedgerItem o s)
- Haspara.Accounting.Ledger: instance GHC.Generics.Generic (Haspara.Accounting.Ledger.Posting a o s)
- Haspara.Accounting.Ledger: mkLedger :: KnownNat s => Account a -> Quantity s -> [Entry o s] -> Ledger a o s
- Haspara.Accounting.Ledger: newtype Posting a o (s :: Nat)
- Haspara.Accounting.Ledger: post :: KnownNat s => Posting a o s -> [(Account a, Entry o s)]
- Haspara.Accounting.Ledger: postingEvents :: KnownNat s => Posting a o s -> [o]
+ Haspara.Accounting.Account: instance GHC.Enum.Bounded Haspara.Accounting.Account.AccountKind
+ Haspara.Accounting.Amount: Amount :: !Side -> !UnsignedQuantity precision -> Amount (precision :: Nat)
+ Haspara.Accounting.Amount: [amountSide] :: Amount (precision :: Nat) -> !Side
+ Haspara.Accounting.Amount: [amountValue] :: Amount (precision :: Nat) -> !UnsignedQuantity precision
+ Haspara.Accounting.Amount: amountCredit :: KnownNat precision => Amount precision -> Maybe (UnsignedQuantity precision)
+ Haspara.Accounting.Amount: amountDebit :: KnownNat precision => Amount precision -> Maybe (UnsignedQuantity precision)
+ Haspara.Accounting.Amount: amountFromQuantity :: KnownNat precision => AccountKind -> Quantity precision -> Amount precision
+ Haspara.Accounting.Amount: amountFromValue :: KnownNat precision => AccountKind -> Quantity precision -> Amount precision
+ Haspara.Accounting.Amount: data Amount (precision :: Nat)
+ Haspara.Accounting.Amount: instance GHC.Classes.Eq (Haspara.Accounting.Amount.Amount precision)
+ Haspara.Accounting.Amount: instance GHC.Classes.Ord (Haspara.Accounting.Amount.Amount precision)
+ Haspara.Accounting.Amount: instance GHC.Generics.Generic (Haspara.Accounting.Amount.Amount precision)
+ Haspara.Accounting.Amount: instance GHC.TypeNats.KnownNat precision => Data.Aeson.Types.FromJSON.FromJSON (Haspara.Accounting.Amount.Amount precision)
+ Haspara.Accounting.Amount: instance GHC.TypeNats.KnownNat precision => Data.Aeson.Types.ToJSON.ToJSON (Haspara.Accounting.Amount.Amount precision)
+ Haspara.Accounting.Amount: instance GHC.TypeNats.KnownNat precision => GHC.Show.Show (Haspara.Accounting.Amount.Amount precision)
+ Haspara.Accounting.Amount: quantityFromAmount :: KnownNat precision => AccountKind -> Amount precision -> Quantity precision
+ Haspara.Accounting.Amount: valueFromAmount :: KnownNat precision => AccountKind -> Amount precision -> Quantity precision
+ Haspara.Accounting.Balance: Balance :: !Side -> !Quantity precision -> Balance (precision :: Nat)
+ Haspara.Accounting.Balance: [balanceSide] :: Balance (precision :: Nat) -> !Side
+ Haspara.Accounting.Balance: [balanceValue] :: Balance (precision :: Nat) -> !Quantity precision
+ Haspara.Accounting.Balance: amountFromBalance :: KnownNat precision => Balance precision -> Amount precision
+ Haspara.Accounting.Balance: balanceCredit :: KnownNat precision => Balance precision -> Maybe (Quantity precision)
+ Haspara.Accounting.Balance: balanceDebit :: KnownNat precision => Balance precision -> Maybe (Quantity precision)
+ Haspara.Accounting.Balance: data Balance (precision :: Nat)
+ Haspara.Accounting.Balance: instance GHC.Classes.Eq (Haspara.Accounting.Balance.Balance precision)
+ Haspara.Accounting.Balance: instance GHC.Generics.Generic (Haspara.Accounting.Balance.Balance precision)
+ Haspara.Accounting.Balance: instance GHC.TypeNats.KnownNat precision => Data.Aeson.Types.FromJSON.FromJSON (Haspara.Accounting.Balance.Balance precision)
+ Haspara.Accounting.Balance: instance GHC.TypeNats.KnownNat precision => Data.Aeson.Types.ToJSON.ToJSON (Haspara.Accounting.Balance.Balance precision)
+ Haspara.Accounting.Balance: instance GHC.TypeNats.KnownNat precision => GHC.Show.Show (Haspara.Accounting.Balance.Balance precision)
+ Haspara.Accounting.Balance: quantityFromBalance :: KnownNat precision => AccountKind -> Balance precision -> Quantity precision
+ Haspara.Accounting.Balance: updateBalance :: KnownNat precision => Balance precision -> Amount precision -> Balance precision
+ Haspara.Accounting.Balance: valueFromBalance :: KnownNat precision => AccountKind -> Balance precision -> Quantity precision
+ Haspara.Accounting.Journal: Journal :: [JournalEntry precision account event] -> Journal (precision :: Nat) account event
+ Haspara.Accounting.Journal: JournalEntry :: !Text -> !Day -> ![JournalEntryItem precision account event] -> !Text -> JournalEntry (precision :: Nat) account event
+ Haspara.Accounting.Journal: JournalEntryItem :: !Amount precision -> !Account account -> !event -> JournalEntryItem (precision :: Nat) account event
+ Haspara.Accounting.Journal: [journalEntries] :: Journal (precision :: Nat) account event -> [JournalEntry precision account event]
+ Haspara.Accounting.Journal: [journalEntryDate] :: JournalEntry (precision :: Nat) account event -> !Day
+ Haspara.Accounting.Journal: [journalEntryDescription] :: JournalEntry (precision :: Nat) account event -> !Text
+ Haspara.Accounting.Journal: [journalEntryId] :: JournalEntry (precision :: Nat) account event -> !Text
+ Haspara.Accounting.Journal: [journalEntryItemAccount] :: JournalEntryItem (precision :: Nat) account event -> !Account account
+ Haspara.Accounting.Journal: [journalEntryItemAmount] :: JournalEntryItem (precision :: Nat) account event -> !Amount precision
+ Haspara.Accounting.Journal: [journalEntryItemEvent] :: JournalEntryItem (precision :: Nat) account event -> !event
+ Haspara.Accounting.Journal: [journalEntryItems] :: JournalEntry (precision :: Nat) account event -> ![JournalEntryItem precision account event]
+ Haspara.Accounting.Journal: data JournalEntry (precision :: Nat) account event
+ Haspara.Accounting.Journal: data JournalEntryItem (precision :: Nat) account event
+ Haspara.Accounting.Journal: instance (GHC.Classes.Eq account, GHC.Classes.Eq event) => GHC.Classes.Eq (Haspara.Accounting.Journal.JournalEntryItem precision account event)
+ Haspara.Accounting.Journal: instance (GHC.TypeNats.KnownNat precision, GHC.Show.Show account, GHC.Show.Show event) => GHC.Show.Show (Haspara.Accounting.Journal.Journal precision account event)
+ Haspara.Accounting.Journal: instance (GHC.TypeNats.KnownNat precision, GHC.Show.Show account, GHC.Show.Show event) => GHC.Show.Show (Haspara.Accounting.Journal.JournalEntry precision account event)
+ Haspara.Accounting.Journal: instance (GHC.TypeNats.KnownNat precision, GHC.Show.Show account, GHC.Show.Show event) => GHC.Show.Show (Haspara.Accounting.Journal.JournalEntryItem precision account event)
+ Haspara.Accounting.Journal: instance GHC.Generics.Generic (Haspara.Accounting.Journal.JournalEntryItem precision account event)
+ Haspara.Accounting.Journal: isJournalEntryBalanced :: KnownNat precision => JournalEntry precision account event -> Bool
+ Haspara.Accounting.Journal: journalEntryTotalCredit :: KnownNat precision => JournalEntry precision account event -> UnsignedQuantity precision
+ Haspara.Accounting.Journal: journalEntryTotalDebit :: KnownNat precision => JournalEntry precision account event -> UnsignedQuantity precision
+ Haspara.Accounting.Journal: mkJournalEntryItemFromQuantity :: KnownNat precision => Quantity precision -> Account account -> event -> JournalEntryItem precision account event
+ Haspara.Accounting.Journal: mkJournalEntryItemFromValue :: KnownNat precision => Quantity precision -> Account account -> event -> JournalEntryItem precision account event
+ Haspara.Accounting.Journal: newtype Journal (precision :: Nat) account event
+ Haspara.Accounting.Ledger: GeneralLedger :: [Ledger precision account event] -> GeneralLedger (precision :: Nat) account event
+ Haspara.Accounting.Ledger: LedgerEntry :: !Day -> !Amount precision -> !Text -> !event -> !Text -> !Balance precision -> LedgerEntry (precision :: Nat) event
+ Haspara.Accounting.Ledger: [generalLedgerLedgers] :: GeneralLedger (precision :: Nat) account event -> [Ledger precision account event]
+ Haspara.Accounting.Ledger: [ledgerEntryAmount] :: LedgerEntry (precision :: Nat) event -> !Amount precision
+ Haspara.Accounting.Ledger: [ledgerEntryBalance] :: LedgerEntry (precision :: Nat) event -> !Balance precision
+ Haspara.Accounting.Ledger: [ledgerEntryDate] :: LedgerEntry (precision :: Nat) event -> !Day
+ Haspara.Accounting.Ledger: [ledgerEntryDescription] :: LedgerEntry (precision :: Nat) event -> !Text
+ Haspara.Accounting.Ledger: [ledgerEntryEvent] :: LedgerEntry (precision :: Nat) event -> !event
+ Haspara.Accounting.Ledger: [ledgerEntryPostingId] :: LedgerEntry (precision :: Nat) event -> !Text
+ Haspara.Accounting.Ledger: data LedgerEntry (precision :: Nat) event
+ Haspara.Accounting.Ledger: initLedger :: KnownNat precision => Account account -> Ledger precision account event
+ Haspara.Accounting.Ledger: initLedgerWithOpeningBalance :: KnownNat precision => Account account -> Balance precision -> Ledger precision account event
+ Haspara.Accounting.Ledger: initLedgerWithOpeningQuantity :: KnownNat precision => Account account -> Quantity precision -> Ledger precision account event
+ Haspara.Accounting.Ledger: initLedgerWithOpeningValue :: KnownNat precision => Account account -> Quantity precision -> Ledger precision account event
+ Haspara.Accounting.Ledger: instance (GHC.Classes.Eq account, GHC.Classes.Eq event) => GHC.Classes.Eq (Haspara.Accounting.Ledger.GeneralLedger precision account event)
+ Haspara.Accounting.Ledger: instance (GHC.Classes.Eq account, GHC.Classes.Eq event) => GHC.Classes.Eq (Haspara.Accounting.Ledger.Ledger precision account event)
+ Haspara.Accounting.Ledger: instance (GHC.TypeNats.KnownNat precision, Data.Aeson.Types.FromJSON.FromJSON account, Data.Aeson.Types.FromJSON.FromJSON event) => Data.Aeson.Types.FromJSON.FromJSON (Haspara.Accounting.Ledger.GeneralLedger precision account event)
+ Haspara.Accounting.Ledger: instance (GHC.TypeNats.KnownNat precision, Data.Aeson.Types.FromJSON.FromJSON account, Data.Aeson.Types.FromJSON.FromJSON event) => Data.Aeson.Types.FromJSON.FromJSON (Haspara.Accounting.Ledger.Ledger precision account event)
+ Haspara.Accounting.Ledger: instance (GHC.TypeNats.KnownNat precision, Data.Aeson.Types.FromJSON.FromJSON event) => Data.Aeson.Types.FromJSON.FromJSON (Haspara.Accounting.Ledger.LedgerEntry precision event)
+ Haspara.Accounting.Ledger: instance (GHC.TypeNats.KnownNat precision, Data.Aeson.Types.ToJSON.ToJSON account, Data.Aeson.Types.ToJSON.ToJSON event) => Data.Aeson.Types.ToJSON.ToJSON (Haspara.Accounting.Ledger.GeneralLedger precision account event)
+ Haspara.Accounting.Ledger: instance (GHC.TypeNats.KnownNat precision, Data.Aeson.Types.ToJSON.ToJSON account, Data.Aeson.Types.ToJSON.ToJSON event) => Data.Aeson.Types.ToJSON.ToJSON (Haspara.Accounting.Ledger.Ledger precision account event)
+ Haspara.Accounting.Ledger: instance (GHC.TypeNats.KnownNat precision, Data.Aeson.Types.ToJSON.ToJSON event) => Data.Aeson.Types.ToJSON.ToJSON (Haspara.Accounting.Ledger.LedgerEntry precision event)
+ Haspara.Accounting.Ledger: instance (GHC.TypeNats.KnownNat precision, GHC.Show.Show account, GHC.Show.Show event) => GHC.Show.Show (Haspara.Accounting.Ledger.GeneralLedger precision account event)
+ Haspara.Accounting.Ledger: instance (GHC.TypeNats.KnownNat precision, GHC.Show.Show account, GHC.Show.Show event) => GHC.Show.Show (Haspara.Accounting.Ledger.Ledger precision account event)
+ Haspara.Accounting.Ledger: instance (GHC.TypeNats.KnownNat precision, GHC.Show.Show event) => GHC.Show.Show (Haspara.Accounting.Ledger.LedgerEntry precision event)
+ Haspara.Accounting.Ledger: instance GHC.Classes.Eq event => GHC.Classes.Eq (Haspara.Accounting.Ledger.LedgerEntry precision event)
+ Haspara.Accounting.Ledger: instance GHC.Generics.Generic (Haspara.Accounting.Ledger.GeneralLedger precision account event)
+ Haspara.Accounting.Ledger: instance GHC.Generics.Generic (Haspara.Accounting.Ledger.Ledger precision account event)
+ Haspara.Accounting.Ledger: instance GHC.Generics.Generic (Haspara.Accounting.Ledger.LedgerEntry precision event)
+ Haspara.Accounting.Ledger: ledgerClosing :: KnownNat precision => Ledger precision account event -> Balance precision
+ Haspara.Accounting.Ledger: newtype GeneralLedger (precision :: Nat) account event
+ Haspara.Accounting.Ledger: postEntries :: KnownNat precision => Eq account => Ord account => GeneralLedger precision account event -> [JournalEntry precision account event] -> GeneralLedger precision account event
+ Haspara.Accounting.Ledger: postEntry :: KnownNat precision => Eq account => Ord account => GeneralLedger precision account event -> JournalEntry precision account event -> GeneralLedger precision account event
+ Haspara.Accounting.Ledger: postEntryItem :: KnownNat precision => Eq account => Ord account => GeneralLedger precision account event -> JournalEntry precision account event -> JournalEntryItem precision account event -> GeneralLedger precision account event
+ Haspara.Accounting.Ledger: postItem :: KnownNat precision => Ledger precision account event -> Day -> Amount precision -> Text -> event -> Text -> Ledger precision account event
+ Haspara.Accounting.Side: SideCredit :: Side
+ Haspara.Accounting.Side: SideDebit :: Side
+ Haspara.Accounting.Side: data Side
+ Haspara.Accounting.Side: instance Data.Aeson.Types.FromJSON.FromJSON Haspara.Accounting.Side.Side
+ Haspara.Accounting.Side: instance Data.Aeson.Types.ToJSON.ToJSON Haspara.Accounting.Side.Side
+ Haspara.Accounting.Side: instance GHC.Classes.Eq Haspara.Accounting.Side.Side
+ Haspara.Accounting.Side: instance GHC.Classes.Ord Haspara.Accounting.Side.Side
+ Haspara.Accounting.Side: instance GHC.Show.Show Haspara.Accounting.Side.Side
+ Haspara.Accounting.Side: normalSideByAccountKind :: AccountKind -> Side
+ Haspara.Accounting.Side: otherSide :: Side -> Side
+ Haspara.Accounting.Side: sideByAccountKind :: KnownNat precision => AccountKind -> Quantity precision -> Side
+ Haspara.Accounting.TrialBalance: TrialBalance :: [TrialBalanceItem precision account event] -> TrialBalance (precision :: Nat) account event
+ Haspara.Accounting.TrialBalance: TrialBalanceItem :: !Ledger precision account event -> !Balance precision -> TrialBalanceItem (precision :: Nat) account event
+ Haspara.Accounting.TrialBalance: [trialBalanceItemBalance] :: TrialBalanceItem (precision :: Nat) account event -> !Balance precision
+ Haspara.Accounting.TrialBalance: [trialBalanceItemLedger] :: TrialBalanceItem (precision :: Nat) account event -> !Ledger precision account event
+ Haspara.Accounting.TrialBalance: [trialBalanceItems] :: TrialBalance (precision :: Nat) account event -> [TrialBalanceItem precision account event]
+ Haspara.Accounting.TrialBalance: data TrialBalanceItem (precision :: Nat) account event
+ Haspara.Accounting.TrialBalance: instance (GHC.Classes.Eq account, GHC.Classes.Eq event) => GHC.Classes.Eq (Haspara.Accounting.TrialBalance.TrialBalance precision account event)
+ Haspara.Accounting.TrialBalance: instance (GHC.Classes.Eq account, GHC.Classes.Eq event) => GHC.Classes.Eq (Haspara.Accounting.TrialBalance.TrialBalanceItem precision account event)
+ Haspara.Accounting.TrialBalance: instance (GHC.TypeNats.KnownNat precision, Data.Aeson.Types.FromJSON.FromJSON account, Data.Aeson.Types.FromJSON.FromJSON event) => Data.Aeson.Types.FromJSON.FromJSON (Haspara.Accounting.TrialBalance.TrialBalance precision account event)
+ Haspara.Accounting.TrialBalance: instance (GHC.TypeNats.KnownNat precision, Data.Aeson.Types.FromJSON.FromJSON account, Data.Aeson.Types.FromJSON.FromJSON event) => Data.Aeson.Types.FromJSON.FromJSON (Haspara.Accounting.TrialBalance.TrialBalanceItem precision account event)
+ Haspara.Accounting.TrialBalance: instance (GHC.TypeNats.KnownNat precision, Data.Aeson.Types.ToJSON.ToJSON account, Data.Aeson.Types.ToJSON.ToJSON event) => Data.Aeson.Types.ToJSON.ToJSON (Haspara.Accounting.TrialBalance.TrialBalance precision account event)
+ Haspara.Accounting.TrialBalance: instance (GHC.TypeNats.KnownNat precision, Data.Aeson.Types.ToJSON.ToJSON account, Data.Aeson.Types.ToJSON.ToJSON event) => Data.Aeson.Types.ToJSON.ToJSON (Haspara.Accounting.TrialBalance.TrialBalanceItem precision account event)
+ Haspara.Accounting.TrialBalance: instance (GHC.TypeNats.KnownNat precision, GHC.Show.Show account, GHC.Show.Show event) => GHC.Show.Show (Haspara.Accounting.TrialBalance.TrialBalance precision account event)
+ Haspara.Accounting.TrialBalance: instance (GHC.TypeNats.KnownNat precision, GHC.Show.Show account, GHC.Show.Show event) => GHC.Show.Show (Haspara.Accounting.TrialBalance.TrialBalanceItem precision account event)
+ Haspara.Accounting.TrialBalance: instance GHC.Generics.Generic (Haspara.Accounting.TrialBalance.TrialBalance precision account event)
+ Haspara.Accounting.TrialBalance: instance GHC.Generics.Generic (Haspara.Accounting.TrialBalance.TrialBalanceItem precision account event)
+ Haspara.Accounting.TrialBalance: mkTrialBalanceItem :: KnownNat precision => Ledger precision account event -> TrialBalanceItem precision account event
+ Haspara.Accounting.TrialBalance: newtype TrialBalance (precision :: Nat) account event
+ Haspara.Accounting.TrialBalance: prepareTrialBalance :: KnownNat precision => GeneralLedger precision account event -> TrialBalance precision account event
+ Haspara.Accounting.TrialBalance: trialBalanceItemAmount :: KnownNat precision => TrialBalanceItem precision account event -> Amount precision
+ Haspara.Accounting.TrialBalance: trialBalanceTotals :: KnownNat precision => TrialBalance precision account event -> (Balance precision, Balance precision)
+ Haspara.Quantity: absQuantity :: KnownNat s => Quantity s -> UnsignedQuantity s
+ Haspara.Quantity: sumUnsignedQuantity :: KnownNat s => [UnsignedQuantity s] -> UnsignedQuantity s
- Haspara.Accounting.Ledger: Ledger :: !Account a -> !Quantity s -> !Quantity s -> ![LedgerItem o s] -> Ledger a o (s :: Nat)
+ Haspara.Accounting.Ledger: Ledger :: !Account account -> !Balance precision -> ![LedgerEntry precision event] -> Ledger (precision :: Nat) account event
- Haspara.Accounting.Ledger: [ledgerAccount] :: Ledger a o (s :: Nat) -> !Account a
+ Haspara.Accounting.Ledger: [ledgerAccount] :: Ledger (precision :: Nat) account event -> !Account account
- Haspara.Accounting.Ledger: [ledgerOpening] :: Ledger a o (s :: Nat) -> !Quantity s
+ Haspara.Accounting.Ledger: [ledgerOpening] :: Ledger (precision :: Nat) account event -> !Balance precision
- Haspara.Accounting.Ledger: [ledgerRunning] :: Ledger a o (s :: Nat) -> ![LedgerItem o s]
+ Haspara.Accounting.Ledger: [ledgerRunning] :: Ledger (precision :: Nat) account event -> ![LedgerEntry precision event]
- Haspara.Accounting.Ledger: data Ledger a o (s :: Nat)
+ Haspara.Accounting.Ledger: data Ledger (precision :: Nat) account event

Files

CHANGELOG.md view
@@ -2,6 +2,32 @@ ## [Unreleased]  +<a name="0.0.0.4"></a>+## [0.0.0.4] - 2022-06-22+### Chore+- fix doctests and Haddock documentation+- start working on Haspara.Accounting.Journal module+- add new functions to Haspara.Quantity module+- bump development version to 0.0.0.4++### Feat+- add Haspara.Accounting.Side module+- **accounting:** improve Ledger module definitions+- **accounting:** delineate value and quantity concepts wrt Amount+- **accounting:** add Bounded instance to AccountKind+- **accounting:** add TrialBalance definitions+- **accounting:** add Amount and Balance definitions+- **accounting:** improve the Haspara.Accounting.Side module+- **accounting:** improve Balance module definitions++### Refactor+- **accounting:** remove Event definitions, revisit exports+- **accounting:** revisit Ledger{Entry} and Journal{Entry,EntryItem}++### Pull Requests+- Merge pull request [#10](https://github.com/telostat/haspara/issues/10) from telostat/vst/issue-9++ <a name="0.0.0.3"></a> ## [0.0.0.3] - 2022-06-17 ### Chore@@ -9,6 +35,7 @@ - adopt Stack lts-19.11 (and GHC902), add Nix Shell - bump development version to 0.0.0.3 - **build:** specify dependency version ranges+- **release:** 0.0.0.3  ### Refactor - revisit Haspara.FxQuote module, add new definitions@@ -79,7 +106,8 @@ - Merge pull request [#1](https://github.com/telostat/haspara/issues/1) from telostat/init  -[Unreleased]: https://github.com/telostat/haspara/compare/0.0.0.3...HEAD+[Unreleased]: https://github.com/telostat/haspara/compare/0.0.0.4...HEAD+[0.0.0.4]: https://github.com/telostat/haspara/compare/0.0.0.3...0.0.0.4 [0.0.0.3]: https://github.com/telostat/haspara/compare/0.0.0.2...0.0.0.3 [0.0.0.2]: https://github.com/telostat/haspara/compare/0.0.0.1...0.0.0.2 [0.0.0.1]: https://github.com/telostat/haspara/compare/0.0.0.0...0.0.0.1
haspara.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           haspara-version:        0.0.0.3+version:        0.0.0.4 synopsis:       A library providing definitions to work with monetary values. description:    Please see the README on GitHub at <https://github.com/telostat/haspara#readme> category:       Finance@@ -30,8 +30,12 @@       Haspara       Haspara.Accounting       Haspara.Accounting.Account-      Haspara.Accounting.Event+      Haspara.Accounting.Amount+      Haspara.Accounting.Balance+      Haspara.Accounting.Journal       Haspara.Accounting.Ledger+      Haspara.Accounting.Side+      Haspara.Accounting.TrialBalance       Haspara.Currency       Haspara.FxQuote       Haspara.Internal.Aeson
src/Haspara/Accounting.hs view
@@ -2,46 +2,19 @@ -- accounting functionality.  module Haspara.Accounting-  ( Account(..)-  , AccountKind(..)-  , accountKindText-  , Entry(..)-  , buildEntry-  , Event(..)-  , eventDate-  , eventObject-  , negateEvent-  , mkEvent-  , Posting(..)-  , postingEvents-  , post-  , Ledger(..)-  , LedgerItem(..)-  , mkLedger-  , addEntry-  , entryDate-  , entryObject-  , entryQuantity-  , entryDebit-  , entryCredit+  ( module Haspara.Accounting.Account+  , module Haspara.Accounting.Amount+  , module Haspara.Accounting.Balance+  , module Haspara.Accounting.Journal+  , module Haspara.Accounting.Ledger+  , module Haspara.Accounting.Side+  , module Haspara.Accounting.TrialBalance   ) where --import Haspara.Accounting.Account (Account(..), AccountKind(..), accountKindText)-import Haspara.Accounting.Event   (Event(..), eventDate, eventObject, mkEvent, negateEvent)+import Haspara.Accounting.Account+import Haspara.Accounting.Amount+import Haspara.Accounting.Balance+import Haspara.Accounting.Journal import Haspara.Accounting.Ledger-       ( Entry(..)-       , Ledger(..)-       , LedgerItem(..)-       , Posting(..)-       , addEntry-       , buildEntry-       , entryCredit-       , entryDate-       , entryDebit-       , entryObject-       , entryQuantity-       , mkLedger-       , post-       , postingEvents-       )+import Haspara.Accounting.Side+import Haspara.Accounting.TrialBalance
src/Haspara/Accounting/Account.hs view
@@ -57,7 +57,7 @@   | AccountKindEquity   | AccountKindRevenue   | AccountKindExpense-  deriving (Enum, Eq, Generic, Ord, Show)+  deriving (Bounded, Enum, Eq, Generic, Ord, Show)   instance Hashable AccountKind
+ src/Haspara/Accounting/Amount.hs view
@@ -0,0 +1,255 @@+-- | This module provides definitions for amounts used as in accounting.+--+-- For balance definition that allows "Negative Balance" phenomenon, see+-- 'Haspara.Accounting.Balance'.++{-# LANGUAGE DataKinds #-}++module Haspara.Accounting.Amount where++import qualified Data.Aeson                 as Aeson+import           GHC.Generics               (Generic)+import           GHC.TypeLits               (KnownNat, Nat)+import           Haspara.Accounting.Account (AccountKind(..))+import           Haspara.Accounting.Side    (Side(..), sideByAccountKind)+import           Haspara.Internal.Aeson     (commonAesonOptions)+import           Haspara.Quantity           (Quantity, UnsignedQuantity, absQuantity)+import           Refined                    (unrefine)+++-- | Data definition for amounts.+data Amount (precision :: Nat) = Amount+  { amountSide  :: !Side+  , amountValue :: !(UnsignedQuantity precision)+  }+  deriving (Eq, Generic, Ord, Show)+++-- | 'Aeson.FromJSON' instance for 'Amount'.+--+-- >>> Aeson.eitherDecode "{\"side\": \"db\", \"value\": 42}" :: Either String (Amount 2)+-- Right (Amount {amountSide = SideDebit, amountValue = Refined 42.00})+-- >>> Aeson.eitherDecode "{\"side\": \"cr\", \"value\": 42}" :: Either String (Amount 2)+-- Right (Amount {amountSide = SideCredit, amountValue = Refined 42.00})+instance KnownNat precision => Aeson.FromJSON (Amount precision) where+  parseJSON = Aeson.genericParseJSON $ commonAesonOptions "amount"+++-- | 'Aeson.ToJSON' instance for 'Amount'.+--+-- >>> import Haspara.Accounting.Side+-- >>> import Haspara.Quantity+-- >>> import Refined.Unsafe+-- >>> Aeson.encode (Amount SideDebit (unsafeRefine (mkQuantity 42 :: Quantity 2)))+-- "{\"side\":\"db\",\"value\":42.0}"+-- >>> Aeson.encode (Amount SideCredit (unsafeRefine (mkQuantity 42 :: Quantity 2)))+-- "{\"side\":\"cr\",\"value\":42.0}"+-- >>> Aeson.eitherDecode (Aeson.encode (Amount SideDebit (unsafeRefine (mkQuantity 42 :: Quantity 2)))) :: Either String (Amount 2)+-- Right (Amount {amountSide = SideDebit, amountValue = Refined 42.00})+-- >>> Aeson.eitherDecode (Aeson.encode (Amount SideCredit (unsafeRefine (mkQuantity 42 :: Quantity 2)))) :: Either String (Amount 2)+-- Right (Amount {amountSide = SideCredit, amountValue = Refined 42.00})+instance KnownNat precision => Aeson.ToJSON (Amount precision) where+  toJSON = Aeson.genericToJSON $ commonAesonOptions "amount"+++-- | Returns the debit value of the 'Amount', if any.+amountDebit :: KnownNat precision => Amount precision -> Maybe (UnsignedQuantity precision)+amountDebit (Amount SideDebit value) = Just value+amountDebit _                        = Nothing+++-- | Returns the credit value of the 'Amount', if any.+amountCredit :: KnownNat precision => Amount precision -> Maybe (UnsignedQuantity precision)+amountCredit (Amount SideCredit value) = Just value+amountCredit _                         = Nothing+++-- | Builds the 'Amount' for the given /value/ for the given 'AccountKind'.+--+-- The /value/ concept here refers to the value of a particular economic event+-- as in the contribution of that event to the net-worth of the entity.+--+-- This definition of the value is different than what we refer to in+-- 'amountFromQuantity'. In 'amountFromQuantity' the /quantity/ is simply+-- reflecting the increment or decrement in a particular account of a particular+-- 'AccountKind'.+--+-- For example, consider getting a loan: There are two immediate events due to+-- this exchange:+--+-- 1. Inflow of cash of some quantity to an 'AccountKindAsset' account.+-- 2. Inflow of loan contract with some notional value of the same quantity to a+--    'AccountKindLiability acount.+--+-- Let's say, the notional is USD 1,000. Therefore:+--+-- 1. Inflow of USD 1,000 to the cash account.+-- 2. Inflow of a Loan Contract of USD 1,000 to the liability account.+--+-- Conventionally, the latter is reflected as follow:+--+-- >>> import Haspara.Quantity+-- >>> amountFromQuantity AccountKindLiability (mkQuantity 1000 :: Quantity 2)+-- Amount {amountSide = SideCredit, amountValue = Refined 1000.00}+--+-- However, if the call-site is referring to values as in the net effect of the+-- event to the net-worth of the entity, then:+--+-- >>> amountFromValue AccountKindLiability (mkQuantity (-1000) :: Quantity 2)+-- Amount {amountSide = SideCredit, amountValue = Refined 1000.00}+--+-- For reference, given:+--+-- >>> let valPos = mkQuantity 42 :: Quantity 2+-- >>> let valNeg = mkQuantity (-42) :: Quantity 2+--+-- ..., let's consider following events:+--+-- We have an inflow and outflow of some assets, respectively:+--+-- >>> amountFromValue AccountKindAsset valPos+-- Amount {amountSide = SideDebit, amountValue = Refined 42.00}+-- >>> amountFromValue AccountKindAsset valNeg+-- Amount {amountSide = SideCredit, amountValue = Refined 42.00}+--+-- We have some decrease and increase in our liabilities, respectively:+--+-- >>> amountFromValue AccountKindLiability valPos+-- Amount {amountSide = SideDebit, amountValue = Refined 42.00}+-- >>> amountFromValue AccountKindLiability valNeg+-- Amount {amountSide = SideCredit, amountValue = Refined 42.00}+--+-- We have some increase and decrease in our equity, respectively:+--+-- >>> amountFromValue AccountKindEquity valPos+-- Amount {amountSide = SideCredit, amountValue = Refined 42.00}+-- >>> amountFromValue AccountKindEquity valNeg+-- Amount {amountSide = SideDebit, amountValue = Refined 42.00}+--+-- We have some profit and loss in our PnL, respectively:+--+-- >>> amountFromValue AccountKindRevenue valPos+-- Amount {amountSide = SideCredit, amountValue = Refined 42.00}+-- >>> amountFromValue AccountKindRevenue valNeg+-- Amount {amountSide = SideDebit, amountValue = Refined 42.00}+--+-- We have some decrease and increase in our expenses, respectively:+--+-- >>> amountFromValue AccountKindExpense valPos+-- Amount {amountSide = SideCredit, amountValue = Refined 42.00}+-- >>> amountFromValue AccountKindExpense valNeg+-- Amount {amountSide = SideDebit, amountValue = Refined 42.00}+amountFromValue+  :: KnownNat precision+  => AccountKind+  -> Quantity precision+  -> Amount precision+amountFromValue k q = case k of+  AccountKindAsset     -> Amount { amountSide = if q >= 0 then SideDebit else SideCredit, amountValue = absQuantity q }+  AccountKindLiability -> Amount { amountSide = if q >= 0 then SideDebit else SideCredit, amountValue = absQuantity q }+  AccountKindEquity    -> Amount { amountSide = if q >= 0 then SideCredit else SideDebit, amountValue = absQuantity q }+  AccountKindRevenue   -> Amount { amountSide = if q >= 0 then SideCredit else SideDebit, amountValue = absQuantity q }+  AccountKindExpense   -> Amount { amountSide = if q >= 0 then SideCredit else SideDebit, amountValue = absQuantity q }+++-- | Returns the value for the given 'Amount' for the given 'AccountKind'.+--+-- This is dual to 'amountFromValue'.+--+-- For values of positive and negative net-effect on the net-worth of the+-- entity, respectively:+--+-- >>> import Haspara.Quantity+-- >>> let valPos = mkQuantity 42 :: Quantity 2+-- >>> let valNeg = mkQuantity (-42) :: Quantity 2+--+-- ..., for a @check@ function that checks if the roundtrip to a value is+-- successful for a given 'AccountKind':+--+-- >>> let check = \k v -> v == valueFromAmount k (amountFromValue k v)+--+-- ..., and for the list of 'AccountKind's.+--+-- >>> let kinds = [minBound .. maxBound] :: [AccountKind]+-- >>> kinds+-- [AccountKindAsset,AccountKindLiability,AccountKindEquity,AccountKindRevenue,AccountKindExpense]+--+-- All checks should pass:+--+-- >>> all (\k -> check k valPos && check k valNeg) kinds+-- True+valueFromAmount+  :: KnownNat precision+  => AccountKind+  -> Amount precision+  -> Quantity precision+valueFromAmount k (Amount s v) = case (k, s, unrefine v) of+  (AccountKindAsset, SideDebit, q)      -> q+  (AccountKindAsset, SideCredit, q)     -> -q+  (AccountKindLiability, SideDebit, q)  -> q+  (AccountKindLiability, SideCredit, q) -> -q+  (AccountKindEquity, SideDebit, q)     -> -q+  (AccountKindEquity, SideCredit, q)    -> q+  (AccountKindRevenue, SideDebit, q)    -> -q+  (AccountKindRevenue, SideCredit, q)   -> q+  (AccountKindExpense, SideDebit, q)    -> -q+  (AccountKindExpense, SideCredit, q)   -> q+++-- | Builds the 'Amount' value for the given account kind and quantity.+--+-- The concept of /quantity/ here refers to the conventional concept of what it+-- means for an 'Haspara.Accounting.Account.Account' of a given 'AccountKind'.+--+-- For example, a loan of USD 1,000 has an increase in our liabilities.+-- Therefore, the quantity is expected to be positive:+--+-- >>> import Haspara.Quantity+-- >>> amountFromQuantity AccountKindLiability (mkQuantity 1000 :: Quantity 2)+-- Amount {amountSide = SideCredit, amountValue = Refined 1000.00}+--+-- Note 'amountFromValue' function if you are rather working with values that+-- are conceptually different than the /quantity/ here whereby a /value/ refers+-- to the value of a particular economic event as in the contribution of that+-- event to the net-worth of the entity. Therefore, above example would be+-- reflected as follows to get the same 'Amount' value:+--+-- >>> amountFromValue AccountKindLiability (mkQuantity (-1000) :: Quantity 2)+-- Amount {amountSide = SideCredit, amountValue = Refined 1000.00}+--+-- Check 'amountFromValue' documentation for further information.+amountFromQuantity+  :: KnownNat precision+  => AccountKind+  -> Quantity precision+  -> Amount precision+amountFromQuantity k q =+  Amount+    { amountSide = sideByAccountKind k q+    , amountValue = absQuantity q+    }+++-- | Returns the quantity for the given amount.+--+-- This is dual to 'amountFromQuantity'.+quantityFromAmount+  :: KnownNat precision+  => AccountKind+  -> Amount precision+  -> Quantity precision+quantityFromAmount k (Amount side absValue) =+  let+    value = unrefine absValue+  in+    case (k, side) of+      (AccountKindAsset, SideDebit)      -> value+      (AccountKindAsset, SideCredit)     -> -value+      (AccountKindLiability, SideDebit)  -> -value+      (AccountKindLiability, SideCredit) -> value+      (AccountKindEquity, SideDebit)     -> -value+      (AccountKindEquity, SideCredit)    -> value+      (AccountKindRevenue, SideDebit)    -> -value+      (AccountKindRevenue, SideCredit)   -> value+      (AccountKindExpense, SideDebit)    -> value+      (AccountKindExpense, SideCredit)   -> -value
+ src/Haspara/Accounting/Balance.hs view
@@ -0,0 +1,162 @@+-- | This module provides definitions for balances used as in accounting.++{-# LANGUAGE DataKinds #-}++module Haspara.Accounting.Balance where++import qualified Data.Aeson                 as Aeson+import           GHC.Generics               (Generic)+import           GHC.TypeLits               (KnownNat, Nat)+import           Haspara.Accounting.Account (AccountKind)+import           Haspara.Accounting.Amount  (Amount(Amount), quantityFromAmount, valueFromAmount)+import           Haspara.Accounting.Side    (Side(..), otherSide)+import           Haspara.Internal.Aeson     (commonAesonOptions)+import           Haspara.Quantity           (Quantity, absQuantity)+import           Refined                    (unrefine)+++-- | Data definition for balances.+--+-- This definition is similar to 'Haspara.Accounting.Amount.Amount', however,+-- the value is allowed to be negative to reflect "Negative Balance" phenomenon.+--+-- See https://www.accountingtools.com/articles/what-is-a-negative-balance.html+data Balance (precision :: Nat) = Balance+  { balanceSide  :: !Side+  , balanceValue :: !(Quantity precision)+  }+  deriving (Eq, Generic, Show)+++-- | 'Aeson.FromJSON' instance for 'Balance'.+--+-- For normal balances:+--+-- >>> Aeson.eitherDecode "{\"side\": \"db\", \"value\": 42}" :: Either String (Balance 2)+-- Right (Balance {balanceSide = SideDebit, balanceValue = 42.00})+-- >>> Aeson.eitherDecode "{\"side\": \"cr\", \"value\": 42}" :: Either String (Balance 2)+-- Right (Balance {balanceSide = SideCredit, balanceValue = 42.00})+--+-- For negative balances:+--+-- >>> Aeson.eitherDecode "{\"side\": \"db\", \"value\": -42}" :: Either String (Balance 2)+-- Right (Balance {balanceSide = SideDebit, balanceValue = -42.00})+-- >>> Aeson.eitherDecode "{\"side\": \"cr\", \"value\": -42}" :: Either String (Balance 2)+-- Right (Balance {balanceSide = SideCredit, balanceValue = -42.00})+instance KnownNat precision => Aeson.FromJSON (Balance precision) where+  parseJSON = Aeson.genericParseJSON $ commonAesonOptions "balance"+++-- | 'Aeson.ToJSON' instance for 'Balance'.+--+-- For normal balances:+--+-- >>> import Haspara.Accounting.Side+-- >>> import Haspara.Quantity+-- >>> Aeson.encode (Balance SideDebit (mkQuantity 42 :: Quantity 2))+-- "{\"side\":\"db\",\"value\":42.0}"+-- >>> Aeson.encode (Balance SideCredit (mkQuantity 42 :: Quantity 2))+-- "{\"side\":\"cr\",\"value\":42.0}"+-- >>> Aeson.eitherDecode (Aeson.encode (Balance SideDebit (mkQuantity 42 :: Quantity 2))) :: Either String (Balance 2)+-- Right (Balance {balanceSide = SideDebit, balanceValue = 42.00})+-- >>> Aeson.eitherDecode (Aeson.encode (Balance SideCredit (mkQuantity 42 :: Quantity 2))) :: Either String (Balance 2)+-- Right (Balance {balanceSide = SideCredit, balanceValue = 42.00})+--+-- For negative balances:+--+-- >>> Aeson.encode (Balance SideDebit (mkQuantity (-42) :: Quantity 2))+-- "{\"side\":\"db\",\"value\":-42.0}"+-- >>> Aeson.encode (Balance SideCredit (mkQuantity (-42) :: Quantity 2))+-- "{\"side\":\"cr\",\"value\":-42.0}"+-- >>> Aeson.eitherDecode (Aeson.encode (Balance SideDebit (mkQuantity (-42) :: Quantity 2))) :: Either String (Balance 2)+-- Right (Balance {balanceSide = SideDebit, balanceValue = -42.00})+-- >>> Aeson.eitherDecode (Aeson.encode (Balance SideCredit (mkQuantity (-42) :: Quantity 2))) :: Either String (Balance 2)+-- Right (Balance {balanceSide = SideCredit, balanceValue = -42.00})+instance KnownNat precision => Aeson.ToJSON (Balance precision) where+  toJSON = Aeson.genericToJSON $ commonAesonOptions "balance"+++-- | Returns the debit quantity, if any.+balanceDebit+  :: KnownNat precision+  => Balance precision+  -> Maybe (Quantity precision)+balanceDebit (Balance SideDebit v) = Just v+balanceDebit _                     = Nothing+++-- | Returns the credit quantity, if any.+balanceCredit+  :: KnownNat precision+  => Balance precision+  -> Maybe (Quantity precision)+balanceCredit (Balance SideCredit v) = Just v+balanceCredit _                      = Nothing+++-- | Updates the balance with the given amount.+--+-- >>> import Haspara.Accounting.Amount+-- >>> import Haspara.Accounting.Side+-- >>> import Refined.Unsafe+-- >>> let balance = Balance SideDebit 42 :: Balance 2+-- >>> balance+-- Balance {balanceSide = SideDebit, balanceValue = 42.00}+-- >>> let amountDebit = Amount SideDebit (unsafeRefine 10) :: Amount 2+-- >>> amountDebit+-- Amount {amountSide = SideDebit, amountValue = Refined 10.00}+-- >>> let amountCredit = Amount SideCredit (unsafeRefine 10) :: Amount 2+-- >>> amountCredit+-- Amount {amountSide = SideCredit, amountValue = Refined 10.00}+-- >>> updateBalance balance amountDebit+-- Balance {balanceSide = SideDebit, balanceValue = 52.00}+-- >>> updateBalance balance amountCredit+-- Balance {balanceSide = SideDebit, balanceValue = 32.00}+updateBalance+  :: KnownNat precision+  => Balance precision+  -> Amount precision+  -> Balance precision+updateBalance (Balance bSide bVal) (Amount aSide aVal) =+  Balance bSide (bVal + (unrefine aVal * (if bSide == aSide then 1 else (-1))))+++-- | Converts the balance to amount.+--+-- >>> import Haspara.Accounting.Side+-- >>> amountFromBalance (Balance SideDebit 42 :: Balance 2)+-- Amount {amountSide = SideDebit, amountValue = Refined 42.00}+-- >>> amountFromBalance (Balance SideDebit (-42) :: Balance 2)+-- Amount {amountSide = SideCredit, amountValue = Refined 42.00}+-- >>> amountFromBalance (Balance SideCredit 42 :: Balance 2)+-- Amount {amountSide = SideCredit, amountValue = Refined 42.00}+-- >>> amountFromBalance (Balance SideCredit (-42) :: Balance 2)+-- Amount {amountSide = SideDebit, amountValue = Refined 42.00}+amountFromBalance+  :: KnownNat precision+  => Balance precision+  -> Amount precision+amountFromBalance (Balance side value) =+  Amount (if value < 0 then otherSide side else side) (absQuantity value)+++-- | Returns the quantity of the balance given the account kind.+--+-- See 'quantityFromAmount' for the meaning of quantity.+quantityFromBalance+  :: KnownNat precision+  => AccountKind+  -> Balance precision+  -> Quantity precision+quantityFromBalance k = quantityFromAmount k . amountFromBalance+++-- | Returns the value of the balance given the account kind.+--+-- See 'valueFromAmount' for the meaning of quantity.+valueFromBalance+  :: KnownNat precision+  => AccountKind+  -> Balance precision+  -> Quantity precision+valueFromBalance k = valueFromAmount k . amountFromBalance
− src/Haspara/Accounting/Event.hs
@@ -1,91 +0,0 @@--- | This module provides definitions for economic events.------ /Note: The concept is not YET REA-compatible although we want to achieve it--- at some point/.--{-# LANGUAGE DataKinds #-}--module Haspara.Accounting.Event where--import           Control.Monad.Except (MonadError(throwError))-import           Data.Aeson           ((.:), (.=))-import qualified Data.Aeson           as Aeson-import qualified Data.Char            as C-import qualified Data.Text            as T-import           Data.Time            (Day)-import           GHC.TypeLits         (KnownNat, Nat)-import           Haspara.Quantity     (Quantity, UnsignedQuantity)-import           Refined              (refine)----- | Type encoding of an economic increment/decrement event.------ The event explicitly carries the date and quantity information along with a--- parameterized, arbitrary object providing the source of the event.------ >>> :set -XDataKinds--- >>> let date = read "2021-01-01"--- >>> let oid = 1 :: Int--- >>> let qty = $$(Refined.refineTH 42) :: UnsignedQuantity 2--- >>> let event = EventDecrement date oid qty--- >>> let json = Data.Aeson.encode event--- >>> json--- "{\"qty\":42.0,\"type\":\"DECREMENT\",\"obj\":1,\"date\":\"2021-01-01\"}"--- >>> Data.Aeson.decode @(Event Int 2) json--- Just (EventDecrement 2021-01-01 1 (Refined 42.00))--- >>> Data.Aeson.decode json == Just event--- True-data Event o (s :: Nat) =-    EventDecrement Day o (UnsignedQuantity s)-  | EventIncrement Day o (UnsignedQuantity s)-  deriving (Eq, Ord, Show)---instance (Aeson.FromJSON o, KnownNat s) => Aeson.FromJSON (Event o s) where-  parseJSON = Aeson.withObject "Event" $ \o -> do-    dorc <- o .: "type"-    cons <- case T.map C.toUpper dorc of-      "DECREMENT" -> pure EventDecrement-      "INCREMENT" -> pure EventIncrement-      x           -> fail ("Unknown event type: " <> T.unpack x)-    date <- o .: "date"-    obj <- o .: "obj"-    qty <- o .: "qty"-    pure (cons date obj qty)---instance (Aeson.ToJSON o, KnownNat s) => Aeson.ToJSON (Event o s) where-  toJSON x = case x of-    EventDecrement d o q -> Aeson.object ["type" .= ("DECREMENT" :: T.Text), "date" .= d, "obj" .= o, "qty" .= q]-    EventIncrement d o q -> Aeson.object ["type" .= ("INCREMENT" :: T.Text), "date" .= d, "obj" .= o, "qty" .= q]----- | Returns the date of the event.-eventDate :: (KnownNat s) => Event o s -> Day-eventDate (EventDecrement d _ _) = d-eventDate (EventIncrement d _ _) = d----- | Returns the source object of the event.-eventObject :: (KnownNat s) => Event o s -> o-eventObject (EventDecrement _ o _) = o-eventObject (EventIncrement _ o _) = o----- | Negates the event.-negateEvent :: (KnownNat s) => Event o s -> Event o s-negateEvent (EventDecrement d o x) = EventIncrement d o x-negateEvent (EventIncrement d o x) = EventDecrement d o x----- | Smart constuctor for 'Event' values.-mkEvent-  :: MonadError String m-  => KnownNat s-  => Day         -- ^ Date of the event.-  -> o           -- ^ Source object of the event.-  -> Quantity s  -- ^ Quantity of the event.-  -> m (Event o s)-mkEvent d o x-  | x < 0     = either (throwError . show) pure $ EventDecrement d o <$> refine (abs x)-  | otherwise = either (throwError . show) pure $ EventIncrement d o <$> refine (abs x)
+ src/Haspara/Accounting/Journal.hs view
@@ -0,0 +1,127 @@+-- | This module provides data definitions and functions to work with journal+-- entries.++{-# LANGUAGE DataKinds #-}++module Haspara.Accounting.Journal where++import qualified Data.Text                  as T+import           Data.Time                  (Day)+import           GHC.Generics               (Generic)+import           GHC.TypeLits               (KnownNat, Nat)+import           Haspara.Accounting.Account (Account(accountKind))+import           Haspara.Accounting.Amount  (Amount(..), amountFromQuantity, amountFromValue)+import           Haspara.Accounting.Side    (Side(..))+import           Haspara.Quantity           (Quantity, UnsignedQuantity, sumUnsignedQuantity)+++-- | Data definition for the journal entries of interest (like a general+-- ledger.)+--+-- A 'Journal' is a list of 'JournalEntry' records which are polymorphic over+-- the precision of the monetary quantities, the account and event objects.+newtype Journal (precision :: Nat) account event = Journal+  { journalEntries :: [JournalEntry precision account event]+  }+  deriving (Show)+++-- | Data definition for a journal entry.+--+-- A journal entry has a (unique) identifier, date and description, and a list+-- of 'JournalEntryItem's. Journal entry definition is polymorphic over the+-- precision of the monetary quantities, the account and event objects.+data JournalEntry (precision :: Nat) account event = JournalEntry+  { journalEntryId          :: !T.Text+  , journalEntryDate        :: !Day+  , journalEntryItems       :: ![JournalEntryItem precision account event]+  , journalEntryDescription :: !T.Text+  }+  deriving (Show)+++-- | Returns the total debit amount of a journal entry.+journalEntryTotalDebit+  :: KnownNat precision+  => JournalEntry precision account event+  -> UnsignedQuantity precision+journalEntryTotalDebit =+    sumUnsignedQuantity+  . fmap (amountValue . journalEntryItemAmount)+  . filter ((==) SideDebit . amountSide . journalEntryItemAmount)+  . journalEntryItems+++-- | Returns the total credit amount of a journal entry.+journalEntryTotalCredit+  :: KnownNat precision+  => JournalEntry precision account event+  -> UnsignedQuantity precision+journalEntryTotalCredit =+    sumUnsignedQuantity+  . fmap (amountValue . journalEntryItemAmount)+  . filter ((==) SideCredit . amountSide . journalEntryItemAmount)+  . journalEntryItems+++-- | Predicate to check if a journal entry is balanced or not.+--+-- The logical check is indeed whether the total debit amount is equal to the+-- total credit amount or not.+isJournalEntryBalanced+  :: KnownNat precision+  => JournalEntry precision account event+  -> Bool+isJournalEntryBalanced = (==)+  <$> journalEntryTotalDebit+  <*> journalEntryTotalCredit+++-- | Data definition for a journal entry item.+--+-- A journal entry item has a 'Side', an unsigned quantity as amount, an account+-- that it belongs to and the event the item is originating from. Journal entry+-- item definition is polymorphic over the precision of the monetary quantities,+-- the account and event objects.+data JournalEntryItem (precision :: Nat) account event = JournalEntryItem+  { journalEntryItemAmount  :: !(Amount precision)+  , journalEntryItemAccount :: !(Account account)+  , journalEntryItemEvent   :: !event+  }+  deriving (Eq, Generic, Show)+++-- | Creates a 'JournalEntryItem' from the given signed /quantity/, the account+-- it belongs to and the event it is originating from.+--+-- The /quantity/ is defined as in 'amountFromQuantity' function.+mkJournalEntryItemFromQuantity+  :: KnownNat precision+  => Quantity precision+  -> Account account+  -> event+  -> JournalEntryItem precision account event+mkJournalEntryItemFromQuantity qty acc evt =+  JournalEntryItem+    { journalEntryItemAmount = amountFromQuantity (accountKind acc) qty+    , journalEntryItemAccount = acc+    , journalEntryItemEvent = evt+    }+++-- | Creates a 'JournalEntryItem' from the given signed /value/, the account it+-- belongs to and the event it is originating from.+--+-- The /value/ is defined as in 'amountFromValue' function.+mkJournalEntryItemFromValue+  :: KnownNat precision+  => Quantity precision+  -> Account account+  -> event+  -> JournalEntryItem precision account event+mkJournalEntryItemFromValue val acc evt =+  JournalEntryItem+    { journalEntryItemAmount = amountFromValue (accountKind acc) val+    , journalEntryItemAccount = acc+    , journalEntryItemEvent = evt+    }
src/Haspara/Accounting/Ledger.hs view
@@ -1,213 +1,207 @@--- | This module provides definitions for postings, ledgers and ledger entries.+-- | This module provides data definitions and functions for ledgers and+-- postings.  {-# LANGUAGE DataKinds   #-} {-# LANGUAGE DerivingVia #-}  module Haspara.Accounting.Ledger where -import           Data.Aeson                 ((.:), (.=)) import qualified Data.Aeson                 as Aeson-import qualified Data.Char                  as C-import qualified Data.List.NonEmpty         as NE+import qualified Data.Map.Strict            as HM+import           Data.Maybe                 (fromMaybe, listToMaybe) import qualified Data.Text                  as T import           Data.Time                  (Day) import           GHC.Generics               (Generic) import           GHC.TypeLits               (KnownNat, Nat)-import           Haspara.Accounting.Account (Account(accountKind), AccountKind(..))-import           Haspara.Accounting.Event   (Event(..), eventObject)+import           Haspara.Accounting.Account (Account(accountKind))+import           Haspara.Accounting.Amount  (Amount, amountFromQuantity, amountFromValue)+import           Haspara.Accounting.Balance (Balance(Balance), updateBalance)+import           Haspara.Accounting.Journal (JournalEntry(..), JournalEntryItem(JournalEntryItem))+import           Haspara.Accounting.Side    (normalSideByAccountKind) import           Haspara.Internal.Aeson     (commonAesonOptions)-import           Haspara.Quantity           (Quantity, UnsignedQuantity)-import           Refined                    (unrefine)+import           Haspara.Quantity           (Quantity)  --- | Type encoding of a ledger.-data Ledger a o (s :: Nat) = Ledger-  { ledgerAccount :: !(Account a)-  , ledgerOpening :: !(Quantity s)-  , ledgerClosing :: !(Quantity s)-  , ledgerRunning :: ![LedgerItem o s]+-- | Data definition for a general ledger.+newtype GeneralLedger (precision :: Nat) account event = GeneralLedger+  { generalLedgerLedgers :: [Ledger precision account event]   }-  deriving (Eq, Generic, Ord, Show)+  deriving (Eq, Generic, Show)  -instance (Aeson.FromJSON a, Aeson.FromJSON o, KnownNat s) => Aeson.FromJSON (Ledger a o s) where-  parseJSON = Aeson.genericParseJSON $ commonAesonOptions "ledger"+instance (KnownNat precision, Aeson.FromJSON account, Aeson.FromJSON event) => Aeson.FromJSON (GeneralLedger precision account event) where+  parseJSON = Aeson.genericParseJSON $ commonAesonOptions "generalLedger"  -instance (Aeson.ToJSON a, Aeson.ToJSON o, KnownNat s) => Aeson.ToJSON (Ledger a o s) where-  toJSON = Aeson.genericToJSON $ commonAesonOptions "ledger"+instance (KnownNat precision, Aeson.ToJSON account, Aeson.ToJSON event) => Aeson.ToJSON (GeneralLedger precision account event) where+  toJSON = Aeson.genericToJSON $ commonAesonOptions "generalLedger"  --- | Type encoding of a ledger item.-data LedgerItem o (s :: Nat) = LedgerItem-  { ledgerItemEntry   :: !(Entry o s)-  , ledgerItemBalance :: !(Quantity s)+-- | Data definition for a ledger.+data Ledger (precision :: Nat) account event = Ledger+  { ledgerAccount :: !(Account account)+  , ledgerOpening :: !(Balance precision)+  , ledgerRunning :: ![LedgerEntry precision event]   }-  deriving (Eq, Generic, Ord, Show)+  deriving (Eq, Generic, Show)  -instance (Aeson.FromJSON o, KnownNat s) => Aeson.FromJSON (LedgerItem o s) where-  parseJSON = Aeson.genericParseJSON $ commonAesonOptions "ledgerItem"+instance (KnownNat precision, Aeson.FromJSON account, Aeson.FromJSON event) => Aeson.FromJSON (Ledger precision account event) where+  parseJSON = Aeson.genericParseJSON $ commonAesonOptions "ledger"  -instance (Aeson.ToJSON o, KnownNat s) => Aeson.ToJSON (LedgerItem o s) where-  toJSON = Aeson.genericToJSON $ commonAesonOptions "ledgerItem"+instance (KnownNat precision, Aeson.ToJSON account, Aeson.ToJSON event) => Aeson.ToJSON (Ledger precision account event) where+  -- TODO: Add ledgerClosing, too.+  toJSON = Aeson.genericToJSON $ commonAesonOptions "ledger"  --- | Creates a ledger from a given list of 'Entry' values.-mkLedger :: KnownNat s => Account a -> Quantity s -> [Entry o s] -> Ledger a o s-mkLedger a o = foldl addEntry (Ledger a o o [])+-- | Returns the closing balance of a ledger.+ledgerClosing+  :: KnownNat precision+  => Ledger precision account event+  -> Balance precision+ledgerClosing ledger = maybe (ledgerOpening ledger) ledgerEntryBalance (listToMaybe (ledgerRunning ledger))  --- | Adds a new entry to a ledger.-addEntry :: KnownNat s => Ledger a o s -> Entry o s -> Ledger a o s-addEntry l@(Ledger _ _ c r) e = l { ledgerClosing = balance, ledgerRunning = r <> [item]}-  where-    balance = c + entryQuantity e-    item = LedgerItem e balance+-- | Type encoding of a ledger item.+data LedgerEntry (precision :: Nat) event = LedgerEntry+  { ledgerEntryDate        :: !Day+  , ledgerEntryAmount      :: !(Amount precision)+  , ledgerEntryDescription :: !T.Text+  , ledgerEntryEvent       :: !event+  , ledgerEntryPostingId   :: !T.Text+  , ledgerEntryBalance     :: !(Balance precision)+  }+  deriving (Eq, Generic, Show)  --- | Type encoding for a posting.------ >>> :set -XDataKinds--- >>> import Haspara.Accounting--- >>> import Refined--- >>> import qualified Data.Aeson as Aeson--- >>> import qualified Data.List.NonEmpty as NE--- >>> let date = read "2021-01-01"--- >>> let oid = 1 :: Int--- >>> let qty = $$(refineTH 42) :: UnsignedQuantity 2--- >>> let event = EventDecrement date oid qty--- >>> let account = Account AccountKindAsset ("Cash" :: String, 1 ::Int)--- >>> let posting =  Posting . NE.fromList $ [(event, account)]--- >>> let json = Aeson.encode posting--- >>> json--- "[[{\"qty\":42.0,\"type\":\"DECREMENT\",\"obj\":1,\"date\":\"2021-01-01\"},{\"kind\":\"ASSET\",\"object\":[\"Cash\",1]}]]"--- >>> Aeson.decode json :: Maybe (Posting (String, Int) Int 2)--- Just (Posting ((EventDecrement 2021-01-01 1 (Refined 42.00),Account {accountKind = AccountKindAsset, accountObject = ("Cash",1)}) :| []))--- >>> Aeson.decode json == Just posting--- True-newtype Posting a o (s :: Nat) = Posting (NE.NonEmpty (Event o s, Account a))-  deriving (Eq, Generic, Ord, Show)+instance (KnownNat precision, Aeson.FromJSON event) => Aeson.FromJSON (LedgerEntry precision event) where+  parseJSON = Aeson.genericParseJSON $ commonAesonOptions "ledgerEntry"  -instance (Aeson.FromJSON a, Aeson.FromJSON o, KnownNat s) => Aeson.FromJSON (Posting a o s) where-  parseJSON = Aeson.genericParseJSON Aeson.defaultOptions+instance (KnownNat precision, Aeson.ToJSON event) => Aeson.ToJSON (LedgerEntry precision event) where+  toJSON = Aeson.genericToJSON $ commonAesonOptions "ledgerEntry"  -instance (Aeson.ToJSON a, Aeson.ToJSON o, KnownNat s) => Aeson.ToJSON (Posting a o s) where-  toJSON = Aeson.genericToJSON Aeson.defaultOptions+-- | Initializes an empty ledger for a given account.+initLedger+  :: KnownNat precision+  => Account account+  -> Ledger precision account event+initLedger acc = Ledger acc balance []+  where+    balance = Balance (normalSideByAccountKind (accountKind acc)) 0  --- | Returns the list of posting event sources.-postingEvents :: (KnownNat s) => Posting a o s -> [o]-postingEvents (Posting es)  = eventObject . fst <$> NE.toList es+-- | Initializes a ledger with the given opening balance.+initLedgerWithOpeningBalance+  :: KnownNat precision+  => Account account+  -> Balance precision+  -> Ledger precision account event+initLedgerWithOpeningBalance acc balance = Ledger acc balance []  --- | Posts an event.-post :: (KnownNat s) => Posting a o s -> [(Account a, Entry o s)]-post (Posting xs)       = go (NE.toList xs)+-- | Initializes a ledger with the given opening value.+--+-- See 'amountFromValue' for the meaning of the concept of value.+initLedgerWithOpeningValue+  :: KnownNat precision+  => Account account+  -> Quantity precision+  -> Ledger precision account event+initLedgerWithOpeningValue acc qty = initLedgerWithOpeningBalance acc balance   where-    go []              = []-    go ((ev, ac) : ys) = (ac, buildEntry ev (accountKind ac)) : go ys+    amount = amountFromValue (accountKind acc) qty+    balance0 = Balance (normalSideByAccountKind (accountKind acc)) 0+    balance = updateBalance balance0 amount  --- | Encoding of a posting entry.+-- | Initializes a ledger with the given opening quantity. ----- >>> :set -XDataKinds--- >>> import Refined--- >>> let date = read "2021-01-01"--- >>> let oid = 1 :: Int--- >>> let qty = $$(refineTH 42) :: UnsignedQuantity 2--- >>> let entry = EntryDebit date oid qty--- >>> let json = Aeson.encode entry--- >>> json--- "{\"qty\":42.0,\"type\":\"DEBIT\",\"obj\":1,\"date\":\"2021-01-01\"}"--- >>> Aeson.decode json :: Maybe (Entry Int 2)--- Just (EntryDebit 2021-01-01 1 (Refined 42.00))--- >>> Aeson.decode json == Just entry--- True-data Entry o (s :: Nat) =-    EntryDebit Day o (UnsignedQuantity s)-  | EntryCredit Day o (UnsignedQuantity s)-  deriving (Eq, Ord, Show)---instance (Aeson.FromJSON o, KnownNat s) => Aeson.FromJSON (Entry o s) where-  parseJSON = Aeson.withObject "Entry" $ \o -> do-    dorc <- o .: "type"-    cons <- case T.map C.toUpper dorc of-      "DEBIT"  -> pure EntryDebit-      "CREDIT" -> pure EntryCredit-      x        -> fail ("Unknown entry type: " <> T.unpack x)-    date <- o .: "date"-    obj <- o .: "obj"-    qty <- o .: "qty"-    pure (cons date obj qty)---instance (Aeson.ToJSON o, KnownNat s) => Aeson.ToJSON (Entry o s) where-  toJSON x = case x of-    EntryDebit d o q  -> Aeson.object ["type" .= ("DEBIT" :: T.Text), "date" .= d, "obj" .= o, "qty" .= q]-    EntryCredit d o q -> Aeson.object ["type" .= ("CREDIT" :: T.Text), "date" .= d, "obj" .= o, "qty" .= q]----- | Returns the date of the posting entry.-entryDate :: KnownNat s => Entry o s -> Day-entryDate (EntryDebit d _ _)  = d-entryDate (EntryCredit d _ _) = d----- | Returns the quantity of the posting entry.-entryQuantity :: KnownNat s => Entry o s -> Quantity s-entryQuantity (EntryDebit _ _ q)  = unrefine q-entryQuantity (EntryCredit _ _ q) = -(unrefine q)+-- See 'amountFromQuantity' for the meaning of the concept of quantity.+initLedgerWithOpeningQuantity+  :: KnownNat precision+  => Account account+  -> Quantity precision+  -> Ledger precision account event+initLedgerWithOpeningQuantity acc qty = initLedgerWithOpeningBalance acc balance+  where+    amount = amountFromQuantity (accountKind acc) qty+    balance0 = Balance (normalSideByAccountKind (accountKind acc)) 0+    balance = updateBalance balance0 amount  --- | Returns the source object of the posting entry.-entryObject :: KnownNat s => Entry o s -> o-entryObject (EntryDebit _ o _)  = o-entryObject (EntryCredit _ o _) = o+-- | Posts a given list of journal entries to the given general ledger and+-- returns the new general ledger.+postEntries+  :: KnownNat precision+  => Eq account+  => Ord account+  => GeneralLedger precision account event+  -> [JournalEntry precision account event]+  -> GeneralLedger precision account event+postEntries = foldl postEntry  --- | Returns the debit quantity of the posting entry.-entryDebit :: KnownNat s => Entry o s -> Maybe (UnsignedQuantity s)-entryDebit (EntryDebit _ _ x) = Just x-entryDebit EntryCredit {}     = Nothing+-- | Posts a given journal entry to the given general ledger and returns the new+-- general ledger.+postEntry+  :: KnownNat precision+  => Eq account+  => Ord account+  => GeneralLedger precision account event+  -> JournalEntry precision account event+  -> GeneralLedger precision account event+postEntry gl je = foldl (`postEntryItem` je) gl (journalEntryItems je)  --- | Returns the credit quantity of the posting entry.-entryCredit :: KnownNat s => Entry o s -> Maybe (UnsignedQuantity s)-entryCredit EntryDebit {}       = Nothing-entryCredit (EntryCredit _ _ x) = Just x+-- | Posts a given journal entry item of a given journal entry to the given+-- general ledger and returns the new general ledger.+postEntryItem+  :: KnownNat precision+  => Eq account+  => Ord account+  => GeneralLedger precision account event+  -> JournalEntry precision account event+  -> JournalEntryItem precision account event+  -> GeneralLedger precision account event+postEntryItem gl je (JournalEntryItem amt acc evt) =+  let+    ledgers = generalLedgerLedgers gl+    ledgersDb = HM.fromList $ zip (fmap ledgerAccount ledgers) ledgers+    ledgerCurr = fromMaybe (initLedger acc) $ HM.lookup acc ledgersDb+    ledgerNext = postItem ledgerCurr (journalEntryDate je) amt (journalEntryDescription je) evt (journalEntryId je)+    ledgersDbNext = HM.insert acc ledgerNext ledgersDb+  in+    GeneralLedger+      { generalLedgerLedgers = HM.elems ledgersDbNext+      }  --- | Consumes an event and a type of account, and produces a posting entry.------ Note the following map as a guide:------ +-----------------------+----------+----------+--- | Kind of account       | Debit    | Credit   |--- +-----------------------+----------+----------+--- | Asset                 | Increase | Decrease |--- +-----------------------+----------+----------+--- | Liability             | Decrease | Increase |--- +-----------------------+----------+----------+--- | Equity/Capital        | Decrease | Increase |--- +-----------------------+----------+----------+--- | Income/Revenue        | Decrease | Increase |--- +-----------------------+----------+----------+--- | Expense/Cost/Dividend | Increase | Decrease |--- +-----------------------+----------+----------+----buildEntry :: (KnownNat s) => Event o s -> AccountKind -> Entry o s-buildEntry (EventDecrement d o x) AccountKindAsset     = EntryCredit d o x-buildEntry (EventIncrement d o x) AccountKindAsset     = EntryDebit  d o x-buildEntry (EventDecrement d o x) AccountKindLiability = EntryDebit  d o x-buildEntry (EventIncrement d o x) AccountKindLiability = EntryCredit d o x-buildEntry (EventDecrement d o x) AccountKindEquity    = EntryDebit  d o x-buildEntry (EventIncrement d o x) AccountKindEquity    = EntryCredit d o x-buildEntry (EventDecrement d o x) AccountKindRevenue   = EntryDebit  d o x-buildEntry (EventIncrement d o x) AccountKindRevenue   = EntryCredit d o x-buildEntry (EventDecrement d o x) AccountKindExpense   = EntryCredit d o x-buildEntry (EventIncrement d o x) AccountKindExpense   = EntryDebit  d o x+-- | Performs a posting to the given ledger.+postItem+  :: KnownNat precision+  => Ledger precision account event+  -> Day+  -> Amount precision+  -> T.Text+  -> event+  -> T.Text+  -> Ledger precision account event+postItem ledger date amt dsc evt pid =+  let+    balanceLast = ledgerClosing ledger+    balanceNext = updateBalance balanceLast amt+    item = LedgerEntry+      { ledgerEntryDate = date+      , ledgerEntryAmount = amt+      , ledgerEntryDescription = dsc+      , ledgerEntryEvent = evt+      , ledgerEntryPostingId = pid+      , ledgerEntryBalance = balanceNext+      }+  in+    ledger+      { ledgerRunning = item : ledgerRunning ledger+      }
+ src/Haspara/Accounting/Side.hs view
@@ -0,0 +1,149 @@+-- | This module provides definitions for and functions to work with+-- Debit/Credit dichotomy which is essential to double-entry bookkeeping.+--+-- In our concept, we refer to this dichotomy as "Side" (materialized via 'Side'+-- sum-type) which is either "Debit" (materialized via 'SideDebit' nullary data+-- constructor) or "Dredit" (materialized via 'SideCredit' nullary data+-- constructor).+--+-- This module provides 'Aeson.FromJSON' and 'Aeson.ToJSON' instances for 'Side'+-- as well. Following accounting conventions, we chose the JSON value for+-- "Debit" as @"db"@, and for "Credit" as @"cr"@.++module Haspara.Accounting.Side where++import qualified Data.Aeson                 as Aeson+import qualified Data.Text                  as T+import           GHC.TypeLits               (KnownNat)+import           Haspara.Accounting.Account (AccountKind(..))+import           Haspara.Quantity           (Quantity)+++-- | Data definition for encoding the debit/credit indicator.+data Side = SideDebit | SideCredit+  deriving (Eq, Ord, Show)+++-- | 'Aeson.FromJSON' instance for 'Side'.+--+-- >>> Aeson.eitherDecode "\"db\"" :: Either String Side+-- Right SideDebit+-- >>> Aeson.eitherDecode "\"cr\"" :: Either String Side+-- Right SideCredit+-- >>> Aeson.eitherDecode "\"hebele\"" :: Either String Side+-- Left "Error in $: Unkown side indicator: \"hebele\". Expecting one of \"db\" or \"cr\""+instance Aeson.FromJSON Side where+  parseJSON = Aeson.withText "Side" $ \t -> case t of+    "db" -> pure SideDebit+    "cr" -> pure SideCredit+    _    -> fail $ "Unkown side indicator: \"" <> T.unpack t <> "\". Expecting one of \"db\" or \"cr\""+++-- | 'Aeson.ToJSON' instance for 'Side'.+--+-- >>> Aeson.encode SideDebit+-- "\"db\""+-- >>> Aeson.encode SideCredit+-- "\"cr\""+-- >>> Aeson.decode (Aeson.encode SideDebit) == Just SideDebit+-- True+-- >>> Aeson.decode (Aeson.encode SideCredit) == Just SideCredit+-- True+instance Aeson.ToJSON Side where+  toJSON SideDebit  = Aeson.String "db"+  toJSON SideCredit = Aeson.String "cr"+++-- | Gives the other side.+--+-- >>> otherSide SideDebit+-- SideCredit+-- >>> otherSide SideCredit+-- SideDebit+otherSide :: Side -> Side+otherSide SideDebit  = SideCredit+otherSide SideCredit = SideDebit+++-- | Computes the 'Side' by the given 'AccountKind' and the sign of the given+-- 'Quantity'.+--+-- The sign of the 'Quantity' is indeed a proxy for whether the event of the+-- 'Quantity' is an increment (@+1@) or decrement (@-1@) event.+--+-- @0@ quantities are considered to originate from an increment event. So far,+-- this seems to be a safe assumption that gives us totality in the context of+-- this function.+--+-- Note the following mapping as a guide:+--+-- +-----------------------+----------+----------++-- | Kind of account       | Debit    | Credit   |+-- +-----------------------+----------+----------++-- | Asset                 | Increase | Decrease |+-- +-----------------------+----------+----------++-- | Liability             | Decrease | Increase |+-- +-----------------------+----------+----------++-- | Equity/Capital        | Decrease | Increase |+-- +-----------------------+----------+----------++-- | Income/Revenue        | Decrease | Increase |+-- +-----------------------+----------+----------++-- | Expense/Cost/Dividend | Increase | Decrease |+-- +-----------------------+----------+----------++--+-- >>> :set -XDataKinds+-- >>> import Haspara.Quantity+-- >>> let decrement = mkQuantity (-0.42) :: Quantity 2+-- >>> let nocrement = mkQuantity 0 :: Quantity 2+-- >>> let increment = mkQuantity 0.42 :: Quantity 2+-- >>> fmap (sideByAccountKind AccountKindAsset) [decrement, nocrement, increment]+-- [SideCredit,SideDebit,SideDebit]+-- >>> fmap (sideByAccountKind AccountKindLiability) [decrement, nocrement, increment]+-- [SideDebit,SideCredit,SideCredit]+-- >>> fmap (sideByAccountKind AccountKindEquity) [decrement, nocrement, increment]+-- [SideDebit,SideCredit,SideCredit]+-- >>> fmap (sideByAccountKind AccountKindRevenue) [decrement, nocrement, increment]+-- [SideDebit,SideCredit,SideCredit]+-- >>> fmap (sideByAccountKind AccountKindExpense) [decrement, nocrement, increment]+-- [SideCredit,SideDebit,SideDebit]+sideByAccountKind+  :: KnownNat precision+  => AccountKind+  -> Quantity precision+  -> Side+sideByAccountKind k q = case (k, signum q >= 0) of+  (AccountKindAsset, False)     -> SideCredit+  (AccountKindAsset, True)      -> SideDebit+  (AccountKindLiability, False) -> SideDebit+  (AccountKindLiability, True)  -> SideCredit+  (AccountKindEquity, False)    -> SideDebit+  (AccountKindEquity, True)     -> SideCredit+  (AccountKindRevenue, False)   -> SideDebit+  (AccountKindRevenue, True)    -> SideCredit+  (AccountKindExpense, False)   -> SideCredit+  (AccountKindExpense, True)    -> SideDebit+++-- | Returns the "normal" side for a given 'AccountKind'.+--+-- Note the following mapping as a guide:+--+-- +-----------------+----------------+------------------++-- | Kind of Account | Normal Balance | Negative Balance |+-- +-----------------+----------------+------------------++-- | Asset           | Debit          | Credit           |+-- +-----------------+----------------+------------------++-- | Liability       | Credit         | Debit            |+-- +-----------------+----------------+------------------++-- | Equity          | Credit         | Debit            |+-- +-----------------+----------------+------------------++-- | Revenue         | Credit         | Debit            |+-- +-----------------+----------------+------------------++-- | Expense         | Debit          | Credit           |+-- +-----------------+----------------+------------------++normalSideByAccountKind :: AccountKind -> Side+normalSideByAccountKind AccountKindAsset     = SideDebit+normalSideByAccountKind AccountKindLiability = SideCredit+normalSideByAccountKind AccountKindEquity    = SideCredit+normalSideByAccountKind AccountKindRevenue   = SideCredit+normalSideByAccountKind AccountKindExpense   = SideDebit
+ src/Haspara/Accounting/TrialBalance.hs view
@@ -0,0 +1,87 @@+-- | This module provides data definitions and functions for trial balances.++{-# LANGUAGE DataKinds #-}++module Haspara.Accounting.TrialBalance where++import qualified Data.Aeson                 as Aeson+import           GHC.Generics               (Generic)+import           GHC.TypeLits               (KnownNat, Nat)+import           Haspara.Accounting.Amount  (Amount)+import           Haspara.Accounting.Balance (Balance(Balance, balanceSide), amountFromBalance, updateBalance)+import           Haspara.Accounting.Ledger  (GeneralLedger(generalLedgerLedgers), Ledger, ledgerClosing)+import           Haspara.Accounting.Side    (Side(..))+import           Haspara.Internal.Aeson     (commonAesonOptions)+++-- | Data definition for a trial balance.+newtype TrialBalance (precision :: Nat) account event = TrialBalance+  { trialBalanceItems :: [TrialBalanceItem precision account event]+  }+  deriving (Eq, Generic, Show)+++instance (KnownNat precision, Aeson.FromJSON account, Aeson.FromJSON event) => Aeson.FromJSON (TrialBalance precision account event) where+  parseJSON = Aeson.genericParseJSON $ commonAesonOptions "trialBalance"+++instance (KnownNat precision, Aeson.ToJSON account, Aeson.ToJSON event) => Aeson.ToJSON (TrialBalance precision account event) where+  toJSON = Aeson.genericToJSON $ commonAesonOptions "trialBalance"+++-- | Data definition for a trial balance item.+data TrialBalanceItem (precision :: Nat) account event = TrialBalanceItem+  { trialBalanceItemLedger  :: !(Ledger precision account event)+  , trialBalanceItemBalance :: !(Balance precision)+  }+  deriving (Eq, Generic, Show)+++instance (KnownNat precision, Aeson.FromJSON account, Aeson.FromJSON event) => Aeson.FromJSON (TrialBalanceItem precision account event) where+  parseJSON = Aeson.genericParseJSON $ commonAesonOptions "trialBalanceItem"+++instance (KnownNat precision, Aeson.ToJSON account, Aeson.ToJSON event) => Aeson.ToJSON (TrialBalanceItem precision account event) where+  toJSON = Aeson.genericToJSON $ commonAesonOptions "trialBalanceItem"+++-- | Returns the amount of the trial balance item. This is a simple conversion+-- from 'Balance' to 'Amount'.+trialBalanceItemAmount+  :: KnownNat precision+  => TrialBalanceItem precision account event+  -> Amount precision+trialBalanceItemAmount = amountFromBalance . trialBalanceItemBalance+++-- | Given a general ledger, prepares the trial balance.+prepareTrialBalance+  :: KnownNat precision+  => GeneralLedger precision account event+  -> TrialBalance precision account event+prepareTrialBalance = TrialBalance . fmap mkTrialBalanceItem . generalLedgerLedgers+++-- | Converts a 'Ledger' to a 'TrialBalanceItem'.+mkTrialBalanceItem+  :: KnownNat precision+  => Ledger precision account event+  -> TrialBalanceItem precision account event+mkTrialBalanceItem ledger =+  TrialBalanceItem ledger (ledgerClosing ledger)+++-- | Computes the trial balance totals as a 2-tuple of total debits and total+-- credits.+trialBalanceTotals+  :: KnownNat precision+  => TrialBalance precision account event+  -> (Balance precision, Balance precision)+trialBalanceTotals (TrialBalance items) =+  let+    itemsFromDb = amountFromBalance . trialBalanceItemBalance <$> filter ((==) SideDebit . balanceSide . trialBalanceItemBalance) items+    itemsFromCr = amountFromBalance . trialBalanceItemBalance <$> filter ((==) SideCredit . balanceSide . trialBalanceItemBalance) items+    totalDb = foldl updateBalance (Balance SideDebit 0) itemsFromDb+    totalCr = foldl updateBalance (Balance SideCredit 0) itemsFromCr+  in+    (totalDb, totalCr)
src/Haspara/Quantity.hs view
@@ -17,7 +17,8 @@ import           GHC.TypeLits               (KnownNat, Nat, natVal, type (+)) import qualified Language.Haskell.TH.Syntax as TH import qualified Numeric.Decimal            as D-import           Refined                    (NonNegative, Refined)+import           Refined                    (NonNegative, Refined, unrefine)+import           Refined.Unsafe             (unsafeRefine)   -- * Data Definition@@ -293,6 +294,32 @@ -- 0.1764 timesLossless :: (KnownNat s, KnownNat k) => Quantity s -> Quantity k -> Quantity (s + k) timesLossless (MkQuantity d1) (MkQuantity d2) = MkQuantity (D.timesDecimal d1 d2)+++-- | Returns the total of a list of unsigned quantities.+--+-- >>> sumUnsignedQuantity [] :: UnsignedQuantity 2+-- Refined 0.00+sumUnsignedQuantity+  :: KnownNat s+  => [UnsignedQuantity s]+  -> UnsignedQuantity s+sumUnsignedQuantity = unsafeRefine . sum . fmap unrefine+++-- | Returns the absolute value of the 'Quantity' as 'UnsignedQuantity'.+--+-- >>> abs (mkQuantity 0.42 :: Quantity 2)+-- 0.42+-- >>> abs (mkQuantity 0 :: Quantity 2)+-- 0.00+-- >>> abs (mkQuantity (-0.42) :: Quantity 2)+-- 0.42+absQuantity+  :: KnownNat s+  => Quantity s+  -> UnsignedQuantity s+absQuantity = unsafeRefine . abs   -- * Internal