Skip to content

Commit

Permalink
Merge 3a1bde6 into 6e13158
Browse files Browse the repository at this point in the history
  • Loading branch information
jaa127 authored Dec 27, 2016
2 parents 6e13158 + 3a1bde6 commit 42161b6
Show file tree
Hide file tree
Showing 14 changed files with 355 additions and 18 deletions.
2 changes: 1 addition & 1 deletion base/src/main/scala/co/uproot/abandon/Ast.scala
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ case class Date(year: Int, month: Int, day: Int) {
/**
* @return ISO-8601 week with week day (2010-01-01 => 2009-W53-5)
*/
def formatISO8601WeekDay = {
def formatISO8601WeekDate = {
val frmtISOWeek = java.time.format.DateTimeFormatter.ISO_WEEK_DATE
val jDate = java.time.LocalDate.of(year, month, day)

Expand Down
50 changes: 48 additions & 2 deletions base/src/main/scala/co/uproot/abandon/Config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,8 @@ object SettingsHelper {
val showZeroAmountAccounts = config.optional("showZeroAmountAccounts") { _.getBoolean(_) }.getOrElse(false)
BalanceReportSettings(title, accountMatch, outFiles, showZeroAmountAccounts)
case "register" =>
RegisterReportSettings(title, accountMatch, outFiles)
val groupBy = GroupBy(config.optional("groupBy"){_.getString(_)})
RegisterReportSettings(title, accountMatch, outFiles, groupBy)
case "book" =>
val account = config.getString("account")
BookReportSettings(title, account, outFiles)
Expand Down Expand Up @@ -406,7 +407,52 @@ case class BalanceReportSettings(
showZeroAmountAccounts: Boolean) extends ReportSettings(_title, _accountMatch, _outFiles) {
}

case class RegisterReportSettings(_title: String, _accountMatch: Option[Seq[String]], _outFiles: Seq[String]) extends ReportSettings(_title, _accountMatch, _outFiles) {
/**
* GroupBy selectors
*/
sealed trait GroupBy
sealed case class GroupByYear() extends GroupBy
sealed case class GroupByMonth() extends GroupBy
sealed case class GroupByDay() extends GroupBy
sealed case class GroupByIsoWeek() extends GroupBy
sealed case class GroupByIsoWeekDate() extends GroupBy

object GroupBy {
def apply(groupBy: Option[String]): GroupBy = {
groupBy match {
case Some("year") => GroupByYear()
case Some("month") => GroupByMonth()
case Some("day") => GroupByDay()
case Some("isoWeek") => GroupByIsoWeek()
case Some("isoWeekDate") => GroupByIsoWeekDate()
/* default */
case None => GroupByMonth()
/* Error*/
case _ => throw new SettingsError(
"GroupBy: unknown operator. Valid operators are: year, month, day, isoWeek, isoWeekDate")
}
}
}

case class RegisterReportSettings(_title: String, _accountMatch: Option[Seq[String]], _outFiles: Seq[String], groupBy: GroupBy) extends ReportSettings(_title, _accountMatch, _outFiles) {

/**
* Get groupOp, either Int or String based groupByOp
*
* @return Left(groupIntOp), Right(groupStrOp)
*/
def groupOp: Either[(PostGroup) => Int, (PostGroup) => String] = {
groupBy match {
/* groupIntOp */
case GroupByDay() => Left({ case txn: PostGroup => txn.date.toInt })
case GroupByMonth() => Left({ case txn: PostGroup => txn.date.toIntYYYYMM })
case GroupByYear() => Left({ case txn: PostGroup => txn.date.toIntYYYY })

/* groupStrOp */
case GroupByIsoWeek() => Right({ case txn: PostGroup => txn.date.formatISO8601Week })
case GroupByIsoWeekDate() => Right({ case txn: PostGroup => txn.date.formatISO8601WeekDate })
}
}
}

case class BookReportSettings(_title: String, account: String, _outFiles: Seq[String]) extends ReportSettings(_title, Some(Seq(account)), _outFiles) {
Expand Down
55 changes: 43 additions & 12 deletions base/src/main/scala/co/uproot/abandon/Report.scala
Original file line number Diff line number Diff line change
Expand Up @@ -102,44 +102,75 @@ object Reports {
BalanceReport(leftRender, rightRender, "%" + leftAmountWidth + ".2f" format leftTotal, "%" + rightAmountWidth + ".2f = %s" format (rightTotal, totalStr))
}


/**
* Formats int date used for grouping to text, based on it's resolution
*
* @param groupingDate
* @return Date as string with correct resolution
*/
private def formatGroupingDate(groupingDate: Int) = {
val date = Date.fromInt(groupingDate)
if (date.hasDayResolution) {
date.formatISO8601Ext
}
else {
else if (date.hasMonthResolution) {
s"${date.year} / ${Helper.monthLabels(date.month - 1)}"
}
else {
s"${date.year}"
}
}

def registerReport(state: AppState, reportSettings: RegisterReportSettings): Seq[RegisterReportGroup] = {
val postGroups = state.accState.postGroups.filter(_.children.exists(c => reportSettings.isAccountMatching(c.name.fullPathStr)))
val monthlyGroups = postGroups.groupBy(d => d.date.toIntYYYYMM).toSeq.sortBy(_._1)
val txns = state.accState.postGroups.filter(_.children.exists(c => reportSettings.isAccountMatching(c.name.fullPathStr)))

/**
* Grouping is done by truncated int numbers. The end result (groups) is
* Seq[(String, Seq[PostGroup])] so we need mapping from truncated intDate -> String
* For dates this fairly trivial, but for iso weeks and ISO week dates it gets complicated.
* For this reason, for now, let's use two different groupBy operators: Int and String based
*
* With groupStrOp we can group by non-trivial dates, e.g. by ISO week dates,
* and we don't have to convert the result, because it is already String
*/
val txnGroups: Seq[(String, Seq[PostGroup])] = reportSettings.groupOp match {
case Left(groupIntOp) => {
txns.groupBy(groupIntOp).toSeq.sortBy(_._1).map({
// groupIntOp result (Int, Seq) has to be converted to (String, Seq)
case (intDate, groups) => (formatGroupingDate(intDate), groups)
})
}
case Right(groupTxtOp) => {
// this is (String, Seq) already
txns.groupBy(groupTxtOp).toSeq.sortBy(_._1)
}
}

var reportGroups = Seq[RegisterReportGroup]()
var groupState = new AccountState()

monthlyGroups.foreach {
case (month, monthlyGroup) =>
monthlyGroup foreach { g =>
txnGroups.foreach {
case (groupName, txnGroup) =>
txnGroup foreach { g =>
groupState.updateAmounts(g)
}
val matchingNames = monthlyGroup.flatMap(_.children.map(_.name)).toSet.filter(name => reportSettings.isAccountMatching(name.fullPathStr))
val matchingNames = txnGroup.flatMap(_.children.map(_.name)).toSet.filter(name => reportSettings.isAccountMatching(name.fullPathStr))
val amounts = groupState.amounts
val matchingAmounts = amounts.filter { case (accountName, amount) => matchingNames.contains(accountName) }

val totalDeltasPerAccount = matchingAmounts.map {
case (accountName, amount) =>
val myTxns = monthlyGroup.flatMap(_.children).filter(_.name equals accountName)
val render = "%-50s %20.2f %20.2f" format (accountName, sumDeltas(myTxns), amount)
(accountName, myTxns, render)
val myPostings = txnGroup.flatMap(_.children).filter(_.name equals accountName)
val render = "%-50s %20.2f %20.2f" format (accountName, sumDeltas(myPostings), amount)
(accountName, myPostings, render)
}

val sortedTotalDeltasPerAccount = totalDeltasPerAccount.toSeq.sortBy(_._1.toString)

reportGroups :+= RegisterReportGroup(
formatGroupingDate(month),
sortedTotalDeltasPerAccount.map { case (accountName, txns, render) => RegisterReportEntry(txns, render) })
groupName,
sortedTotalDeltasPerAccount.map { case (accountName, postings, render) => RegisterReportEntry(postings, render) })
}
reportGroups
}
Expand Down
2 changes: 1 addition & 1 deletion base/src/test/scala/co/uproot/abandon/AstTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ class AstTest extends FlatSpec with Matchers {
dates.forall({
case (date, refWeek, refWeekDay) => {
date.formatISO8601Week == refWeek &&
date.formatISO8601WeekDay == refWeekDay
date.formatISO8601WeekDate == refWeekDay
}
}) should be(true)
}
Expand Down
89 changes: 89 additions & 0 deletions base/src/test/scala/co/uproot/abandon/GroupByTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package co.uproot.abandon

import org.scalatest.{FlatSpec, Matchers}

class GroupByTest extends FlatSpec with Matchers {

behavior of "GroupBy"
it should "handle unknown groupBy" in {
assertThrows [SettingsError] {
GroupBy(Option("asdf"))
}
}
it should "handle default" in {
GroupBy(None) shouldBe an[GroupByMonth]
}
it should "handle year" in {
GroupBy(Option("year")) shouldBe an[GroupByYear]
}
it should "handle month" in {
GroupBy(Option("month")) shouldBe an[GroupByMonth]
}
it should "handle day" in {
GroupBy(Option("day")) shouldBe an[GroupByDay]
}
it should "handle week" in {
GroupBy(Option("isoWeek")) shouldBe an[GroupByIsoWeek]
}
it should "handle week date" in {
GroupBy(Option("isoWeekDate")) shouldBe an[GroupByIsoWeekDate]
}


behavior of "groupByOp"
it should "select toIntYYYY for year" in {
val regCfg = RegisterReportSettings("", None, Nil, GroupBy(Option("year")))
val date = Date(2016, 12, 21)
val postGroup = new PostGroup(Nil, null, date, None, None, Nil)

val str = regCfg.groupOp match {
case Left(groupBy) => groupBy(postGroup)
case _ => fail("wrong way")
}
str should be(date.toIntYYYY)
}
it should "select toIntYYYYMM for month" in {
val regCfg = RegisterReportSettings("", None, Nil, GroupBy(Option("month")))
val date = Date(2016, 12, 21)
val postGroup = new PostGroup(Nil, null, date, None, None, Nil)

val str = regCfg.groupOp match {
case Left(groupBy) => groupBy(postGroup)
case _ => fail("wrong way")
}
str should be(date.toIntYYYYMM)
}
it should "select toInt for day" in {
val regCfg = RegisterReportSettings("", None, Nil, GroupBy(Option("day")))
val date = Date(2016, 12, 21)
val postGroup = new PostGroup(Nil, null, date, None, None, Nil)

val str = regCfg.groupOp match {
case Left(groupBy) => groupBy(postGroup)
case _ => fail("wrong way")
}
str should be(date.toInt)
}
it should "select formatISO8601Week for iso week" in {
val regCfg = RegisterReportSettings("", None, Nil, GroupBy(Option("isoWeek")))
val date = Date(2016, 12, 21)
val postGroup = new PostGroup(Nil, null, date, None, None, Nil)

val str = regCfg.groupOp match {
case Right(groupBy) => groupBy(postGroup)
case _ => fail("wrong way")
}
str should be(date.formatISO8601Week)
}
it should "select formatISO8601WeekDate for iso week date" in {
val regCfg = RegisterReportSettings("", None, Nil, GroupBy(Option("isoWeekDate")))
val date = Date(2016, 12, 21)
val postGroup = new PostGroup(Nil, null, date, None, None, Nil)

val str = regCfg.groupOp match {
case Right(groupBy) => groupBy(postGroup)
case _ => fail("wrong way")
}
str should be(date.formatISO8601WeekDate)
}
}
3 changes: 2 additions & 1 deletion gui/src/main/scala/co/uproot/abandon/BalanceReport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ object BalanceUIReport extends UIReport {
RegisterReportSettings(
selectedAccountNames.map(_.fullPathStr).mkString(","),
Some(selectedAccountPatterns),
Nil
Nil,
GroupByMonth()
)
CurrReports.addReport(appState, settings, regSettings, canClose = true)
}
Expand Down
2 changes: 1 addition & 1 deletion gui/src/main/scala/co/uproot/abandon/UI.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ object CurrReports {
BalanceUIReport.mkBalanceReport(appState, settings, balSettings)
case bookSettings: BookReportSettings =>
// TODO
RegUIReport.mkRegisterReport(appState, RegisterReportSettings(bookSettings.title, bookSettings.accountMatch, Nil))
RegUIReport.mkRegisterReport(appState, RegisterReportSettings(bookSettings.title, bookSettings.accountMatch, Nil, GroupByMonth()))
}
AbandonUI.tabPane.addOrSetTab(rs.title, reportRender, canClose)
}
Expand Down
17 changes: 17 additions & 0 deletions tests/sclT0006-groupby/g01.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
inputs += groupby.ledger

reports += {
title = "Balance Sheet"
type = balance
outFiles = ["-", "out.g01.balance.txt"]
accountMatch = ["^Assets.*", "^Expenses.*"]
}

reports += {
title = "Register"
type = register
outFiles = ["-", "out.g01.register.txt"]
groupBy = month
accountMatch = ["^Assets.*", "^Expenses.*"]
}

20 changes: 20 additions & 0 deletions tests/sclT0006-groupby/g01.ref.balance.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Balance Sheet

-197.00 Assets (-174)
-23.00 └╴MatchSubAccount
197.00 Expenses
2.00 ├╴E02
3.00 ├╴E03
5.00 ├╴E05
7.00 ├╴E07
11.00 ├╴E11
13.00 ├╴E13:matchme
23.00 ├╴E23
29.00 ├╴E29
31.00 ├╴E31
37.00 ├╴E37
36.00 └╴MatchSubAccount
17.00 ├╴E17
19.00 └╴E19
─────────────────────────────────────────────
0.00 0.00 = Zero
21 changes: 21 additions & 0 deletions tests/sclT0006-groupby/g01.ref.register.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Register

2016 / Jan
Assets -2.00 -2.00
Expenses:E02 2.00 2.00
2016 / Feb
Assets -104.00 -106.00
Assets:MatchSubAccount -23.00 -23.00
Expenses:E03 3.00 3.00
Expenses:E05 5.00 5.00
Expenses:E07 7.00 7.00
Expenses:E11 11.00 11.00
Expenses:E13:matchme 13.00 13.00
Expenses:E23 23.00 23.00
Expenses:E29 29.00 29.00
Expenses:MatchSubAccount:E17 17.00 17.00
Expenses:MatchSubAccount:E19 19.00 19.00
2016 / Mar
Assets -68.00 -174.00
Expenses:E31 31.00 31.00
Expenses:E37 37.00 37.00
17 changes: 17 additions & 0 deletions tests/sclT0006-groupby/g02.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
inputs += groupby.ledger

reports += {
title = "Balance Sheet"
type = balance
outFiles = ["-", "out.g02.balance.txt"]
accountMatch = ["^Assets.*", "^Expenses.*"]
}

reports += {
title = "Register"
type = register
outFiles = ["-", "out.g02.register.txt"]
groupBy = isoWeek
accountMatch = ["^Assets.*", "^Expenses.*"]
}

20 changes: 20 additions & 0 deletions tests/sclT0006-groupby/g02.ref.balance.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Balance Sheet

-197.00 Assets (-174)
-23.00 └╴MatchSubAccount
197.00 Expenses
2.00 ├╴E02
3.00 ├╴E03
5.00 ├╴E05
7.00 ├╴E07
11.00 ├╴E11
13.00 ├╴E13:matchme
23.00 ├╴E23
29.00 ├╴E29
31.00 ├╴E31
37.00 ├╴E37
36.00 └╴MatchSubAccount
17.00 ├╴E17
19.00 └╴E19
─────────────────────────────────────────────
0.00 0.00 = Zero
Loading

0 comments on commit 42161b6

Please sign in to comment.