diff --git a/BTCPayServer/Controllers/InvoiceController.UI.cs b/BTCPayServer/Controllers/InvoiceController.UI.cs index 60f036ecd..1ff8e1e82 100644 --- a/BTCPayServer/Controllers/InvoiceController.UI.cs +++ b/BTCPayServer/Controllers/InvoiceController.UI.cs @@ -447,8 +447,11 @@ namespace BTCPayServer.Controllers Count = count, StatusMessage = StatusMessage }; - - var list = await ListInvoicesProcess(searchTerm, skip, count); + InvoiceQuery invoiceQuery = GetInvoiceQuery(searchTerm); + model.Total = await _InvoiceRepository.GetInvoicesTotal(invoiceQuery); + invoiceQuery.Count = count; + invoiceQuery.Skip = skip; + var list = await _InvoiceRepository.GetInvoices(invoiceQuery); foreach (var invoice in list) { var state = invoice.GetInvoiceState(); @@ -468,14 +471,12 @@ namespace BTCPayServer.Controllers return View(model); } - private async Task ListInvoicesProcess(string searchTerm = null, int skip = 0, int count = 50) + private InvoiceQuery GetInvoiceQuery(string searchTerm = null) { var filterString = new SearchString(searchTerm); - var list = await _InvoiceRepository.GetInvoices(new InvoiceQuery() + var invoiceQuery = new InvoiceQuery() { TextSearch = filterString.TextSearch, - Count = count, - Skip = skip, UserId = GetUserId(), Unusual = !filterString.Filters.ContainsKey("unusual") ? null : !bool.TryParse(filterString.Filters["unusual"].First(), out var r) ? (bool?)null @@ -485,9 +486,8 @@ namespace BTCPayServer.Controllers 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 - }); - - return list; + }; + return invoiceQuery; } [HttpGet] @@ -497,7 +497,10 @@ namespace BTCPayServer.Controllers { var model = new InvoiceExport(_NetworkProvider, _CurrencyNameTable); - var invoices = await ListInvoicesProcess(searchTerm, 0, int.MaxValue); + InvoiceQuery invoiceQuery = GetInvoiceQuery(searchTerm); + invoiceQuery.Count = int.MaxValue; + invoiceQuery.Skip = 0; + var invoices = await _InvoiceRepository.GetInvoices(invoiceQuery); var res = model.Process(invoices, format); var cd = new ContentDisposition diff --git a/BTCPayServer/Models/InvoicingModels/InvoicesModel.cs b/BTCPayServer/Models/InvoicingModels/InvoicesModel.cs index 7a4e3fb9c..5f359df22 100644 --- a/BTCPayServer/Models/InvoicingModels/InvoicesModel.cs +++ b/BTCPayServer/Models/InvoicingModels/InvoicesModel.cs @@ -15,6 +15,10 @@ namespace BTCPayServer.Models.InvoicingModels { get; set; } + public int Total + { + get; set; + } public string SearchTerm { get; set; diff --git a/BTCPayServer/Services/Invoices/InvoiceRepository.cs b/BTCPayServer/Services/Invoices/InvoiceRepository.cs index 314a90bfa..207fcdaa5 100644 --- a/BTCPayServer/Services/Invoices/InvoiceRepository.cs +++ b/BTCPayServer/Services/Invoices/InvoiceRepository.cs @@ -420,91 +420,108 @@ retry: return entity; } + private IQueryable GetInvoiceQuery(ApplicationDbContext context, InvoiceQuery queryObject) + { + IQueryable query = context.Invoices; + + if (!string.IsNullOrEmpty(queryObject.InvoiceId)) + { + query = query.Where(i => i.Id == queryObject.InvoiceId); + } + + if (queryObject.StoreId != null && queryObject.StoreId.Length > 0) + { + var stores = queryObject.StoreId.ToHashSet(); + query = query.Where(i => stores.Contains(i.StoreDataId)); + } + + if (queryObject.UserId != null) + { + query = query.Where(i => i.StoreData.UserStores.Any(u => u.ApplicationUserId == queryObject.UserId)); + } + + if (!string.IsNullOrEmpty(queryObject.TextSearch)) + { + var ids = new HashSet(SearchInvoice(queryObject.TextSearch)); + if (ids.Count == 0) + { + // 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); + } + query = query.Where(i => ids.Contains(i.Id)); + } + + if (queryObject.StartDate != null) + query = query.Where(i => queryObject.StartDate.Value <= i.Created); + + if (queryObject.EndDate != null) + query = query.Where(i => i.Created <= queryObject.EndDate.Value); + + if (queryObject.OrderId != null && queryObject.OrderId.Length > 0) + { + var statusSet = queryObject.OrderId.ToHashSet(); + query = query.Where(i => statusSet.Contains(i.OrderId)); + } + if (queryObject.ItemCode != null && queryObject.ItemCode.Length > 0) + { + var statusSet = queryObject.ItemCode.ToHashSet(); + query = query.Where(i => statusSet.Contains(i.ItemCode)); + } + + if (queryObject.Status != null && queryObject.Status.Length > 0) + { + var statusSet = queryObject.Status.ToHashSet(); + query = query.Where(i => statusSet.Contains(i.Status)); + } + + if (queryObject.Unusual != null) + { + var unused = queryObject.Unusual.Value; + query = query.Where(i => unused == (i.Status == "invalid" || i.ExceptionStatus != null)); + } + + if (queryObject.ExceptionStatus != null && queryObject.ExceptionStatus.Length > 0) + { + var exceptionStatusSet = queryObject.ExceptionStatus.Select(s => NormalizeExceptionStatus(s)).ToHashSet(); + query = query.Where(i => exceptionStatusSet.Contains(i.ExceptionStatus)); + } + + query = query.OrderByDescending(q => q.Created); + + if (queryObject.Skip != null) + query = query.Skip(queryObject.Skip.Value); + + if (queryObject.Count != null) + query = query.Take(queryObject.Count.Value); + + return query; + } + + public async Task GetInvoicesTotal(InvoiceQuery queryObject) + { + using (var context = _ContextFactory.CreateContext()) + { + var query = GetInvoiceQuery(context, queryObject); + return await query.CountAsync(); + } + } public async Task GetInvoices(InvoiceQuery queryObject) { using (var context = _ContextFactory.CreateContext()) { - IQueryable query = context - .Invoices - .Include(o => o.Payments) + var query = GetInvoiceQuery(context, queryObject); + query = query.Include(o => o.Payments) .Include(o => o.RefundAddresses); if (queryObject.IncludeAddresses) query = query.Include(o => o.HistoricalAddressInvoices).Include(o => o.AddressInvoices); if (queryObject.IncludeEvents) query = query.Include(o => o.Events); - if (!string.IsNullOrEmpty(queryObject.InvoiceId)) - { - query = query.Where(i => i.Id == queryObject.InvoiceId); - } - - if (queryObject.StoreId != null && queryObject.StoreId.Length > 0) - { - var stores = queryObject.StoreId.ToHashSet(); - query = query.Where(i => stores.Contains(i.StoreDataId)); - } - - if (queryObject.UserId != null) - { - query = query.Where(i => i.StoreData.UserStores.Any(u => u.ApplicationUserId == queryObject.UserId)); - } - - if (!string.IsNullOrEmpty(queryObject.TextSearch)) - { - var ids = new HashSet(SearchInvoice(queryObject.TextSearch)); - if (ids.Count == 0) - return Array.Empty(); - query = query.Where(i => ids.Contains(i.Id)); - } - - if (queryObject.StartDate != null) - query = query.Where(i => queryObject.StartDate.Value <= i.Created); - - if (queryObject.EndDate != null) - query = query.Where(i => i.Created <= queryObject.EndDate.Value); - - if (queryObject.OrderId != null && queryObject.OrderId.Length > 0) - { - var statusSet = queryObject.OrderId.ToHashSet(); - query = query.Where(i => statusSet.Contains(i.OrderId)); - } - if (queryObject.ItemCode != null && queryObject.ItemCode.Length > 0) - { - var statusSet = queryObject.ItemCode.ToHashSet(); - query = query.Where(i => statusSet.Contains(i.ItemCode)); - } - - if (queryObject.Status != null && queryObject.Status.Length > 0) - { - var statusSet = queryObject.Status.ToHashSet(); - query = query.Where(i => statusSet.Contains(i.Status)); - } - - if (queryObject.Unusual != null) - { - var unused = queryObject.Unusual.Value; - query = query.Where(i => unused == (i.Status == "invalid" || i.ExceptionStatus != null)); - } - - if (queryObject.ExceptionStatus != null && queryObject.ExceptionStatus.Length > 0) - { - var exceptionStatusSet = queryObject.ExceptionStatus.Select(s => NormalizeExceptionStatus(s)).ToHashSet(); - query = query.Where(i => exceptionStatusSet.Contains(i.ExceptionStatus)); - } - - query = query.OrderByDescending(q => q.Created); - - if (queryObject.Skip != null) - query = query.Skip(queryObject.Skip.Value); - - if (queryObject.Count != null) - query = query.Take(queryObject.Count.Value); var data = await query.ToArrayAsync().ConfigureAwait(false); - return data.Select(ToEntity).ToArray(); } - } private string NormalizeExceptionStatus(string status) diff --git a/BTCPayServer/Views/Invoice/ListInvoices.cshtml b/BTCPayServer/Views/Invoice/ListInvoices.cshtml index be5af531a..ce1106e54 100644 --- a/BTCPayServer/Views/Invoice/ListInvoices.cshtml +++ b/BTCPayServer/Views/Invoice/ListInvoices.cshtml @@ -142,23 +142,31 @@ } - - @if (Model.Skip != 0) - { - +
    +
  • + << - - } - >> - + })">Previous +
  • +
  • + @(Model.Skip + 1) to @(Model.Skip + Model.Invoices.Count) of @Model.Total +
  • +
  • + Next +
  • +
+ +