Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module Tables.BuySellTable
module Features.BuySell.BuySellTable
( createBuySellTable,
)
where
Expand Down
104 changes: 104 additions & 0 deletions app/Features/GroupByCompany/GroupByCompanyArgParse.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
module Features.GroupByCompany.GroupByCompanyArgParse (ParsedGroupBuySellCliFlags (..), parseCliFlags, defaultGroupByCompanySortOption, allSortableColumnsKeys, sortableColumnToStr) where

import Data.Char (toLower)
import Data.List (intercalate)
import qualified Data.Map as Map
import qualified Data.Text as T
import Features.GroupByCompany.GroupByCompanyTypes
import Text.Read (readMaybe)
import Types.Table.SortOption (SortOption (..), SortOrder (..), allSortKeys, parseOrder)

data ParsedGroupBuySellCliFlags = ParsedGroupBuySellCliFlags
{ sortOptions :: !GroupByCompanySortOption,
limit :: !(Maybe Int)
}

defaultFlags :: ParsedGroupBuySellCliFlags
defaultFlags =
ParsedGroupBuySellCliFlags
{ sortOptions = defaultGroupByCompanySortOption,
limit = Nothing
}

defaultGroupByCompanySortOption :: GroupByCompanySortOption
defaultGroupByCompanySortOption = GroupByCompanySortOption (SortOption Company Descending)

parseCliFlags :: [String] -> Either String ParsedGroupBuySellCliFlags
parseCliFlags args = go args defaultFlags
where
go [] acc = Right acc
go ("--sort" : col : ord : rest) acc =
case parseGroupByCompanySortOpts (col ++ " " ++ ord) of
Right opt -> go rest acc {sortOptions = opt}
Left err -> Left $ "Failed to parse --sort flag: " ++ err
go ("--sort" : col : rest) acc =
case parseGroupByCompanySortOpts col of
Right opt -> go rest acc {sortOptions = opt}
Left err -> Left $ "Failed to parse --sort flag: " ++ err
go ("--limit" : num : rest) acc =
case parseLimit num of
Just opt -> go rest acc {limit = Just opt}
Nothing ->
Left $
"Failed to parse --limit flag due to invalid value: \""
++ num
++ "\". Expected an integear larger than 1"

