From d5bd0ee7812b3ba00cfcf90b7c470bdce6fd4323 Mon Sep 17 00:00:00 2001 From: rockstardev Date: Thu, 25 Apr 2019 18:13:17 -0500 Subject: [PATCH] Filtering invoices by StartDate and EndDate Now it's required to separate parameters with comma. Forced to do this because dates have spaces between date and time part --- BTCPayServer.Tests/UnitTest1.cs | 20 ++++- .../Controllers/InvoiceController.UI.cs | 82 +++++++------------ .../Models/InvoicingModels/InvoicesModel.cs | 52 +++--------- BTCPayServer/SearchString.cs | 58 +++++++++---- .../Services/Invoices/InvoiceRepository.cs | 2 +- .../Views/Invoice/ListInvoices.cshtml | 17 +++- 6 files changed, 112 insertions(+), 119 deletions(-) diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index 203039045..9b55439b9 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -742,6 +742,12 @@ namespace BTCPayServer.Tests AssertSearchInvoice(acc, false, invoice.Id, $"exceptionstatus:paidOver"); AssertSearchInvoice(acc, true, invoice.Id, $"unusual:true"); AssertSearchInvoice(acc, false, invoice.Id, $"unusual:false"); + + var time = invoice.InvoiceTime; + AssertSearchInvoice(acc, true, invoice.Id, $"startdate:{time.ToString("yyyy-MM-dd HH:mm:ss")}"); + AssertSearchInvoice(acc, true, invoice.Id, $"enddate:{time.ToStringLowerInvariant()}"); + AssertSearchInvoice(acc, false, invoice.Id, $"startdate:{time.AddSeconds(1).ToString("yyyy-MM-dd HH:mm:ss")}"); + AssertSearchInvoice(acc, false, invoice.Id, $"enddate:{time.AddSeconds(-1).ToString("yyyy-MM-dd HH:mm:ss")}"); } } @@ -879,22 +885,28 @@ namespace BTCPayServer.Tests [Trait("Fast", "Fast")] public void CanParseFilter() { - var filter = "storeid:abc status:abed blabhbalh "; + var filter = "storeid:abc, status:abed, blabhbalh "; var search = new SearchString(filter); - Assert.Equal("storeid:abc status:abed blabhbalh", search.ToString()); + Assert.Equal("storeid:abc, status:abed, blabhbalh", search.ToString()); Assert.Equal("blabhbalh", search.TextSearch); Assert.Single(search.Filters["storeid"]); Assert.Single(search.Filters["status"]); Assert.Equal("abc", search.Filters["storeid"].First()); Assert.Equal("abed", search.Filters["status"].First()); - filter = "status:abed status:abed2"; + filter = "status:abed, status:abed2"; search = new SearchString(filter); - Assert.Equal("status:abed status:abed2", search.ToString()); + Assert.Equal("", search.TextSearch); + Assert.Equal("status:abed, status:abed2", search.ToString()); Assert.Throws(() => search.Filters["test"]); Assert.Equal(2, search.Filters["status"].Count); Assert.Equal("abed", search.Filters["status"].First()); Assert.Equal("abed2", search.Filters["status"].Skip(1).First()); + + filter = "StartDate:2019-04-25 01:00 AM, hekki"; + search = new SearchString(filter); + Assert.Equal("2019-04-25 01:00 AM", search.Filters["startdate"].First()); + Assert.Equal("hekki", search.TextSearch); } [Fact] diff --git a/BTCPayServer/Controllers/InvoiceController.UI.cs b/BTCPayServer/Controllers/InvoiceController.UI.cs index 5eb991cb2..fb8b99ff2 100644 --- a/BTCPayServer/Controllers/InvoiceController.UI.cs +++ b/BTCPayServer/Controllers/InvoiceController.UI.cs @@ -276,7 +276,7 @@ namespace BTCPayServer.Controllers storeBlob.ChangellySettings.IsConfigured()) ? storeBlob.ChangellySettings : null; - + CoinSwitchSettings coinswitch = (storeBlob.CoinSwitchSettings != null && storeBlob.CoinSwitchSettings.Enabled && storeBlob.CoinSwitchSettings.IsConfigured()) ? storeBlob.CoinSwitchSettings @@ -335,7 +335,7 @@ namespace BTCPayServer.Controllers ChangellyMerchantId = changelly?.ChangellyMerchantId, ChangellyAmountDue = changellyAmountDue, CoinSwitchEnabled = coinswitch != null, - CoinSwitchAmountMarkupPercentage = coinswitch?.AmountMarkupPercentage?? 0, + CoinSwitchAmountMarkupPercentage = coinswitch?.AmountMarkupPercentage ?? 0, CoinSwitchMerchantId = coinswitch?.MerchantId, CoinSwitchMode = coinswitch?.Mode, StoreId = store.Id, @@ -410,7 +410,7 @@ namespace BTCPayServer.Controllers if (!HttpContext.WebSockets.IsWebSocketRequest) return NotFound(); var invoice = await _InvoiceRepository.GetInvoice(invoiceId); - if (invoice == null || invoice.Status == InvoiceStatus.Complete || invoice.Status == InvoiceStatus.Invalid || invoice.Status == InvoiceStatus.Expired) + if (invoice == null || invoice.Status == InvoiceStatus.Complete || invoice.Status == InvoiceStatus.Invalid || invoice.Status == InvoiceStatus.Expired) return NotFound(); var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); CompositeDisposable leases = new CompositeDisposable(); @@ -465,25 +465,22 @@ namespace BTCPayServer.Controllers [Route("invoices")] [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)] [BitpayAPIConstraint(false)] - public async Task ListInvoices(string searchTerm = null, int skip = 0, int count = 50) + public async Task ListInvoices(string searchTerm = null, int skip = 0, int count = 50, int timezoneOffset = 0) { - if (searchTerm == null) - { - searchTerm = HttpContext.Session.GetString("InvoicesSearchTerm"); - } var model = new InvoicesModel { SearchTerm = searchTerm, Skip = skip, Count = count, - StatusMessage = StatusMessage + StatusMessage = StatusMessage, + TimezoneOffset = timezoneOffset }; - InvoiceQuery invoiceQuery = GetInvoiceQuery(searchTerm); + InvoiceQuery invoiceQuery = GetInvoiceQuery(searchTerm, timezoneOffset); var counting = _InvoiceRepository.GetInvoicesTotal(invoiceQuery); invoiceQuery.Count = count; invoiceQuery.Skip = skip; var list = await _InvoiceRepository.GetInvoices(invoiceQuery); - + foreach (var invoice in list) { var state = invoice.GetInvoiceState(); @@ -504,21 +501,21 @@ namespace BTCPayServer.Controllers return View(model); } - private InvoiceQuery GetInvoiceQuery(string searchTerm = null) + private InvoiceQuery GetInvoiceQuery(string searchTerm = null, int timezoneOffset = 0) { - var filterString = new SearchString(searchTerm); + var fs = new SearchString(searchTerm); var invoiceQuery = new InvoiceQuery() { - TextSearch = filterString.TextSearch, + TextSearch = fs.TextSearch, UserId = GetUserId(), - Unusual = !filterString.Filters.ContainsKey("unusual") ? null - : !bool.TryParse(filterString.Filters["unusual"].First(), out var r) ? (bool?)null - : r, - Status = filterString.Filters.ContainsKey("status") ? filterString.Filters["status"].ToArray() : null, - ExceptionStatus = filterString.Filters.ContainsKey("exceptionstatus") ? filterString.Filters["exceptionstatus"].ToArray() : null, - StoreId = filterString.Filters.ContainsKey("storeid") ? filterString.Filters["storeid"].ToArray() : null, - ItemCode = filterString.Filters.ContainsKey("itemcode") ? filterString.Filters["itemcode"].ToArray() : null, - OrderId = filterString.Filters.ContainsKey("orderid") ? filterString.Filters["orderid"].ToArray() : null + Unusual = fs.GetFilterBool("unusual"), + Status = fs.GetFilterArray("status"), + ExceptionStatus = fs.GetFilterArray("exceptionstatus"), + StoreId = fs.GetFilterArray("storeid"), + ItemCode = fs.GetFilterArray("itemcode"), + OrderId = fs.GetFilterArray("orderid"), + StartDate = fs.GetFilterDate("startdate", timezoneOffset), + EndDate = fs.GetFilterDate("enddate", timezoneOffset) }; return invoiceQuery; } @@ -526,13 +523,13 @@ namespace BTCPayServer.Controllers [HttpGet] [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)] [BitpayAPIConstraint(false)] - public async Task Export(string format, string searchTerm = null) + public async Task Export(string format, string searchTerm = null, int timezoneOffset = 0) { var model = new InvoiceExport(_NetworkProvider, _CurrencyNameTable); - InvoiceQuery invoiceQuery = GetInvoiceQuery(searchTerm); - invoiceQuery.Count = int.MaxValue; + InvoiceQuery invoiceQuery = GetInvoiceQuery(searchTerm, timezoneOffset); invoiceQuery.Skip = 0; + invoiceQuery.Count = int.MaxValue; var invoices = await _InvoiceRepository.GetInvoices(invoiceQuery); var res = model.Process(invoices, format); @@ -627,27 +624,6 @@ namespace BTCPayServer.Controllers } } - [HttpPost] - [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)] - [BitpayAPIConstraint(false)] - public IActionResult SearchInvoice(InvoicesModel invoices) - { - if (invoices.SearchTerm == null) - { - HttpContext.Session.Remove("InvoicesSearchTerm"); - } - else - { - HttpContext.Session.SetString("InvoicesSearchTerm", invoices.SearchTerm); - } - return RedirectToAction(nameof(ListInvoices), new - { - searchTerm = invoices.SearchTerm, - skip = invoices.Skip, - count = invoices.Count, - }); - } - [HttpGet] [Route("invoices/{invoiceId}/changestate/{newState}")] [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)] @@ -696,7 +672,7 @@ namespace BTCPayServer.Controllers _EventAggregator.Publish(new InvoiceEvent(invoice, 1008, InvoiceEvent.MarkedInvalid)); StatusMessage = "Invoice marked invalid"; } - else if(newState == "complete") + else if (newState == "complete") { await _InvoiceRepository.UpdatePaidInvoiceToComplete(invoiceId); _EventAggregator.Publish(new InvoiceEvent(invoice, 2008, InvoiceEvent.MarkedCompleted)); @@ -721,18 +697,18 @@ namespace BTCPayServer.Controllers { public static Dictionary ParsePosData(string posData) { - var result = new Dictionary(); + var result = new Dictionary(); if (string.IsNullOrEmpty(posData)) { return result; } - + try { - var jObject =JObject.Parse(posData); + var jObject = JObject.Parse(posData); foreach (var item in jObject) { - + switch (item.Value.Type) { case JTokenType.Array: @@ -749,7 +725,7 @@ namespace BTCPayServer.Controllers result.Add(item.Key, item.Value.ToString()); break; } - + } } catch @@ -759,6 +735,6 @@ namespace BTCPayServer.Controllers return result; } } - + } } diff --git a/BTCPayServer/Models/InvoicingModels/InvoicesModel.cs b/BTCPayServer/Models/InvoicingModels/InvoicesModel.cs index 5f359df22..b0faaba49 100644 --- a/BTCPayServer/Models/InvoicingModels/InvoicesModel.cs +++ b/BTCPayServer/Models/InvoicingModels/InvoicesModel.cs @@ -7,32 +7,14 @@ namespace BTCPayServer.Models.InvoicingModels { public class InvoicesModel { - public int Skip - { - get; set; - } - public int Count - { - get; set; - } - public int Total - { - get; set; - } - public string SearchTerm - { - get; set; - } + public int Skip { get; set; } + public int Count { get; set; } + public int Total { get; set; } + public string SearchTerm { get; set; } + public int? TimezoneOffset { get; set; } - public List Invoices - { - get; set; - } = new List(); - public string StatusMessage - { - get; - set; - } + public List Invoices { get; set; } = new List(); + public string StatusMessage { get; set; } } public class InvoiceModel @@ -41,27 +23,15 @@ namespace BTCPayServer.Models.InvoicingModels public string OrderId { get; set; } public string RedirectUrl { get; set; } - public string InvoiceId - { - get; set; - } + public string InvoiceId { get; set; } - public string Status - { - get; set; - } + public string Status { get; set; } public bool CanMarkComplete { get; set; } public bool CanMarkInvalid { get; set; } public bool CanMarkStatus => CanMarkComplete || CanMarkInvalid; public bool ShowCheckout { get; set; } public string ExceptionStatus { get; set; } - public string AmountCurrency - { - get; set; - } - public string StatusMessage - { - get; set; - } + public string AmountCurrency { get; set; } + public string StatusMessage { get; set; } } } diff --git a/BTCPayServer/SearchString.cs b/BTCPayServer/SearchString.cs index 8d8d0a7e8..b283b3d27 100644 --- a/BTCPayServer/SearchString.cs +++ b/BTCPayServer/SearchString.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -15,35 +16,58 @@ namespace BTCPayServer str = str.Trim(); _OriginalString = str.Trim(); TextSearch = _OriginalString; - var splitted = str.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + var splitted = str.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); Filters = splitted - .Select(t => t.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries)) + .Select(t => t.Split(new char[] { ':' }, 2, StringSplitOptions.RemoveEmptyEntries)) .Where(kv => kv.Length == 2) - .Select(kv => new KeyValuePair(kv[0].ToLowerInvariant(), kv[1])) + .Select(kv => new KeyValuePair(kv[0].ToLowerInvariant().Trim(), kv[1])) .ToMultiValueDictionary(o => o.Key, o => o.Value); - foreach(var filter in splitted) - { - if(filter.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries).Length == 2) - { - TextSearch = TextSearch.Replace(filter, string.Empty, StringComparison.InvariantCulture); - } - } - TextSearch = TextSearch.Trim(); + var val = splitted.FirstOrDefault(a => a?.IndexOf(':', StringComparison.OrdinalIgnoreCase) == -1); + if (val != null) + TextSearch = val.Trim(); + else + TextSearch = ""; } - public string TextSearch - { - get; - private set; - } - + public string TextSearch { get; private set; } + public MultiValueDictionary Filters { get; private set; } public override string ToString() { return _OriginalString; } + + internal string[] GetFilterArray(string key) + { + return Filters.ContainsKey(key) ? Filters[key].ToArray() : null; + } + + internal bool? GetFilterBool(string key) + { + if (!Filters.ContainsKey(key)) + return null; + + return bool.TryParse(Filters[key].First(), out var r) ? + r : (bool?)null; + } + + internal DateTimeOffset? GetFilterDate(string key, int timezoneOffset) + { + if (!Filters.ContainsKey(key)) + return null; + + var val = Filters[key].First(); + var success = DateTimeOffset.TryParse(val, null as IFormatProvider, DateTimeStyles.AssumeUniversal, out var r); + if (success) + { + r = r.AddMinutes(timezoneOffset); + return r; + } + + return null; + } } } diff --git a/BTCPayServer/Services/Invoices/InvoiceRepository.cs b/BTCPayServer/Services/Invoices/InvoiceRepository.cs index 18a1fe315..2965580f7 100644 --- a/BTCPayServer/Services/Invoices/InvoiceRepository.cs +++ b/BTCPayServer/Services/Invoices/InvoiceRepository.cs @@ -463,7 +463,7 @@ retry: { // Hacky way to return an empty query object. The nice way is much too elaborate: // https://stackoverflow.com/questions/33305495/how-to-return-empty-iqueryable-in-an-async-repository-method - return query.Where(x => false); + return query.Where(x => false); } query = query.Where(i => ids.Contains(i.Id)); } diff --git a/BTCPayServer/Views/Invoice/ListInvoices.cshtml b/BTCPayServer/Views/Invoice/ListInvoices.cshtml index a8f1a922f..fcda35738 100644 --- a/BTCPayServer/Views/Invoice/ListInvoices.cshtml +++ b/BTCPayServer/Views/Invoice/ListInvoices.cshtml @@ -33,6 +33,8 @@
  • status:(expired|invalid|complete|confirmed|paid|new) for filtering a specific status
  • exceptionstatus:(paidover|paidlate|paidpartial) for filtering a specific exception state
  • unusual:(true|false) for filtering invoices which might requires merchant attention (those invalid or with an exceptionstatus)
  • +
  • startdate:yyyy-MM-dd HH:mm:ss getting invoices that were created after certain date
  • +
  • enddate:yyyy-MM-dd HH:mm:ss getting invoices that were created before certain date
  • If you want all confirmed and complete invoices, you can duplicate a filter status:confirmed status:complete. @@ -52,15 +54,16 @@

    -
    +
    +