private void processAccount(GncAccount acct, int depth) { var balsnaps = acct.EnumSplits(false).Where(spl => spl.IsBalsnap).ToArray(); if (!balsnaps.Any()) { _report.SetValue(acct, Tr.PgLastBalsnap.ColLast, Tr.PgLastBalsnap.LastNever, "lastbalsnap_never"); } else { var lastsnap = balsnaps.OrderBy(spl => spl.Transaction.DatePosted).Last(); var lastsplit = acct.EnumSplits(false).Last(); if (lastsnap.Balsnap == 0 && object.ReferenceEquals(lastsplit, lastsnap) && lastsnap.AccountBalanceAfter == 0) { _report.SetValue(acct, Tr.PgLastBalsnap.ColLast, Tr.PgLastBalsnap.LastZero, "lastbalsnap_zero"); } else { int days = (int)(DateTime.Today - lastsnap.Transaction.DatePosted).TotalDays; _report.SetValue(acct, Tr.PgLastBalsnap.ColLast, Tr.PgLastBalsnap.LastNDaysAgo.Fmt(Tr.Language.GetNumberSystem(), days), makeCss(days)); } } foreach (var acctChild in acct.EnumChildren()) { processAccount(acctChild, depth + 1); } }
protected override AccountValueInfo GetAccountValue(GncAccount account, int depth) { return(new AccountValueInfo { Amount = ConvertTo == null?account.GetBalanceWithSubaccounts(_date) : account.GetBalanceConverted(_date, true, ConvertTo), }); }
protected override void ProcessAccount(GncAccount acct, int depth) { var intervalTotal = new GncMultiAmount(); int intervalDays = 0, intervalCount = 0; var earliest = acct.Book.EarliestDate; var latest = acct.Book.LatestDate; foreach (var interval in EnumIntervals()) { var tot = ConvertTo == null ? acct.GetTotalWithSubaccounts(interval) : acct.GetTotalConverted(interval, true, ConvertTo); if (Negate) tot.NegateInplace(); // Count only the full months for the purpose of averaging if (interval.Start >= earliest && interval.End <= latest) { intervalTotal.AddInplace(tot); intervalCount++; intervalDays += (int) (interval.End - interval.Start).TotalDays + 1; } SetReportAmount(Report, acct, interval, tot, ConvertTo != null, "/Trns?Acct={0}&Fr={1}&To={2}{3}".Fmt( acct.Path(":").UrlEscape(), interval.Start.Date.ToIsoStringOptimal(), interval.End.Date.ToIsoStringOptimal(), acct.EnumChildren().Any() ? "&SubAccts=true" : "")); } if (intervalCount > 1) { if (!Report.ContainsCol("average")) Report.AddCol("average", Tr.PgMonthlyTotals.ColAverage, "aw-col-average"); intervalTotal.MultiplyInplace(30.43m / intervalDays); // daily average * days in month SetReportAmount(Report, acct, "average", intervalTotal, ConvertTo != null); } }
private IEnumerable <object> generateAcctPath(GncAccount acct) { var path = acct.PathAsList(_account); if (path.Count == 0) { yield return(new I(Tr.PgTrns.ThisAccount)); } else { for (int i = 0; i < path.Count - 1; i++) { yield return(new A(path[i].Name) { class_ = "nocolor", href = Request.Url.WithQuery("Acct", path[i].Path(":")).ToHref() }); yield return(" : "); } if (path.Count > 0) { var pathlast = path[path.Count - 1]; yield return(new A(pathlast.Name) { class_ = "nocolor", href = Request.Url.WithQuery("Acct", pathlast.Path(":")).ToHref() }); } } }
public override object GetContent() { _account = GetAccount("Acct"); _report = new ReportAccounts(_account, Request, true, true); processAccount(_account, 0); return(new DIV(_report.GetHtml())); }
private void doAccount(GncAccount account, int depth) { ProcessAccount(account, depth); if (depth < MaxDepth || MaxDepth == -1) { foreach (var acctChild in account.EnumChildren()) { doAccount(acctChild, depth + 1); } } }
public ReportAccounts(GncAccount baseacct, HttpRequest request, bool autoAddAcct, bool autoAddCol) { _baseAcct = baseacct; _request = request; _autoAddAcct = autoAddAcct; _autoAddCol = autoAddCol; Table = new ReportTable(); _rowMap = new Dictionary <GncAccount, ReportTable.Row>(); _colMap = new Dictionary <object, ReportTable.Col>(); _colAcctName = Table.AddCol(Program.Tr.ReportTable_ColAccount, "acct_name"); }
private IEnumerable <object> listAccount(GncAccount acct, int depth) { yield return(new P("\u2003\u2003".Repeat(depth), new A(acct.Name) { href = Request.Url.WithQuery("Acct", acct.Path(":")).ToHref() })); foreach (var subacct in acct.EnumChildren()) { yield return(listAccount(subacct, depth + 1)); } }
protected override AccountValueInfo GetAccountValue(GncAccount account, int depth) { var interval = new DateInterval(_dateFr, _dateTo); return(new AccountValueInfo { Amount = ConvertTo == null?account.GetTotalWithSubaccounts(interval) : account.GetTotalConverted(interval, true, ConvertTo), Url = "/Trns?Acct={0}&Fr={1}&To={2}{3}".Fmt( account.Path(":").UrlEscape(), _dateFr.ToIsoStringOptimal(), _dateTo.ToIsoStringOptimal(), account.EnumChildren().Any() ? "&SubAccts=true" : ""), }); }
private void doAccount(GncAccount account, int depth) { var value = GetAccountValue(account, depth); SetReportAmount(Report, account, GetColumnCaption(), value.Amount, ConvertTo != null, value.Url); if (depth < MaxDepth || MaxDepth == -1) { foreach (var acctChild in account.EnumChildren()) { doAccount(acctChild, depth + 1); } } }
private void ensureAcct(GncAccount acct) { if (_rowMap.ContainsKey(acct)) { return; } if (_autoAddAcct) { AddAcct(acct); } else { throw new RTException("ReportAccounts: account \"{0}\" not yet defined and AutoAdd is disabled.".Fmt(acct.Path(":"))); } }
protected static void SetReportAmount(ReportAccounts report, GncAccount acct, object colref, GncMultiAmount amount, bool isConverted, string url = null) { if (amount.Count == 0) { report.SetValue(acct, colref, "-", ReportTable.CssClassNumber(0)); } else { var val = FormatCcys(amount, isConverted, whole: true); report.SetValue(acct, colref, url == null ? val : new A(val) { href = url }, isConverted ? ReportTable.CssClassNumber(amount.Single().Quantity) : ""); } }
protected override void ProcessAccount(GncAccount acct, int depth) { var earliest = acct.Book.EarliestDate; foreach (var interval in EnumIntervals()) { var bal = ConvertTo == null?acct.GetBalanceWithSubaccounts(interval.End) : acct.GetBalanceConverted(interval.End, true, ConvertTo); if (Negate) { bal.NegateInplace(); } SetReportAmount(Report, acct, interval, bal, ConvertTo != null); } }
public void AddAcct(GncAccount acct) { var row = Table.AddRow(); _rowMap.Add(acct, row); row.Depth = acct.Depth - _baseAcct.Depth; string indent = "\u2003\u2003".Repeat(acct.Depth - _baseAcct.Depth - 1); string name = (acct == _baseAcct) ? (Program.Tr.ReportTable_GrandTotal.Fmt(acct.Name)) : acct.Name; if (acct.EnumChildren().Any()) { row.SetValue(_colAcctName, new object[] { indent, new A(name) { class_ = "nocolor", href = _request.Url.WithQuery("Acct", acct.Path(":")).ToHref() } }); } else { row.SetValue(_colAcctName, indent + name); } }
public IEnumerable <object> GetAccountBreadcrumbs(string acctArgName, GncAccount account) { var path = account == null ? null : account.PathAsList(); if (account == null || path.Count == 0) { yield return new SPAN(account.Book.AccountRoot.Name) { class_ = "aw-breadcrumbs aw-current" } } ; else { yield return(new A(account.Book.AccountRoot.Name) { class_ = "aw-breadcrumbs", href = Request.Url.WithQuery(acctArgName, "").ToHref() }); yield return(" : "); foreach (var item in path.Take(path.Count - 1)) { yield return(new A(item.Name) { class_ = "aw-breadcrumbs", href = Request.Url.WithQuery(acctArgName, item.Path(":")).ToHref() }); yield return(" : "); } yield return(new SPAN(path.Last().Name) { class_ = "aw-breadcrumbs aw-current" }); } }
public void SetValue(GncAccount acct, object colref, object content, string cssclass) { ensureAcct(acct); ensureCol(colref); _rowMap[acct].SetValue(_colMap[colref], content, cssclass); }
private object renderFormWithResults() { _account = GetAccount("Acct"); var amtFmt = Request.GetValidated("AmtFmt", "#,0.00"); var statementText = ""; var regexText = ""; var btnPressed = btn.None; if (Request.Method == HttpMethod.Post) { statementText = Request.Post["stmt"].Value; if (Request.Post["rec_custom"].Value == Tr.PgReconcile.BtnReconcile) { btnPressed = btn.ReconcileCustom; regexText = Request.Post["rec_custom_regex"].Value; } else { btnPressed = Request.Post["rec_preset"].Value == Tr.PgReconcile.BtnReconcile ? btn.ReconcilePreset : btn.DeletePreset; regexText = Request.Post["rec_preset_regex"].Value; } } string regexName = ""; List <object> content = new List <object>(); if (btnPressed != btn.None) { var regexSplit = Regex.Match(regexText, @"^(.*):\s*(.*)$"); if (!regexSplit.Success) { throw new ValidationException(Tr.PgReconcile.Validation_ReconcilePreset, regexText, Tr.PgReconcile.Validation_ReconcilePresetMsg); } regexName = regexSplit.Groups[1].Value; string regexValue = regexSplit.Groups[2].Value; var regex = new Regex(regexValue); if (btnPressed == btn.ReconcileCustom) { Program.Settings.ReconcileRegexes[regexName] = regexValue; Program.Settings.SaveQuiet(); } else if (btnPressed == btn.DeletePreset) { Program.Settings.ReconcileRegexes.Remove(regexName); Program.Settings.SaveQuiet(); } if (btnPressed == btn.ReconcilePreset || btnPressed == btn.ReconcileCustom) { // Parse lines into transactions and create the augmented statement for display var statementSplits = new List <statementSplit>(); var linesContent = statementText.Split('\n').Select(line => { var m = regex.Match(line); if (m.Success) { try { var statementSplit = new statementSplit { Timestamp = parseapproxDate(m.Groups["date"]), Comment = m.Groups["comment"].Value, Amount = parseapproxAmount(m.Groups["dr"], m.Groups["cr"]), Balance = parseapproxBalance(m.Groups["balance"]) }; statementSplits.Add(statementSplit); try { var stmtDateM = Regex.Match(statementSplit.Comment, @"(\d\d)(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(\d\d)"); if (stmtDateM.Success) { var stmtDate = DateTime.ParseExact(stmtDateM.Value, "ddMMMyy", null); if (Math.Abs((stmtDate - statementSplit.Timestamp).TotalDays) < 7) { statementSplit.Timestamp = stmtDate; } } } catch { } // ignore: we already have the primary date } catch (Exception e) { return(new P(line, new SPAN(" - " + e.Message) { style = "color: #f00" }) { class_ = "reconcile-match" }); } } if (!m.Success) { return new P(line) { class_ = "reconcile-nomatch" } } ; else { return new P(line) { class_ = "reconcile-match" } }; }).ToArray(); content.Add(new H1(Tr.PgReconcile.HeaderParsedStatement)); content.Add(new DIV(linesContent) { class_ = "reconcile-parsed" }); statementSplits.Sort((spl1, spl2) => spl1.Timestamp.CompareTo(spl2.Timestamp)); if (statementSplits.Count == 0) { content.Add(new P("Warning: no valid transactions were extracted from the statement.")); } else { // Group splits in statement and in account by amount var fromDate = statementSplits.Min(tx => tx.Timestamp) - TimeSpan.FromDays(7); var toDate = statementSplits.Max(tx => tx.Timestamp) + TimeSpan.FromDays(7); var accountSplits = _account.EnumSplits(false).Where(spl => !spl.IsBalsnap && spl.Transaction.DatePosted >= fromDate && spl.Transaction.DatePosted <= toDate).ToList(); var groups = accountSplits .Select(spl => spl.Amount.Quantity).Concat(statementSplits.Select(tx => tx.Amount)) .Distinct() .ToDictionary( amount => amount, amount => new { InAccount = accountSplits.Where(spl => spl.Amount.Quantity == amount).ToList(), InStatement = statementSplits.Where(spl => spl.Amount == amount).ToList() }); // Match up the entries in each amount group and create a flat list var entries = new List <entry>(); foreach (var kvp in groups) { var val = kvp.Value; val.InAccount.Sort((spl1, spl2) => spl1.Transaction.DatePosted.CompareTo(spl2.Transaction.DatePosted)); val.InStatement.Sort((spl1, spl2) => spl1.Timestamp.CompareTo(spl2.Timestamp)); if (val.InAccount.Count == 1 && val.InStatement.Count == 1 && Math.Abs((val.InAccount[0].Transaction.DatePosted - val.InStatement[0].Timestamp).TotalDays) < 30) { // just treat as a match entries.Add(new entry { Amount = kvp.Key, InAccount = val.InAccount[0], InStatement = val.InStatement[0] }); } else if (val.InAccount.Count == 0 || val.InStatement.Count == 0) { // no match entries.AddRange(val.InAccount.Select(spl => new entry { Amount = kvp.Key, InAccount = spl })); entries.AddRange(val.InStatement.Select(spl => new entry { Amount = kvp.Key, InStatement = spl })); } else { // both lists have at least one entry, one of them has at least two. // First try an order-based approach bool done = false; if (val.InAccount.Count == val.InStatement.Count) { var diffs = val.InStatement.Select(spl => spl.Timestamp) .Zip(val.InAccount.Select(spl => spl.Transaction.DatePosted), (item1, item2) => Math.Abs((item1 - item2).TotalDays)); if (diffs.Max() <= 2) { entries.AddRange(val.InAccount.Zip(val.InStatement, (inAccount, inStatement) => new entry { Amount = kvp.Key, InAccount = inAccount, InStatement = inStatement })); done = true; } } // If didn't work fall back onto a more generic algorithm that's prone to re-order matches if (!done) { var bestmatchesS2A = val.InStatement.Select(inst => val.InAccount.Select((inacc, index) => new { Index = index, Diff = Math.Abs((inacc.Transaction.DatePosted - inst.Timestamp).TotalDays) } ) .Where(v => v.Diff < 30) .OrderBy(v => v.Diff) .ToList() ) .Select(options => (options.Count == 0) ? -1 : (options.Count == 1) ? options[0].Index : (options[1].Diff / options[0].Diff >= 2.5) ? options[0].Index : -1) .ToList(); var bestmatchesA2S = val.InAccount.Select(inacc => val.InStatement.Select((inst, index) => new { Index = index, Diff = Math.Abs((inacc.Transaction.DatePosted - inst.Timestamp).TotalDays) } ) .Where(v => v.Diff < 30) .OrderBy(v => v.Diff) .ToList() ) .Select(options => (options.Count == 0) ? -1 : (options.Count == 1) ? options[0].Index : (options[1].Diff / options[0].Diff >= 2.5) ? options[0].Index : -1) .ToList(); // Pick any matches that were optimal in both directions var bestmatches = bestmatchesS2A .Select((indexAcct, indexStmt) => new { indexAcct, indexStmt }) .Where(m => m.indexAcct >= 0 && bestmatchesA2S[m.indexAcct] == m.indexStmt) .ToList(); entries.AddRange(bestmatches.Select(m => new entry { Amount = kvp.Key, InAccount = val.InAccount[m.indexAcct], InStatement = val.InStatement[m.indexStmt] })); entries.AddRange(val.InAccount.Where((spl, index) => !bestmatches.Any(m => m.indexAcct == index)).Select(spl => new entry { Amount = kvp.Key, InAccount = spl })); entries.AddRange(val.InStatement.Where((spl, index) => !bestmatches.Any(m => m.indexStmt == index)).Select(spl => new entry { Amount = kvp.Key, InStatement = spl })); } } } // Sort the entries so as to preserve the original account split ordering entries.Sort((e1, e2) => { if (e1.InAccount != null && e2.InAccount != null) { return(accountSplits.IndexOf(e1.InAccount).CompareTo(accountSplits.IndexOf(e2.InAccount))); } else if (e1.InAccount == null && e2.InAccount != null) { return(e1.InStatement.Timestamp.CompareTo(e2.InAccount.Transaction.DatePosted)); } else if (e1.InAccount != null && e2.InAccount == null) { return(e1.InAccount.Transaction.DatePosted.CompareTo(e2.InStatement.Timestamp)); } else { return(e1.InStatement.Timestamp.CompareTo(e2.InStatement.Timestamp)); } }); // Create the table summarising the results var table = new ReportTable(); var colAcctDate = table.AddCol(Tr.PgReconcile.ColAcctDate); var colStmtDate = table.AddCol(Tr.PgReconcile.ColStmtDate); var colComment = table.AddCol(Tr.PgReconcile.ColComment); var colAmount = table.AddCol(Tr.PgReconcile.ColAmount); var colBalance = table.AddCol(Tr.PgReconcile.ColBalance); int nonMatches = 0; var lastMatchingBalance = default(DateTime); foreach (var entry in entries) { string type; if (entry.InAccount == null || entry.InStatement == null) { type = "none"; } else if (Math.Abs((entry.InAccount.Transaction.DatePosted - entry.InStatement.Timestamp).TotalDays) <= 3) { type = "perfect"; } else { type = "uncertain"; } if (type == "none") { nonMatches++; } var row = table.AddRow(); var cellcss = "reconcile-match-" + type; if (entry.InAccount != null) { row.SetValue(colAcctDate, entry.InAccount.Transaction.DatePosted.ToShortDateString(), cellcss); } if (entry.InStatement != null) { row.SetValue(colStmtDate, entry.InStatement.Timestamp.ToShortDateString(), cellcss); } row.SetValue(colComment, new object[] { entry.InAccount == null ? null : new object[] { entry.InAccount.ReadableDescAndMemo, new BR() }, entry.InStatement == null ? null : entry.InStatement.Comment }, cellcss); row.SetValue(colAmount, entry.Amount.ToString(amtFmt), cellcss); if (entry.InAccount != null) { var balance = entry.InAccount.AccountBalanceAfter; var matchingBalanceSplits = statementSplits.Where(spl => spl.Balance != null && spl.Balance.Value == balance); bool matchingBalance = false; foreach (var matchingBalanceSplit in matchingBalanceSplits) { // Verify that the InStatement entries up to now and the statement splits up to the matching split are exactly the same set. var balanceContributingStmtStmt = statementSplits.TakeWhile(spl => spl != matchingBalanceSplit).Concat(matchingBalanceSplit).ToList(); var balanceContributingStmtAcct = entries.TakeWhile(e => e != entry).Concat(entry).Where(e => e.InStatement != null).Select(e => e.InStatement).ToList(); //if (balanceContributingStmtStmt.Count != balanceContributingStmtAcct.Count) // continue; //if (balanceContributingStmtAcct.Any(spl => !balanceContributingStmtStmt.Contains(spl))) // continue; //if (balanceContributingStmtStmt.Any(spl => !balanceContributingStmtAcct.Contains(spl))) // continue; matchingBalance = true; break; } row.SetValue(colBalance, balance.ToString(amtFmt), cellcss + (matchingBalance ? " reconcile-balance-match" : "")); if (matchingBalance && entry.InAccount.Transaction.DatePosted > lastMatchingBalance) { lastMatchingBalance = entry.InAccount.Transaction.DatePosted; } } } content.Add(new H1(Tr.PgReconcile.HeaderReconciledTransactions)); content.Add(table.GetHtml()); var titleBalance = (lastMatchingBalance == default(DateTime) ? "no balance match" : $"balance match {(DateTime.Today - lastMatchingBalance).TotalDays:0} days ago"); content.Add(new SCRIPTLiteral($"document.title = 'Reconcile – {nonMatches} non-matches remaining – {titleBalance}';")); } } } return(new object[] { generateForm(statementText, regexName, regexText), (btnPressed != btn.ReconcilePreset && btnPressed != btn.ReconcileCustom) ? null : content }); }
protected abstract void ProcessAccount(GncAccount account, int depth);
protected abstract AccountValueInfo GetAccountValue(GncAccount account, int depth);
public override object GetContent() { MaxDepth = Request.GetValidated <int>("MaxDepth", -1, x => x >= 0, Tr.PgMonthly.Validation_NonNegative); Account = GetAccount("Acct"); { var ccys = Program.CurFile.Book.EnumCommodities(); var ccy = Request.GetValidated <string>("Ccy", null, x => x == null || ccys.Any(c => c.Identifier == x), Tr.PgMonthly.Validation_OneOfCommodities.Fmt(ccys.Select(c => c.Identifier).Order().JoinString(", "))); ConvertTo = ccy == null ? null : ccys.Single(c => c.Identifier == ccy); } InitRequestOptions(); Report = new ReportAccounts(Account, Request, true, false); Report.AddCol(GetColumnCaption(), GetColumnCaption()); doAccount(Account, 0); // MaxDepth UI var maxdepthUi = new List <object>(); { maxdepthUi.Add(Tr.PgMonthly.SubAcctsDepth); for (int i = 0; i <= 5; i++) { var label = i == 0 ? Tr.PgMonthly.SubAcctsNone.Translation : i.ToString(); if (MaxDepth == i) { maxdepthUi.Add(new SPAN(label) { class_ = "aw-current" }); } else { maxdepthUi.Add(new A(label) { href = Request.Url.WithQuery("MaxDepth", i.ToString()).ToHref() }); } maxdepthUi.Add(" · "); } if (MaxDepth == -1) { maxdepthUi.Add(new SPAN(Tr.PgMonthly.SubAcctsAll) { class_ = "aw-current" }); } else { maxdepthUi.Add(new A(Tr.PgMonthly.SubAcctsAll) { href = Request.Url.WithoutQuery("MaxDepth").ToHref() }); } } // ConvertTo currency UI var convertToUI = new List <object>(); { var ccys = Program.CurFile.Book.EnumCommodities().OrderBy(c => c.Identifier); convertToUI.Add(Program.Tr.PgMonthly.ConvertTo); foreach (var ccy in ((GncCommodity)null).Concat(ccys)) { if (ccy != null) { convertToUI.Add(" · "); } if (ConvertTo == ccy) { convertToUI.Add(new SPAN(ccy?.Identifier ?? Program.Tr.PgMonthly.ConvertToNone) { class_ = "aw-current" }); } else if (ccy == null) { convertToUI.Add(new A(Program.Tr.PgMonthly.ConvertToNone) { href = Request.Url.WithoutQuery("Ccy").ToHref() }); } else { convertToUI.Add(new A(ccy) { href = Request.Url.WithQuery("Ccy", ccy.Identifier).ToHref() }); } } } var html = new DIV( new P(Tr.PgMonthly.CurAccount, GetAccountBreadcrumbs("Acct", Account)), new P(maxdepthUi), new P(convertToUI), Report.GetHtml() ); return(html); }
public override object GetContent() { GroupMonths = Request.GetValidated <int>("Group", 1, x => x >= 1, Tr.PgMonthly.Validation_1OrGreater); int pastYears = Request.GetValidated <int>("Years", 1, x => x >= 1, Tr.PgMonthly.Validation_1OrGreater); int pastGroups = (12 * pastYears) / GroupMonths + 1; // Default to the last N months such that we see M whole groups, where M = number of groups per year + 1 { var earliest = Program.CurFile.Book.EarliestDate; var toDefault = GroupMonths == 1 // exclude current month iff it's incomplete for all groupings other than single month ? DateTime.Today.EndOfMonth().AssumeUtc() : DateTime.Today == DateTime.Today.EndOfMonth() ? DateTime.Today : DateTime.Today.AddMonths(-1).EndOfMonth().AssumeUtc(); var frDefault = toDefault.AddMonths(1 - pastGroups * GroupMonths).StartOfMonth().AssumeUtc(); if (frDefault < earliest) { frDefault = earliest; } var fy = Request.GetValidated <int>("FrYr", frDefault.Year); var fm = Request.GetValidated <int>("FrMo", frDefault.Month, x => x >= 1 && x <= 12, Tr.PgMonthly.Validation_Between1and12); var ty = Request.GetValidated <int>("ToYr", toDefault.Year, x => x >= fy, Tr.PgMonthly.Validation_NotSmallerYear.Fmt(fy)); var tm = Request.GetValidated <int>("ToMo", toDefault.Month, x => x >= 1 && x <= 12 && (fy < ty || x >= fm), Tr.PgMonthly.Validation_Between1and12_NotSmallerMonth.Fmt(fm)); Interval = new DateInterval(fy, fm, 1, ty, tm, DateTime.DaysInMonth(ty, tm)); } MaxDepth = Request.GetValidated <int>("MaxDepth", -1, x => x >= 0, Tr.PgMonthly.Validation_NonNegative); Negate = Request.GetValidated <bool>("Neg", false); Account = GetAccount("Acct"); { var ccys = Program.CurFile.Book.EnumCommodities(); var ccy = Request.GetValidated <string>("Ccy", null, x => x == null || ccys.Any(c => c.Identifier == x), Tr.PgMonthly.Validation_OneOfCommodities.Fmt(ccys.Select(c => c.Identifier).Order().JoinString(", "))); ConvertTo = ccy == null ? null : ccys.Single(c => c.Identifier == ccy); } Report = new ReportAccounts(Account, Request, true, false); foreach (var interval in EnumIntervals()) { double months = interval.TotalMonths; string capt = (months <= 1) ? interval.Start.ToString("MMM\nyy") : (interval.Start.ToString("MMMyy\n") + Tr.PgMonthly.MonthGroupJoiner + interval.End.ToString("\nMMMyy")); if (months != GroupMonths) { capt += "\n({0:0.#} {1})".Fmt(months, Tr.PgMonthly.MoSuffix); } Report.AddCol(interval, capt); } doAccount(Account, 0); // MaxDepth UI var maxdepthUi = new List <object>(); { maxdepthUi.Add(Tr.PgMonthly.SubAcctsDepth); for (int i = 0; i <= 5; i++) { var label = i == 0 ? Tr.PgMonthly.SubAcctsNone.Translation : i.ToString(); if (MaxDepth == i) { maxdepthUi.Add(new SPAN(label) { class_ = "aw-current" }); } else { maxdepthUi.Add(new A(label) { href = Request.Url.WithQuery("MaxDepth", i.ToString()).ToHref() }); } maxdepthUi.Add(" · "); } if (MaxDepth == -1) { maxdepthUi.Add(new SPAN(Tr.PgMonthly.SubAcctsAll) { class_ = "aw-current" }); } else { maxdepthUi.Add(new A(Tr.PgMonthly.SubAcctsAll) { href = Request.Url.WithoutQuery("MaxDepth").ToHref() }); } } // Group months UI var groupMonthsUi = new List <object>(); { groupMonthsUi.Add(Tr.PgMonthly.GroupMonthsCount); foreach (var len in new[] { 1, 2, 3, 4, 6, 12 }) { if (len != 1) { groupMonthsUi.Add(" · "); } if (GroupMonths == len) { groupMonthsUi.Add(new SPAN(len) { class_ = "aw-current" }); } else if (len == 1) { groupMonthsUi.Add(new A(len) { href = Request.Url.WithoutQuery("Group").ToHref() }); } else { groupMonthsUi.Add(new A(len) { href = Request.Url.WithQuery("Group", len.ToString()).ToHref() }); } } } // Years to show UI var yearsUi = new List <object>(); { yearsUi.Add(Tr.PgMonthly.PastYears); foreach (var yrs in Enumerable.Range(1, (int)Math.Ceiling((DateTime.Now - Program.CurFile.Book.EarliestDate).TotalDays / 365))) { if (yrs != 1) { yearsUi.Add(" · "); } if (pastYears == yrs) { yearsUi.Add(new SPAN(Tr.PgMonthly.YearsMonths.Fmt(Tr.Ns, yrs, GroupMonths)) { class_ = "aw-current" }); } else if (yrs == 1) { yearsUi.Add(new A(yrs) { href = Request.Url.WithoutQuery("Years").ToHref() }); } else { yearsUi.Add(new A(yrs) { href = Request.Url.WithQuery("Years", yrs.ToString()).ToHref() }); } } } // ConvertTo currency UI var convertToUI = new List <object>(); { var ccys = Program.CurFile.Book.EnumCommodities().OrderBy(c => c.Identifier); convertToUI.Add(Program.Tr.PgMonthly.ConvertTo); foreach (var ccy in ((GncCommodity)null).Concat(ccys)) { if (ccy != null) { convertToUI.Add(" · "); } if (ConvertTo == ccy) { convertToUI.Add(new SPAN(ccy?.Identifier ?? Program.Tr.PgMonthly.ConvertToNone) { class_ = "aw-current" }); } else if (ccy == null) { convertToUI.Add(new A(Program.Tr.PgMonthly.ConvertToNone) { href = Request.Url.WithoutQuery("Ccy").ToHref() }); } else { convertToUI.Add(new A(ccy) { href = Request.Url.WithQuery("Ccy", ccy.Identifier).ToHref() }); } } } // Mode var modeUi = new List <object>(); { if (this is PageMonthlyTotals) { modeUi.Add(new SPAN(Tr.PgMonthly.ViewModeTotals) { class_ = "aw-current" }); } else { modeUi.Add(new A(Tr.PgMonthly.ViewModeTotals) { href = "/MonthlyTotals" + Request.Url.QueryString }); } modeUi.Add(" · "); if (this is PageMonthlyBalances) { modeUi.Add(new SPAN(Tr.PgMonthly.ViewModeBalances) { class_ = "aw-current" }); } else { modeUi.Add(new A(Tr.PgMonthly.ViewModeBalances) { href = "/MonthlyBalances" + Request.Url.QueryString }); } } var html = new DIV( new P(Tr.PgMonthly.CurAccount, GetAccountBreadcrumbs("Acct", Account)), new P(maxdepthUi), new P(groupMonthsUi, new RawTag(" "), yearsUi), new P(modeUi, new RawTag(" "), convertToUI), Report.GetHtml() ); return(html); }
public override object GetContent() { var minDate = DateTime.MinValue; var maxDate = DateTime.MaxValue - TimeSpan.FromDays(1); var frDate = Request.GetValidated("Fr", minDate); var toDate = Request.GetValidated("To", maxDate, dt => dt >= frDate, Tr.PgTrns.Validation_NotBeforeFr); var amtFmt = Request.GetValidated("AmtFmt", "#,0"); _interval = new DateInterval(frDate.Date, toDate.Date + TimeSpan.FromDays(1) - TimeSpan.FromTicks(1)); _account = GetAccount("Acct"); var subAccts = _subaccts = Request.GetValidated("SubAccts", false); var showBalance = Request.GetValidated("ShowBal", false, val => !(val && _subaccts), Tr.PgTrns.Validation_ShowBalVsSubAccts); _subaccts &= _account.EnumChildren().Any(); ReportTable table = new ReportTable(); var colDate = table.AddCol(Tr.PgTrns.ColDate); var colDesc = table.AddCol(Tr.PgTrns.ColDescription); var colQty = table.AddCol(Tr.PgTrns.ColQuantity); var colCcy = table.AddCol(Tr.PgTrns.ColCurrency); var colBal = table.AddCol(Tr.PgTrns.ColBalance); var colAcct = table.AddCol(Tr.PgTrns.ColAccount); var colInBase = table.AddCol(Tr.PgTrns.ColInBaseCcy.Fmt(Program.CurFile.Book.BaseCurrencyId)); if (!_subaccts) { table.Cols.Remove(colAcct); } if (showBalance) { table.Cols.Remove(colInBase); } else { table.Cols.Remove(colBal); } var splits = _account.EnumSplits(_subaccts); splits = splits.Where(split => split.Transaction.DatePosted >= frDate && split.Transaction.DatePosted <= toDate); splits = splits.OrderBy(split => split.Transaction); foreach (var split in splits) { var trn = split.Transaction; var row = table.AddRow(); row.SetValue(colDate, trn.DatePosted.ToShortDateString()); row.SetValue(colDesc, (split.Memo == null || split.Memo == "" || split.Memo == trn.Description) ? trn.Description : (trn.Description + " [" + split.Memo + "]")); row.SetValue(colQty, split.Quantity.ToString(amtFmt), ReportTable.CssClassNumber(split.Quantity)); row.SetValue(colCcy, split.Account.Commodity.Identifier, "ccy_name ccy_" + split.Account.Commodity.Identifier.Replace(":", "_")); if (!showBalance) { row.SetValue(colInBase, split.AmountConverted(Program.CurFile.Book.BaseCurrency).Quantity.ToString(amtFmt), ReportTable.CssClassNumber(split.Quantity)); } if (showBalance) { row.SetValue(colBal, split.AccountBalanceAfter.ToString(amtFmt), ReportTable.CssClassNumber(split.AccountBalanceAfter)); } if (_subaccts) { row.SetValue(colAcct, generateAcctPath(split.Account)); } if (split.IsBalsnap) { try { row.CssClass = split.AccountBalanceAfter == split.Balsnap ? "balsnap_ok" : "balsnap_wrong"; } catch (GncBalsnapParseException) { row.CssClass = "balsnap_error"; } } } return(Ut.NewArray( new P(Tr.PgMonthly.CurAccount, GetAccountBreadcrumbs("Acct", _account)), new P(Tr.PgTrns.ModeCaption, showBalance && !subAccts ? (object)new SPAN(Tr.PgTrns.ModeWithBalance) { class_ = "aw-current" } : new A(Tr.PgTrns.ModeWithBalance) { href = Request.Url.WithQuery("ShowBal", "true").WithoutQuery("SubAccts").ToHref() }, " · ", !showBalance && subAccts ? (object)new SPAN(Tr.PgTrns.ModeWithSubaccts) { class_ = "aw-current" } : new A(Tr.PgTrns.ModeWithSubaccts) { href = Request.Url.WithoutQuery("ShowBal").WithQuery("SubAccts", "true").ToHref() } ), new P(Tr.PgTrns.RoundingCaption, amtFmt == "#,0" ? (object)new SPAN(Tr.PgTrns.RoundingWhole) { class_ = "aw-current" } : new A(Tr.PgTrns.RoundingWhole) { href = Request.Url.WithoutQuery("AmtFmt").ToHref() }, " · ", amtFmt == "#,0.00" ? (object)new SPAN(Tr.PgTrns.RoundingDecimals) { class_ = "aw-current" } : new A(Tr.PgTrns.RoundingDecimals) { href = Request.Url.WithQuery("AmtFmt", "#,0.00").ToHref() } ), new P(Tr.PgTrns.DateCaption, (frDate == minDate && toDate == maxDate) ? (object)Tr.PgTrns.ShowingAll : (frDate == minDate) ? Tr.PgTrns.ShowingOnOrBefore.FmtEnumerable( (object)Ut.NewArray <object>(new SPAN(toDate.ToShortDateString()) { class_ = "filter_hilite" }, " (", new A(Tr.PgTrns.DateRemove) { href = Request.Url.WithoutQuery("To").ToHref() }, ")") ) : (toDate == maxDate) ? Tr.PgTrns.ShowingOnOrAfter.FmtEnumerable( (object)Ut.NewArray <object>(new SPAN(frDate.ToShortDateString()) { class_ = "filter_hilite" }, " (", new A(Tr.PgTrns.DateRemove) { href = Request.Url.WithoutQuery("Fr").ToHref() }, ")") ) : Tr.PgTrns.ShowingBetween.FmtEnumerable( Ut.NewArray <object>(new SPAN(frDate.ToShortDateString()) { class_ = "filter_hilite" }, " (", new A(Tr.PgTrns.DateRemove) { href = Request.Url.WithoutQuery("Fr").ToHref() }, ")"), Ut.NewArray <object>(new SPAN(toDate.ToShortDateString()) { class_ = "filter_hilite" }, " (", new A(Tr.PgTrns.DateRemove) { href = Request.Url.WithoutQuery("To").ToHref() }, ")") ) ), table.GetHtml() )); }