parseGroupByCompanySortOpts :: String -> Either String GroupByCompanySortOption
parseGroupByCompanySortOpts input =
case words (map toLower input) of
[col, ord] -> do
column <- parseCol col
order <- parseOrd ord
Right $ GroupByCompanySortOption (SortOption column order)
-- Use default ordering
[col] -> do
column <- parseCol col
let order' = order (unGroupByCompanySortOption defaultGroupByCompanySortOption)
Right $ GroupByCompanySortOption (SortOption column order')
_ -> Left "Invalid input format. Use: <column> <order> (e.g., 'profit desc')."
where
parseCol col =
maybe
( Left $
"Invalid column \""
++ col
++ "\". Please use one of the supported options: "
++ intercalate ", " allSortableColumnsKeys
)
Right
$ parseSortableColumnMap col

parseOrd ord =
maybe
( Left $
"Invalid order: \""
++ ord
++ "\". Please use one of the support options: "
++ intercalate ", " allSortKeys
)
Right
(parseOrder ord)

parseLimit :: String -> Maybe Int
parseLimit str = do
n <- readMaybe str
if n > 0 then Just n else Nothing

strToSortableColumnMap :: Map.Map String SortableColumn
strToSortableColumnMap =
Map.fromList $
map (\col -> (lowercaseFirst (show col), col)) [minBound .. maxBound]

parseSortableColumnMap :: String -> Maybe SortableColumn
parseSortableColumnMap key = Map.lookup key strToSortableColumnMap

sortableColumnToStr :: SortableColumn -> String
sortableColumnToStr col = lowercaseFirst (show col)

allSortableColumnsKeys :: [String]
allSortableColumnsKeys = Map.keys strToSortableColumnMap

lowercaseFirst :: String -> String
lowercaseFirst [] = []
lowercaseFirst (x : xs) = toLower x : xs
167 changes: 167 additions & 0 deletions app/Features/GroupByCompany/GroupByCompanyTable.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
module Features.GroupByCompany.GroupByCompanyTable
( createGroupByCompanyTable,
defaultGroupByCompanySortOption,
)
where

import Calc
( calcNumBought,
calcNumSold,
calcRemainingAmount,
calcTotalBuySellProfit,
calcTotalDividendProfit,
calcTotalProfitForCompany,
)
import Data.Char (toLower)
import Data.Function (on)
import Data.List (sortBy)
import Data.Text (Text)
import qualified Data.Text as T
import Features.GroupByCompany.GroupByCompanyArgParse
import Features.GroupByCompany.GroupByCompanyTypes
import PrettyPrint
( createPrettyTable,
intToText,
moneyToText,
)
import Types.PrintableCell
( FixedWidth (FixedWidth, l, s),
PrintableCell
( textAlign,
width
),
TextAlign
( LeftAlign,
RightAlign
),
createCellFromText,
createFilledCell,
createFixedCell,
)
import Types.Table.SortOption (SortOption (..), SortOrder (..), parseOrder)
import Types.Transaction.GenericTransaction
( getCompany,
)
import Types.Transaction.ParsedTransaction
( ParsedTransaction,
extractBuySellRows,
extractDividendRows,
extractGenericTransaction,
)
import Types.UiSize (UiSize)
import Types.UtilTypes
( SortedByDateList
( getSortedByDateList
),
)

createGroupByCompanyTable ::
UiSize -> Int -> [String] -> [SortedByDateList ParsedTransaction] -> Text
createGroupByCompanyTable uiSize termWidth cliFlags rows =
case parseCliFlags cliFlags of
Left errMsg -> T.pack errMsg
Right parsedCliFlags ->
let header = groupByCompanyHeader
mapContent row = createGroupByCompanyRow row header
groupByCompanyContentRows = map mapContent rows

-- apply cli flags

-- apply sort
sortedRows = sortGroupByCompanyRows (sortOptions parsedCliFlags) groupByCompanyContentRows

-- apply limit
limitedRows = case limit parsedCliFlags of
Just n -> take n sortedRows
Nothing -> sortedRows

content = map (groupByCompanyRowToCells header) limitedRows
spacing = map width header
in createPrettyTable
uiSize
termWidth
header
content
spacing

-- | Företag | Köpt | Sålt | Nuv. balans | Vinst (kr) | Vinst sålda (kr) |
groupByCompanyHeader :: [PrintableCell]
groupByCompanyHeader =
[ createFilledCell "Företag" LeftAlign,
createFixedCell "Köpt" FixedWidth {s = 10, l = Just 10} RightAlign,
createFixedCell "Sålt" FixedWidth {s = 10, l = Just 10} RightAlign,
createFixedCell
"Nuv. balans"
FixedWidth {s = 10, l = Just 12}
RightAlign,
createFixedCell "Vinst (kr)" FixedWidth {s = 10, l = Just 12} RightAlign,
createFixedCell
"Utdelning (kr)"
FixedWidth {s = 10, l = Just 14}
RightAlign,
createFixedCell
"Vinst sålda (kr)"
FixedWidth {s = 15, l = Just 17}
RightAlign
]

-- | Transforms a list of parsed transactions that shoud belong to the same company into printable
-- cells
createGroupByCompanyRow ::
SortedByDateList ParsedTransaction -> [PrintableCell] -> GroupByCompanyContentRow
createGroupByCompanyRow rows header =
let company =
getCompany . extractGenericTransaction . head $
getSortedByDateList
rows
buySellRows = extractBuySellRows rows
dividendRows = extractDividendRows rows
in GroupByCompanyContentRow
{ company = company,
bought = calcNumBought buySellRows,
sold = calcNumSold buySellRows,
currentAmmount = calcRemainingAmount (getSortedByDateList rows),
profit = calcTotalBuySellProfit buySellRows,
dividence = calcTotalDividendProfit dividendRows,
profitForSold = calcTotalProfitForCompany rows
}

sortGroupByCompanyRows ::
GroupByCompanySortOption ->
[GroupByCompanyContentRow] ->
[GroupByCompanyContentRow]
sortGroupByCompanyRows (GroupByCompanySortOption (SortOption column order)) rows =
case column of
Company -> applySort (compare `on` (T.toLower . company)) rows
Bought -> applySort (compare `on` bought) rows
Sold -> applySort (compare `on` sold) rows
CurrentAmount -> applySort (compare `on` currentAmmount) rows
Profit -> applySort (compare `on` profit) rows
Dividend -> applySort (compare `on` dividence) rows
ProfitForSold -> applySort (compare `on` profitForSold) rows
where
applySort cmp = case order of
Ascending -> sortBy cmp
Descending -> sortBy (flip cmp)

groupByCompanyRowToCells :: [PrintableCell] -> GroupByCompanyContentRow -> [PrintableCell]
groupByCompanyRowToCells header row =
let tableRows =
[ company row,
intToText $ bought row,
intToText $ sold row,
intToText $ currentAmmount row,
moneyToText $ profit row,
moneyToText $ dividence row,
moneyToText $ profitForSold row
]
in -- Use info in header to create cells with spacing
zipWith
( \title headerCell ->
createCellFromText
title
(width headerCell)
(textAlign headerCell)
)
tableRows
header
28 changes: 28 additions & 0 deletions app/Features/GroupByCompany/GroupByCompanyTypes.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module Features.GroupByCompany.GroupByCompanyTypes (SortableColumn (..), GroupByCompanyContentRow (..), GroupByCompanySortOption (..)) where

import Data.Text (Text)
import Types.Money (Money (..))
import Types.Table.SortOption (SortOption (..), SortOrder (..))

data SortableColumn
= Company
| Bought
| Sold
| CurrentAmount
| Profit
| Dividend
| ProfitForSold
deriving (Show, Read, Enum, Bounded, Eq, Ord)

newtype GroupByCompanySortOption = GroupByCompanySortOption {unGroupByCompanySortOption :: SortOption SortableColumn}

-- columns: |Företag|Köpt|Sålt|Nuv. balans|Vinst (kr)|Utdelning (kr)| Vinst sålda (kr)|
data GroupByCompanyContentRow = GroupByCompanyContentRow
{ company :: !Text,
bought :: !Int,
sold :: !Int,
currentAmmount :: !Int,
profit :: !Money,
dividence :: !Money,
profitForSold :: !Money
}
Binary file modified app/Main
Binary file not shown.
Loading
Loading