public TableRowViewModel(IList <MoneyStateModel> item, List <MoneyColumnMetadataJsModel> headers, Dictionary <string, MoneyColumnMetadataJsModel> headersCached, Dictionary <MoneyColumnMetadataJsModel, Dictionary <DateTime, double> > paymentsToExempt) { var minDate = item.Select(v => v.When).Min(); var maxDate = item.Select(v => v.When).Max(); When = minDate.AddSeconds((maxDate - minDate).TotalSeconds / 2); When = When.Date.AddHours(12); var grouped = item.GroupBy(v => v.Column).ToDictionary(v => v.Key, s => s.OrderByDescending(t => t.When).First()); CalculatedCells = new Dictionary <MoneyColumnMetadataJsModel, CalculatedResult>(); foreach (var h in headers) { CalculatedResult cell = null; var money = grouped.GetValueOrDefault(h.Column); if (money == null && h.IsComputed) { cell = CalculatedResult.FromComputed(headersCached, h, CalculatedCells); } else if (money != null) { var adj = paymentsToExempt.GetValueOrDefault(h)?.GetValueOrDefault(money.When.Date.AddDays(-1)) ?? 0; cell = CalculatedResult.FromMoney(h, money, adj); } CalculatedCells[h] = cell ?? CalculatedResult.Missing(h); } // make a copy of dictionary to make it editable and to not lost references to dependencies CalculatedCells = CalculatedCells.ToDictionary(v => v.Key, v => v.Value); }
private TableViewModel( ObjectRepository repository) { Headers = repository.Set <MoneyColumnMetadataModel>().SortColumns().ToList(); Dictionary <string, MoneyColumnMetadataModel> headersCached = new Dictionary <string, MoneyColumnMetadataModel>(); foreach (var h in Headers) { if (!string.IsNullOrWhiteSpace(h.UserFriendlyName)) { headersCached[h.UserFriendlyName] = h; } headersCached[h.Provider + "/" + h.AccountName] = h; } Values = repository.Set <MoneyStateModel>() .GroupBy(x => x.When.Date) .OrderByDescending(v => v.Key) .Select(v => new TableRowViewModel(v.ToList(), Headers, headersCached)) .ToList(); for (int i = 0; i < Values.Count - 1; i++) { var row = Values[i]; row.Previous = Values[i + 1]; foreach (var value in row.Cells) { var previous = Values[i + 1]; value.PreviousValue = previous.Cells.FirstOrDefault(v => v.Column == value.Column); } } var markedAsOkCells = Enumerable.Empty <MoneyColumnMetadataModel>(); for (int i = Values.Count - 1; i >= 0; i--) { var row = Values[i]; var toAddMissing = markedAsOkCells.Except(row.Cells.Select(v => v.Column)).ToList(); foreach (var item in toAddMissing) { row.Cells.Add(CalculatedResult.Empty(item)); } markedAsOkCells = row.Cells.Where(v => v.Value != null && double.IsNaN(v.Value.Value)) .Select(v => v.Column).ToList(); } foreach (var rowViewModel in Values) { rowViewModel.CalculateItems(); } }
public override void Evaluate(Dictionary <MoneyColumnMetadataJsModel, CalculatedResult> dependencies) { if (!_evaluated) { _evaluated = true; dependencies.TryGetValue(_column, out var matchedDependency); Value = matchedDependency?.IsOk == true ? matchedDependency : CalculatedResult.ResolutionFail(_column, _column.IsComputed ? _column.UserFriendlyName : (_column.Provider + "/" + (_column.UserFriendlyName ?? _column.AccountName))); } }
public override void Evaluate(Dictionary <MoneyColumnMetadataJsModel, CalculatedResult> dependencies) { var match = parseRegex.Match(_source); var valueString = match.Groups["value"].Captures.FirstOrDefault()?.Value; var ccy = match.Groups["ccy"].Captures.FirstOrDefault()?.Value; var adjustmentString = match.Groups["adj"].Captures.FirstOrDefault()?.Value; valueString = valueString?.Replace(",", "."); adjustmentString = adjustmentString?.Replace(",", "."); var numberFormatInfo = new NumberFormatInfo { NumberDecimalSeparator = "." }; double.TryParse(valueString, NumberStyles.Any, numberFormatInfo, out var value); double.TryParse(adjustmentString, NumberStyles.Any, numberFormatInfo, out var adjustment); Value = CalculatedResult.FromComputed(_model, value, ccy, Enumerable.Empty <string>(), adjustment, ToString()); }
public TableRowViewModel(IList <MoneyStateModel> item, List <MoneyColumnMetadataModel> headers, Dictionary <string, MoneyColumnMetadataModel> headersCached) { When = item.OrderByDescending(v => v.When).Select(v => v.When).FirstOrDefault(); Cells = new List <CalculatedResult>(); foreach (var h in headers) { if (h.IsComputed) { Cells.Add(CalculatedResult.FromComputed(headersCached, h)); } else { var money = item.Where(v => v.Provider == h.Provider && v.AccountName == h.AccountName).OrderByDescending(v => v.When).FirstOrDefault(); if (money != null) { Cells.Add(CalculatedResult.FromMoney(h, money)); } } } }
public EmptyExpression(MoneyColumnMetadataJsModel model) { Value = CalculatedResult.Empty(model); }
public TableViewModel(ObjectRepository repository) { var headersDictionary = repository.Set <MoneyColumnMetadataModel>().SortColumns() .ToDictionary(v => new MoneyColumnMetadataJsModel(v), v => v); Headers = headersDictionary.Keys.ToList(); var headersCached = new Dictionary <string, MoneyColumnMetadataJsModel>(); foreach (var h in Headers) { if (!string.IsNullOrWhiteSpace(h.UserFriendlyName)) { headersCached[h.UserFriendlyName] = h; } headersCached[h.Provider + "/" + h.AccountName] = h; } var paymentsToExempt = repository.Set <PaymentModel>().Where(v => v.Kind == PaymentKind.Transfer).ToList().GroupBy(v => v.Column) .ToDictionary(v => new MoneyColumnMetadataJsModel(v.Key), v => { var d = new Dictionary <DateTime, double>(); foreach (var item in v) { d[item.When.Date] = d.GetValueOrDefault(item.When.Date, 0) + item.Amount; } return(d); }); var rows = repository.Set <MoneyStateModel>() .GroupBy(x => x.When.Date) .OrderByDescending(v => v.Key) .ToList(); var end = rows.Select(v => (DateTime?)v.Key.Date).FirstOrDefault(); foreach (var col in paymentsToExempt) { var cumulative = 0.0; DateTime start = col.Value.Keys.Min(); for (; start <= (end ?? start); start = start.AddDays(1)) { var curValue = col.Value.GetValueOrDefault(start, 0); if (Math.Abs(cumulative + curValue) >= 0.0001) { col.Value[start] = -(curValue + cumulative); cumulative += curValue; } } } Values = rows .Select(v => new TableRowViewModel(v.ToList(), Headers, headersCached, paymentsToExempt)) .ToList(); for (int i = 0; i < Values.Count - 1; i++) { var row = Values[i]; row.Previous = Values[i + 1]; foreach (var value in row.CalculatedCells.Values) { var previous = Values[i + 1]; value.PreviousValue = previous.CalculatedCells.GetValueOrDefault(value.Column); } } var markedAsOkCells = Enumerable.Empty <MoneyColumnMetadataJsModel>(); for (int i = Values.Count - 1; i >= 0; i--) { var row = Values[i]; var toAddMissing = markedAsOkCells.Except(row.CalculatedCells.Keys).ToList(); foreach (var item in toAddMissing) { row.CalculatedCells.Add(item, CalculatedResult.Empty(item)); } markedAsOkCells = row.CalculatedCells.Values.Where(v => !(v is ExpressionCalculatedResult) && v.Value != null && double.IsNaN(v.Value.Value)) .Select(v => v.Column).ToList(); } var columnsToCheck = Values.SelectMany(v => v.CalculatedCells.Keys).Distinct() .Where(v => !v.IsComputed).ToList(); for (int i = Values.Count - 1; i >= 0; i--) { var row = Values[i]; columnsToCheck = columnsToCheck.Except(row.CalculatedCells.Keys).ToList(); foreach (var item in columnsToCheck) { row.CalculatedCells.Add(item, CalculatedResult.Empty(item)); } } foreach (var r in Values) { foreach (var cell in r.CalculatedCells.Values.OfType <ExpressionCalculatedResult>()) { if (cell.IsOk && cell.Value != null) { repository.Add(new MoneyStateModel { Amount = cell.Value.Value, Ccy = cell.Ccy, Column = headersDictionary[cell.Column], When = r.When, Description = cell.TooltipWithoutAdjustment }); } } } }
public FailedToParseExpression(MoneyColumnMetadataJsModel model, string function) { _function = function; Value = CalculatedResult.ResolutionFail(model, function); }
public override void Evaluate(Dictionary <MoneyColumnMetadataJsModel, CalculatedResult> dependencies) { if (Left == null || Right == null) { Value = CalculatedResult.ResolutionFail(_model, _symbol); return; } Left.Evaluate(dependencies); Right.Evaluate(dependencies); var leftValue = Left.Value.Value; var rightValue = Right.Value.Value; bool leftIsNan = leftValue != null && double.IsNaN(leftValue.Value); bool rightIsNan = rightValue != null && double.IsNaN(rightValue.Value); if (leftIsNan && !rightIsNan) { leftValue = 0; } if (rightIsNan && !leftIsNan) { rightValue = 0; } IEnumerable <string> failedToResolve = Left.Value.FailedToResolve.Concat(Right.Value.FailedToResolve).ToList(); double?value = null; double adjustment = 0; string ccy = null; var leftValueAdj = Left.Value; var rightValueAdj = Right.Value; while (leftValueAdj?.Value != null && double.IsNaN(leftValueAdj.Value.Value)) { leftValueAdj = leftValueAdj.PreviousValue; } while (rightValueAdj?.Value != null && double.IsNaN(rightValueAdj.Value.Value)) { rightValueAdj = rightValueAdj.PreviousValue; } switch (_symbol) { case "??": value = leftValue ?? rightValue; ccy = SelectCcy(Left.Value.Ccy, Right.Value.Ccy); adjustment = leftValueAdj?.Adjustment ?? rightValueAdj?.Adjustment ?? 0; break; case "+": value = (leftValue ?? 0) + (rightValue ?? 0); ccy = SelectCcy(Left.Value.Ccy, Right.Value.Ccy); adjustment = (leftValueAdj?.Adjustment ?? 0) + (rightValueAdj?.Adjustment ?? 0); break; case "-": value = (leftValue ?? 0) - (rightValue ?? 0); ccy = SelectCcy(Left.Value.Ccy, Right.Value.Ccy); adjustment = (leftValueAdj?.Adjustment ?? 0) - (rightValueAdj?.Adjustment ?? 0); break; case "*": value = (leftValue ?? 0) * (rightValue ?? 0); adjustment = (leftValueAdj?.Adjustment ?? 0) * (rightValueAdj?.Adjustment ?? 0); ccy = SelectCcy(Left.Value.Ccy, Right.Value.Ccy); // TODO ccy? break; case "/": value = (leftValue ?? 0) / (rightValue ?? 0); var leftAdj = leftValueAdj?.Adjustment ?? 0; var rightAdj = rightValueAdj?.Adjustment ?? 0; if (Math.Abs(rightAdj) < double.Epsilon) { rightAdj = 1; } adjustment = leftAdj / rightAdj; ccy = SelectCcy(Left.Value.Ccy, Right.Value.Ccy); // TODO ccy? break; default: failedToResolve = new[] { _symbol }; break; } Value = CalculatedResult.FromComputed(_model, value, ccy, failedToResolve, adjustment, ToString()); }