-
Notifications
You must be signed in to change notification settings - Fork 26
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
Fix11k #38
Changes from 8 commits
bb301b4
9b4d5a5
e5e30ed
9b7f36c
fd50369
558333e
3024e98
8f554dd
ad022d5
d825734
a881fc2
b6c8ad3
5f538aa
da0a553
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
|
@@ -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) | ||
|
@@ -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 | ||
ClosureExportSettings(source, destination) | ||
} | ||
} | ||
abstract class Constraint { | ||
def check(appState: AppState): Boolean | ||
|
@@ -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)) | ||
|
@@ -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 _) | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't need this class 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)) | ||
|
@@ -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 => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rename this val to |
||
val srcEntries = amounts.toSeq.filter{ name => a.isClosureMatchingSrc(name._1.fullPathStr) } | ||
val destEntries = amounts.toSeq.filter{ name => a.isClosureMatchingDest(name._1.fullPathStr) } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
} | ||
val closureEntries = closureEntry.head | ||
val sortedByName1 = closureEntries.toSeq.sortBy(_.accountName.toString) | ||
val ledger = LedgerExportData(latestDate, sortedByName) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rename this val to |
||
val closure = LedgerExportData(latestDate, sortedByName1) | ||
Seq(exportLedgerClosure(ledger, closure)) | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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)) => | ||
|
@@ -40,10 +41,11 @@ class ProcessorTest extends FlatSpec with Matchers with Inside { | |
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
/* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
@@ -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) | ||
|
||
|
@@ -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) | ||
|
||
|
@@ -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) | ||
|
||
|
@@ -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) | ||
} | ||
} | ||
} | ||
} | ||
|
||
} | ||
|
||
} */ | ||
} |
There was a problem hiding this comment.
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.