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) } }