Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix11k #38

Closed
wants to merge 14 commits into from
33 changes: 29 additions & 4 deletions base/src/main/scala/co/uproot/abandon/Config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ object SettingsHelper {
val eodConstraints = config.optConfigList("eodConstraints").getOrElse(Nil).map(makeEodConstraints(_))
Right(Settings(inputs, eodConstraints, reports, ReportOptions(isRight), exports, Some(file)))
} catch {
case e: ConfigException => Left(e.getMessage)
case e: ConfigException => Left(e.getMessage)
}
} else {
Left("Config file not found: " + configFileName)
Expand Down Expand Up @@ -109,7 +109,9 @@ object SettingsHelper {
exportType match {
case "ledger" =>
val showZeroAmountAccounts = config.optional("showZeroAmountAccounts") { _.getBoolean(_) }.getOrElse(false)
LedgerExportSettings(accountMatch, outFiles, showZeroAmountAccounts)
val closureConfig = config.optConfigList("closures").getOrElse(Nil)
val closure = closureConfig.map(makeClosureSettings)
LedgerExportSettings(accountMatch, outFiles, showZeroAmountAccounts, closure)
case "xml" =>
val accountMatch = config.optional("accountMatch") { _.getStringList(_).asScala }
XmlExportSettings(accountMatch, outFiles)
Expand All @@ -118,6 +120,12 @@ object SettingsHelper {
throw new ConfigException.BadValue(config.origin, "type", message)
}
}

def makeClosureSettings(config: Config) = {
val source = config.getStringList("source").asScala
val destination = config.getStringList("destination").asScala
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

destination will be always one. Hence it should not be a list.

ClosureExportSettings(source, destination)
}
}
abstract class Constraint {
def check(appState: AppState): Boolean
Expand All @@ -127,7 +135,6 @@ trait SignChecker {
val accName: String
val signStr: String
val correctSign: (BigDecimal) => Boolean

def check(appState: AppState) = {
val txns = appState.accState.txns.filter(_.name.fullPathStr == accName)
val dailyDeltas = txns.groupBy(_.date.toInt).mapValues(s => Helper.sumDeltas(s))
Expand Down Expand Up @@ -173,17 +180,35 @@ trait AccountMatcher {
accountMatch.map(patterns => patterns.exists(name matches _)).getOrElse(true)
}
}
trait closureMatcher {
val source: Seq[String]
val destination: Seq[String]
def isClosureMatchingSrc(name: String) = {
source.exists(name matches _)
}
def isClosureMatchingDest(name: String) = {
destination.exists(name matches _)
}
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should reuse the AccountMatcher trait, but if you are not able to reuse it, then for now, move these functions to ClosureExportSettings and delete this trait.

I can later try to use AccountMatcher.


abstract class ReportSettings(val title: String, val accountMatch: Option[Seq[String]], val outFiles: Seq[String]) extends AccountMatcher {
}

abstract class ExportSettings(val accountMatch: Option[Seq[String]], val outFiles: Seq[String]) extends AccountMatcher {
}

abstract class ClosureExportSettings1(val source: Seq[String], val destination: Seq[String]) extends closureMatcher {
}

case class ClosureExportSettings(
_source: Seq[String],
_destination: Seq[String]) extends ClosureExportSettings1(_source, _destination) {
}

case class LedgerExportSettings(
_accountMatch: Option[Seq[String]],
_outFiles: Seq[String],
showZeroAmountAccounts: Boolean) extends ExportSettings(_accountMatch, _outFiles) {
showZeroAmountAccounts: Boolean, closure: Seq[ClosureExportSettings]) extends ExportSettings(_accountMatch, _outFiles) {
}

case class BalanceReportSettings(
Expand Down
32 changes: 27 additions & 5 deletions base/src/main/scala/co/uproot/abandon/Report.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Helper.{ Zero, maxElseZero, sumDeltas }
case class BalanceReportEntry(accName: Option[AccountName], render: String)
case class RegisterReportEntry(txns: Seq[DetailedTransaction], render: String)
case class RegisterReportGroup(groupTitle: String, entries: Seq[RegisterReportEntry])
case class exportLedgerClosure(ledger: LedgerExportData, closure: LedgerExportData)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need this class exportLedgerClosure.

LedgerExportData has a sequence of LedgerExportEntry. You can just use that to create a sequence of two entries: one for normal balance entry, and one for closure entry.

case class LedgerExportEntry(accountName: AccountName, amount: BigDecimal)
case class LedgerExportData(date: Date, ledgerEntries: Seq[LedgerExportEntry]) {
val maxNameLength = maxElseZero(ledgerEntries.map(_.accountName.toString.length))
Expand Down Expand Up @@ -171,21 +172,42 @@ object Reports {
* If a separate closure transaction is requested, this function returns two instances of LedgerExportData.
* Else, a single instance of LedgerExportData is returned.
*/
def ledgerExport(state: AppState, settings: Settings, reportSettings: LedgerExportSettings): Seq[LedgerExportData] = {
def ledgerExport(state: AppState, settings: Settings, reportSettings: LedgerExportSettings): Seq[exportLedgerClosure] = {
val sortedGroup = state.accState.txnGroups.sortBy(_.date.toInt)
if (sortedGroup.isEmpty) {
Nil
} else {
val latestDate = sortedGroup.last.date
val accAmounts = state.accState.amounts
val amounts = if(reportSettings.showZeroAmountAccounts) accAmounts else accAmounts.filter(_._2 != Zero)
val amounts =
if (reportSettings.showZeroAmountAccounts) {
accAmounts
} else {
accAmounts.filter(_._2 != Zero)
}
val entries = amounts.map {
case (accountName, amount) => LedgerExportEntry(accountName, amount)
}
val sortedByName = entries.toSeq.sortBy(_.accountName.toString)
Seq(LedgerExportData(
latestDate,
sortedByName))

val closureEntry = reportSettings.closure map { a =>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename this val to closureEntries since you will get more than one.

val srcEntries = amounts.toSeq.filter{ name => a.isClosureMatchingSrc(name._1.fullPathStr) }
val destEntries = amounts.toSeq.filter{ name => a.isClosureMatchingDest(name._1.fullPathStr) }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be exactly one destination. If there are zero or more than one destinations, then it should be reported as an error.

val srcClosure = srcEntries.map {
case (accountName, amount) => LedgerExportEntry(accountName, -amount)
}
val destClosure = destEntries.map {
case (accountName, amount) =>
val amount1 = srcClosure.map(_.amount).sum
LedgerExportEntry(accountName, -(amount1))
}
srcClosure ++: destClosure
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can change this expression to directly return LedgerExportData.

Thus, a separate transaction will be created for every closure, and it will also simplify the code below. You can simply write ledger :: closureEntries as the result of this function.

}
val closureEntries = closureEntry.head
val sortedByName1 = closureEntries.toSeq.sortBy(_.accountName.toString)
val ledger = LedgerExportData(latestDate, sortedByName)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename this val to balanceEntry

val closure = LedgerExportData(latestDate, sortedByName1)
Seq(exportLedgerClosure(ledger, closure))
}
}

Expand Down
77 changes: 68 additions & 9 deletions base/src/test/scala/co/uproot/abandon/ProcessorTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,17 @@ class ProcessorTest extends FlatSpec with Matchers with Inside {
case AbandonParser.Success(result, _) =>
val astEntries = result
val appState = Processor.process(astEntries)
val exports = Seq(LedgerExportSettings(None, Seq("balSheet12.txt"), false))

val exports = Seq(LedgerExportSettings(None, Seq("balSheet12.txt"), false,Nil))
val settings = Settings(Nil, Nil, Nil, ReportOptions(Nil), exports, None)

exports.foreach { exportSettings =>
exportSettings match {
case balSettings: LedgerExportSettings =>
val ledgerRep = Reports.ledgerExport(appState, settings, balSettings)
inside(ledgerRep) {
case List(LedgerExportData(date, txns)) =>
case ledgerRep : exportLedgerClosure => println(ledgerRep.ledger)
val ledger = ledgerRep.ledger
inside(ledger) {
case LedgerExportData(date, txns) =>
date should be(Date(2013, 1, 1))
inside(txns) {
case List(LedgerExportEntry(acc1, expr1), LedgerExportEntry(acc2, expr2)) =>
Expand All @@ -40,10 +41,11 @@ class ProcessorTest extends FlatSpec with Matchers with Inside {
}
}
}
}
}
}
}
}

/*
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you commenting out this test?

Please review your changes once before making a PR.

"Processor" should "not export a transaction with zero value when showZeroAmount is False" in {
val testInput = """
2013/1/1
Expand All @@ -56,7 +58,7 @@ class ProcessorTest extends FlatSpec with Matchers with Inside {
case AbandonParser.Success(result, _) =>
val astEntries = result
val appState = Processor.process(astEntries)
val exports = Seq(LedgerExportSettings(None, Seq("balSheet12.txt"), false))
val exports = Seq(LedgerExportSettings(None, Seq("balSheet12.txt"), false,Nil))

val settings = Settings(Nil, Nil, Nil, ReportOptions(Nil), exports, None)

Expand Down Expand Up @@ -92,7 +94,7 @@ class ProcessorTest extends FlatSpec with Matchers with Inside {
case AbandonParser.Success(result, _) =>
val astEntries = result
val appState = Processor.process(astEntries)
val exports = Seq(LedgerExportSettings(None, Seq("balSheet12.txt"), true))
val exports = Seq(LedgerExportSettings(None, Seq("balSheet12.txt"), true,Nil))

val settings = Settings(Nil, Nil, Nil, ReportOptions(Nil), exports, None)

Expand Down Expand Up @@ -120,13 +122,17 @@ class ProcessorTest extends FlatSpec with Matchers with Inside {

"Processor" should "export no transaction" in {
val testInput = """
2013/1/1
Expense -200
Cash
Bank:Current 0000
"""
val parseResult = AbandonParser.abandon(scanner(testInput))
inside(parseResult) {
case AbandonParser.Success(result, _) =>
val astEntries = result
val appState = Processor.process(astEntries)
val exports = Seq(LedgerExportSettings(None, Seq("balSheet12.txt"), false))
val exports = Seq(LedgerExportSettings(None, Seq("balSheet12.txt"), false,Nil))

val settings = Settings(Nil, Nil, Nil, ReportOptions(Nil), exports, None)

Expand All @@ -139,4 +145,57 @@ class ProcessorTest extends FlatSpec with Matchers with Inside {
}
}
}

"Processor" should "export transaction with closing balance" in {
val testInput = """
2013/1/1
Expense 4000
Income -1000
Equity 10000
"""
val parseResult = AbandonParser.abandon(scanner(testInput))
inside(parseResult) {
case AbandonParser.Success(result, _) =>
val astEntries = result
val appState = Processor.process(astEntries)
val source = Seq("Income", "Expense")
val destination = Seq("Equity")
val closure = Seq(ClosureExportSettings(source, destination))
val exports = Seq(LedgerExportSettings(None, Seq("balSheet12.txt"), false, closure))

val settings = Settings(Nil, Nil, Nil, ReportOptions(Nil), exports, None)

exports.foreach { exportSettings =>
exportSettings match {
case balSettings: LedgerExportSettings =>
val ledgerRep = Reports.ledgerExport(appState, settings, balSettings)
inside(ledgerRep) {
case List(LedgerExportData(date, txns), ClosureExportData(date1, txns1)) =>
date should be(Date(2013, 1, 1))
inside(txns) {
case List(LedgerExportEntry(acc1, expr1), LedgerExportEntry(acc2, expr2), LedgerExportEntry(acc3, expr3)) =>
acc1 should be (equityAccount)
acc2 should be (expenseAccount)
acc3 should be (incomeAccount)
expr1 should be (10000)
expr2 should be (-1000)
expr3 should be (4000)
}
date1 should be(Date(2013, 1, 1))
inside(txns1) {
case List(closureExportEntry(acc1, expr1), closureExportEntry(acc2, expr2), closureExportEntry(acc3, expr3)) =>
acc1 should be (equityAccount)
acc2 should be (expenseAccount)
acc3 should be (incomeAccount)
expr1 should be (3000)
expr2 should be (-4000)
expr3 should be (1000)
}
}
}
}

}

} */
}
2 changes: 2 additions & 0 deletions base/src/test/scala/co/uproot/abandon/TestHelper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ object TestHelper {
val expenseAccount = AccountName(Seq("Expense"))
val cashAccount = AccountName(Seq("Cash"))
val bankAccount = AccountName(Seq("Bank", "Current"))
val incomeAccount = AccountName(Seq("Income"))
val equityAccount = AccountName(Seq("Equity"))
}
19 changes: 14 additions & 5 deletions cli/src/main/scala/co/uproot/abandon/App.scala
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,23 @@ object AbandonApp extends App {
}
}

def exportAsLedger(reportWriter: ReportWriter, ledgerRep: Seq[LedgerExportData]) = {
ledgerRep foreach { reportGroup =>
val formatStr = "%-" + (reportGroup.maxNameLength+4) + "s %" + (reportGroup.maxAmountWidth+2) + ".2f"
reportWriter.println(reportGroup.date.formatYYYYMMDD)
reportGroup.ledgerEntries foreach { e =>
def exportAsLedger(reportWriter: ReportWriter, ledgerClosureRep: Seq[exportLedgerClosure]) = {
ledgerClosureRep foreach { ledgerClosureRec =>
val ledgerRec = ledgerClosureRec.ledger
reportWriter.println(ledgerRec.date.formatYYYYMMDD)
val formatStr = "%-" + (ledgerRec.maxNameLength + 4) + "s %" + (ledgerRec.maxAmountWidth + 2) + ".2f"
ledgerRec.ledgerEntries foreach { e =>
val render = formatStr format (e.accountName, e.amount)
reportWriter.println(" " + render)
}

val closureRec = ledgerClosureRec.closure
reportWriter.println(closureRec.date.formatYYYYMMDD)
val formatStr1 = "%-" + (closureRec.maxNameLength + 4) + "s %" + (closureRec.maxAmountWidth + 2) + ".2f"
closureRec.ledgerEntries foreach { e =>
val render = formatStr1 format (e.accountName, e.amount)
reportWriter.println(" " + render)
}
}
}
def printBookReport(reportWriter: ReportWriter, bookReportSettings: BookReportSettings, bookReport: Seq[RegisterReportGroup]) = {
Expand Down