package gw.financials
uses java.lang.IllegalStateException
uses java.util.Date
uses java.util.ArrayList
uses java.lang.Exception
uses gw.pl.currency.MonetaryAmount
uses java.util.List
class StandardTransactionReducer implements TransactionReducer{
var _defaultProrater : Prorater
var _allTransactions : List> = {}
var _fallbackReducer : TransactionReducer
construct (prorater : Prorater, fallback : TransactionReducer) {
_defaultProrater = prorater
_fallbackReducer = fallback
}
override function add(t : WrappedProratable) : TransactionReducer {
_allTransactions.add(t)
return this
}
override function reduce() : List> {
try {
return abstractReduce( \e -> { throw new IllegalStateException(e) })
} catch (e : Exception) {
if (_fallbackReducer == null) {
throw e //no fallback
}
_allTransactions.each( \t -> _fallbackReducer.add(t))
return _fallbackReducer.reduce()
}
}
function validate() : List /* error messages */ {
var errorMessages : List = {}
abstractReduce(\ msg -> errorMessages.add(msg))
return errorMessages
}
private function abstractReduce(error(message : String)) : List> {
var results = new ArrayList>()
var partitioned = _allTransactions.partition( \ t -> t.sameOriginalTransactionPartitioner())
partitioned.Values.each(\txns ->{
reduceBySameOriginalTransaction(txns, error, results)
})
enforceNonOverlap(results, error)
return results
}
private function reduceBySameOriginalTransaction(input : List>, error(message : String), result : List>) {
var segmenter = new DateRangeSegmenter>()
input.each( \t -> {segmenter.add( t.Start, t.End, t)})
segmenter.segment( \ start, end, active -> {
var original = active.firstWhere( \ w -> w.Original)
if (original == null) {
error("No original transactions")
} else if (active.Count == 1) {
result.add(original.subPeriod(start, end, prorate(start, end, original)))
} else {
var remainingAmount = active.map( \ w -> prorate(start, end, w) ).sumWithAtLeastOneNonNullCurrency()
if (not remainingAmount.IsZero) {
result.add(original.subPeriod(start, end, remainingAmount))
}
}
})
}
private function enforceNonOverlap(wrapped : List>, error(message : String)) {
var segmenter = new DateRangeSegmenter>()
wrapped.each( \t -> segmenter.add( t.Start, t.End, t))
segmenter.segment( \ start, end, active -> {
if (active.Count != 1) {
var iter = active.iterator()
error("There are multiple overlapping unreversed transactions, including at least:\n"
+ iter.next().toString() + "\n and " + iter.next().toString())
}
})
}
function prorate(start : Date, end : Date, transaction : WrappedProratable) : MonetaryAmount {
// Need to do this prorate operation the same way that TransactionAdjustmentCalculator does.
var _prorater = transaction.getProrater(_defaultProrater)
return TransactionAdjustmentCalculator.prorateTransaction(_prorater, start, end, transaction)
}
}