From 6c856aba48cde347dad0493f0d6409eb2c2c7f8c Mon Sep 17 00:00:00 2001 From: Andrew Camilleri Date: Wed, 30 Jun 2021 08:59:01 +0100 Subject: [PATCH] Introduce Server paging for Payouts List (#2564) * Introduce Server paging for Payouts List * Add paging params * Minor code and formatting improvements * View updates * Apply suggestions from code review Co-authored-by: Zaxounette <51208677+Zaxounette@users.noreply.github.com> * fix tests Co-authored-by: Dennis Reimann Co-authored-by: Zaxounette <51208677+Zaxounette@users.noreply.github.com> --- BTCPayServer.Tests/SeleniumTests.cs | 180 +++++++-------- .../WalletsController.PullPayments.cs | 119 +++++----- .../Extensions/ControllerBaseExtensions.cs | 4 + .../Models/WalletViewModels/PayoutsModel.cs | 18 +- BTCPayServer/Views/Wallets/Payouts.cshtml | 216 +++++++++--------- 5 files changed, 267 insertions(+), 270 deletions(-) diff --git a/BTCPayServer.Tests/SeleniumTests.cs b/BTCPayServer.Tests/SeleniumTests.cs index d7caa0036..9441ca945 100644 --- a/BTCPayServer.Tests/SeleniumTests.cs +++ b/BTCPayServer.Tests/SeleniumTests.cs @@ -912,113 +912,113 @@ namespace BTCPayServer.Tests [Trait("Selenium", "Selenium")] public async Task CanUsePullPaymentsViaUI() { - using (var s = SeleniumTester.Create()) - { - await s.StartAsync(); - s.RegisterNewUser(true); - s.CreateNewStore(); - s.GenerateWallet("BTC", "", true, true); + using var s = SeleniumTester.Create(); + await s.StartAsync(); + s.RegisterNewUser(true); + s.CreateNewStore(); + s.GenerateWallet("BTC", "", true, true); - await s.Server.ExplorerNode.GenerateAsync(1); - await s.FundStoreWallet(denomination: 50.0m); - s.GoToWallet(navPages: WalletsNavPages.PullPayments); - s.Driver.FindElement(By.Id("NewPullPayment")).Click(); - s.Driver.FindElement(By.Id("Name")).SendKeys("PP1"); - s.Driver.FindElement(By.Id("Amount")).Clear(); - s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0");; - s.Driver.FindElement(By.Id("Create")).Click(); - s.Driver.FindElement(By.LinkText("View")).Click(); + await s.Server.ExplorerNode.GenerateAsync(1); + await s.FundStoreWallet(denomination: 50.0m); + s.GoToWallet(navPages: WalletsNavPages.PullPayments); + s.Driver.FindElement(By.Id("NewPullPayment")).Click(); + s.Driver.FindElement(By.Id("Name")).SendKeys("PP1"); + s.Driver.FindElement(By.Id("Amount")).Clear(); + s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0");; + s.Driver.FindElement(By.Id("Create")).Click(); + s.Driver.FindElement(By.LinkText("View")).Click(); - s.GoToWallet(navPages: WalletsNavPages.PullPayments); + s.GoToWallet(navPages: WalletsNavPages.PullPayments); - s.Driver.FindElement(By.Id("NewPullPayment")).Click(); - s.Driver.FindElement(By.Id("Name")).SendKeys("PP2"); - s.Driver.FindElement(By.Id("Amount")).Clear(); - s.Driver.FindElement(By.Id("Amount")).SendKeys("100.0"); - s.Driver.FindElement(By.Id("Create")).Click(); + s.Driver.FindElement(By.Id("NewPullPayment")).Click(); + s.Driver.FindElement(By.Id("Name")).SendKeys("PP2"); + s.Driver.FindElement(By.Id("Amount")).Clear(); + s.Driver.FindElement(By.Id("Amount")).SendKeys("100.0"); + s.Driver.FindElement(By.Id("Create")).Click(); - // This should select the first View, ie, the last one PP2 - s.Driver.FindElement(By.LinkText("View")).Click(); - var address = await s.Server.ExplorerNode.GetNewAddressAsync(); - s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString()); - s.Driver.FindElement(By.Id("ClaimedAmount")).Clear(); - s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("15" + Keys.Enter); - s.FindAlertMessage(); + // This should select the first View, ie, the last one PP2 + s.Driver.FindElement(By.LinkText("View")).Click(); + var address = await s.Server.ExplorerNode.GetNewAddressAsync(); + s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString()); + s.Driver.FindElement(By.Id("ClaimedAmount")).Clear(); + s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("15" + Keys.Enter); + s.FindAlertMessage(); - // We should not be able to use an address already used - s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString()); - s.Driver.FindElement(By.Id("ClaimedAmount")).Clear(); - s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("20" + Keys.Enter); - s.FindAlertMessage(StatusMessageModel.StatusSeverity.Error); + // We should not be able to use an address already used + s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString()); + s.Driver.FindElement(By.Id("ClaimedAmount")).Clear(); + s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("20" + Keys.Enter); + s.FindAlertMessage(StatusMessageModel.StatusSeverity.Error); - address = await s.Server.ExplorerNode.GetNewAddressAsync(); - s.Driver.FindElement(By.Id("Destination")).Clear(); - s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString()); - s.Driver.FindElement(By.Id("ClaimedAmount")).Clear(); - s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("20" + Keys.Enter); - s.FindAlertMessage(); - Assert.Contains("Awaiting Approval", s.Driver.PageSource); + address = await s.Server.ExplorerNode.GetNewAddressAsync(); + s.Driver.FindElement(By.Id("Destination")).Clear(); + s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString()); + s.Driver.FindElement(By.Id("ClaimedAmount")).Clear(); + s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("20" + Keys.Enter); + s.FindAlertMessage(); + Assert.Contains("Awaiting Approval", s.Driver.PageSource); - var viewPullPaymentUrl = s.Driver.Url; - // This one should have nothing - s.GoToWallet(navPages: WalletsNavPages.PullPayments); - var payouts = s.Driver.FindElements(By.ClassName("pp-payout")); - Assert.Equal(2, payouts.Count); - payouts[1].Click(); - Assert.Empty(s.Driver.FindElements(By.ClassName("payout"))); - // PP2 should have payouts - s.GoToWallet(navPages: WalletsNavPages.PullPayments); - payouts = s.Driver.FindElements(By.ClassName("pp-payout")); - payouts[0].Click(); + var viewPullPaymentUrl = s.Driver.Url; + // This one should have nothing + s.GoToWallet(navPages: WalletsNavPages.PullPayments); + var payouts = s.Driver.FindElements(By.ClassName("pp-payout")); + Assert.Equal(2, payouts.Count); + payouts[1].Click(); + Assert.Empty(s.Driver.FindElements(By.ClassName("payout"))); + // PP2 should have payouts + s.GoToWallet(navPages: WalletsNavPages.PullPayments); + payouts = s.Driver.FindElements(By.ClassName("pp-payout")); + payouts[0].Click(); - Assert.NotEmpty(s.Driver.FindElements(By.ClassName("payout"))); - s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-selectAllCheckbox")).Click(); - s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-actions")).Click(); - s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve-pay")).Click(); + Assert.NotEmpty(s.Driver.FindElements(By.ClassName("payout"))); + s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-selectAllCheckbox")).Click(); + s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-actions")).Click(); + s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve-pay")).Click(); - s.Driver.FindElement(By.Id("SignTransaction")).Click(); - s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click(); - s.FindAlertMessage(); + s.Driver.FindElement(By.Id("SignTransaction")).Click(); + s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click(); + s.FindAlertMessage(); - TestUtils.Eventually(() => - { - s.Driver.Navigate().Refresh(); - Assert.Contains("badge transactionLabel", s.Driver.PageSource); - }); - Assert.Equal("payout", s.Driver.FindElement(By.ClassName("transactionLabel")).Text); + TestUtils.Eventually(() => + { + s.Driver.Navigate().Refresh(); + Assert.Contains("badge transactionLabel", s.Driver.PageSource); + }); + Assert.Equal("payout", s.Driver.FindElement(By.ClassName("transactionLabel")).Text); - s.GoToWallet(navPages: WalletsNavPages.Payouts); - ReadOnlyCollection txs; - TestUtils.Eventually(() => - { - s.Driver.Navigate().Refresh(); + s.GoToWallet(navPages: WalletsNavPages.Payouts); + var x = s.Driver.PageSource; + s.Driver.FindElement(By.Id($"{PayoutState.InProgress}-view")).Click(); + ReadOnlyCollection txs; + TestUtils.Eventually(() => + { + s.Driver.Navigate().Refresh(); - txs = s.Driver.FindElements(By.ClassName("transaction-link")); - Assert.Equal(2, txs.Count); - }); - - s.Driver.Navigate().GoToUrl(viewPullPaymentUrl); txs = s.Driver.FindElements(By.ClassName("transaction-link")); Assert.Equal(2, txs.Count); - Assert.Contains("In Progress", s.Driver.PageSource); + }); - await s.Server.ExplorerNode.GenerateAsync(1); + s.Driver.Navigate().GoToUrl(viewPullPaymentUrl); + txs = s.Driver.FindElements(By.ClassName("transaction-link")); + Assert.Equal(2, txs.Count); + Assert.Contains(PayoutState.InProgress.GetStateString(), s.Driver.PageSource); - TestUtils.Eventually(() => - { - s.Driver.Navigate().Refresh(); - Assert.Contains("Completed", s.Driver.PageSource); - }); - await s.Server.ExplorerNode.GenerateAsync(10); - var pullPaymentId = viewPullPaymentUrl.Split('/').Last(); + await s.Server.ExplorerNode.GenerateAsync(1); - await TestUtils.EventuallyAsync(async () => - { - using var ctx = s.Server.PayTester.GetService().CreateContext(); - var payoutsData = await ctx.Payouts.Where(p => p.PullPaymentDataId == pullPaymentId).ToListAsync(); - Assert.True(payoutsData.All(p => p.State == PayoutState.Completed)); - }); - } + TestUtils.Eventually(() => + { + s.Driver.Navigate().Refresh(); + Assert.Contains(PayoutState.Completed.GetStateString(), s.Driver.PageSource); + }); + await s.Server.ExplorerNode.GenerateAsync(10); + var pullPaymentId = viewPullPaymentUrl.Split('/').Last(); + + await TestUtils.EventuallyAsync(async () => + { + using var ctx = s.Server.PayTester.GetService().CreateContext(); + var payoutsData = await ctx.Payouts.Where(p => p.PullPaymentDataId == pullPaymentId).ToListAsync(); + Assert.True(payoutsData.All(p => p.State == PayoutState.Completed)); + }); } private static void CanBrowseContent(SeleniumTester s) diff --git a/BTCPayServer/Controllers/WalletsController.PullPayments.cs b/BTCPayServer/Controllers/WalletsController.PullPayments.cs index 5e87fe25a..83d9a950a 100644 --- a/BTCPayServer/Controllers/WalletsController.PullPayments.cs +++ b/BTCPayServer/Controllers/WalletsController.PullPayments.cs @@ -24,8 +24,7 @@ namespace BTCPayServer.Controllers { public partial class WalletsController { - [HttpGet] - [Route("{walletId}/pull-payments/new")] + [HttpGet("{walletId}/pull-payments/new")] public IActionResult NewPullPayment([ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId) { @@ -40,9 +39,8 @@ namespace BTCPayServer.Controllers EmbeddedCSS = "", }); } - - [HttpPost] - [Route("{walletId}/pull-payments/new")] + + [HttpPost("{walletId}/pull-payments/new")] public async Task NewPullPayment([ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, NewPullPaymentModel model) { @@ -86,9 +84,8 @@ namespace BTCPayServer.Controllers }); return RedirectToAction(nameof(PullPayments), new { walletId = walletId.ToString() }); } - - [HttpGet] - [Route("{walletId}/pull-payments")] + + [HttpGet("{walletId}/pull-payments")] public async Task PullPayments( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId) @@ -149,8 +146,7 @@ namespace BTCPayServer.Controllers return time; } - [HttpGet] - [Route("{walletId}/pull-payments/{pullPaymentId}/archive")] + [HttpGet("{walletId}/pull-payments/{pullPaymentId}/archive")] public IActionResult ArchivePullPayment( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, @@ -164,8 +160,8 @@ namespace BTCPayServer.Controllers Action = "Archive" }); } - [HttpPost] - [Route("{walletId}/pull-payments/{pullPaymentId}/archive")] + + [HttpPost("{walletId}/pull-payments/{pullPaymentId}/archive")] public async Task ArchivePullPaymentPost( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, @@ -180,8 +176,7 @@ namespace BTCPayServer.Controllers return RedirectToAction(nameof(PullPayments), new { walletId = walletId.ToString() }); } - [HttpPost] - [Route("{walletId}/payouts")] + [HttpPost("{walletId}/payouts")] public async Task PayoutsPost( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, PayoutsModel vm, CancellationToken cancellationToken) @@ -312,7 +307,7 @@ namespace BTCPayServer.Controllers }); if (result != PayoutPaidRequest.PayoutPaidResult.Ok) { - this.TempData.SetStatusMessageModel(new StatusMessageModel() + TempData.SetStatusMessageModel(new StatusMessageModel() { Message = PayoutPaidRequest.GetErrorMessage(result), Severity = StatusMessageModel.StatusSeverity.Error @@ -362,65 +357,77 @@ namespace BTCPayServer.Controllers return payouts; } - [HttpGet] - [Route("{walletId}/payouts")] + [HttpGet("{walletId}/payouts")] public async Task Payouts( [ModelBinder(typeof(WalletIdModelBinder))] - WalletId walletId, PayoutsModel vm = null) + WalletId walletId, string pullPaymentId, PayoutState payoutState, + int skip = 0, int count = 50) { - vm ??= new PayoutsModel(); - vm.PayoutStateSets ??= ((PayoutState[]) Enum.GetValues(typeof(PayoutState))).Select(state => - new PayoutsModel.PayoutStateSet() {State = state, Payouts = new List()}).ToList(); - using var ctx = this._dbContextFactory.CreateContext(); + var vm = this.ParseListQuery(new PayoutsModel + { + PaymentMethodId = new PaymentMethodId(walletId.CryptoCode, PaymentTypes.BTCLike), + PullPaymentId = pullPaymentId, + PayoutState = payoutState, + Skip = skip, + Count = count + }); + vm.Payouts = new List(); + await using var ctx = _dbContextFactory.CreateContext(); var storeId = walletId.StoreId; - vm.PaymentMethodId = new PaymentMethodId(walletId.CryptoCode, PaymentTypes.BTCLike); var payoutRequest = ctx.Payouts.Where(p => p.PullPaymentData.StoreId == storeId && !p.PullPaymentData.Archived); if (vm.PullPaymentId != null) { payoutRequest = payoutRequest.Where(p => p.PullPaymentDataId == vm.PullPaymentId); + vm.PullPaymentName = (await ctx.PullPayments.FindAsync(pullPaymentId)).GetBlob().Name; } + if (vm.PaymentMethodId != null) + { + var pmiStr = vm.PaymentMethodId.ToString(); + payoutRequest = payoutRequest.Where(p => p.PaymentMethodId == pmiStr); + } + + vm.PayoutStateCount = payoutRequest.GroupBy(data => data.State) + .Select(e => new {e.Key, Count = e.Count()}) + .ToDictionary(arg => arg.Key, arg => arg.Count); + foreach (PayoutState value in Enum.GetValues(typeof(PayoutState))) + { + if(vm.PayoutStateCount.ContainsKey(value)) + continue; + vm.PayoutStateCount.Add(value, 0); + } + + vm.PayoutStateCount = vm.PayoutStateCount.OrderBy(pair => pair.Key) + .ToDictionary(pair => pair.Key, pair => pair.Value); + + payoutRequest = payoutRequest.Where(p => p.State == vm.PayoutState); + vm.Total = await payoutRequest.CountAsync(); + payoutRequest = payoutRequest.Skip(vm.Skip).Take(vm.Count); + var payouts = await payoutRequest.OrderByDescending(p => p.Date) .Select(o => new { Payout = o, PullPayment = o.PullPaymentData }).ToListAsync(); - foreach (var stateSet in payouts.GroupBy(arg => arg.Payout.State)) + foreach (var item in payouts) { - var state = vm.PayoutStateSets.SingleOrDefault(set => set.State == stateSet.Key); - if (state == null) + var ppBlob = item.PullPayment.GetBlob(); + var payoutBlob = item.Payout.GetBlob(_jsonSerializerSettings); + var m = new PayoutsModel.PayoutModel { - state = new PayoutsModel.PayoutStateSet() - { - Payouts = new List(), State = stateSet.Key - }; - vm.PayoutStateSets.Add(state); - } - - foreach (var item in stateSet) - { - - if (item.Payout.GetPaymentMethodId() != vm.PaymentMethodId) - continue; - var ppBlob = item.PullPayment.GetBlob(); - var payoutBlob = item.Payout.GetBlob(_jsonSerializerSettings); - var m = new PayoutsModel.PayoutModel(); - m.PullPaymentId = item.PullPayment.Id; - m.PullPaymentName = ppBlob.Name ?? item.PullPayment.Id; - m.Date = item.Payout.Date; - m.PayoutId = item.Payout.Id; - m.Amount = _currencyTable.DisplayFormatCurrency(payoutBlob.Amount, ppBlob.Currency); - m.Destination = payoutBlob.Destination; - var handler = _payoutHandlers - .FirstOrDefault(handler => handler.CanHandle(item.Payout.GetPaymentMethodId())); - var proofBlob = handler?.ParseProof(item.Payout); - m.ProofLink = proofBlob?.Link; - state.Payouts.Add(m); - - } + PullPaymentId = item.PullPayment.Id, + PullPaymentName = ppBlob.Name ?? item.PullPayment.Id, + Date = item.Payout.Date, + PayoutId = item.Payout.Id, + Amount = _currencyTable.DisplayFormatCurrency(payoutBlob.Amount, ppBlob.Currency), + Destination = payoutBlob.Destination + }; + var handler = _payoutHandlers + .FirstOrDefault(handler => handler.CanHandle(item.Payout.GetPaymentMethodId())); + var proofBlob = handler?.ParseProof(item.Payout); + m.ProofLink = proofBlob?.Link; + vm.Payouts.Add(m); } - - vm.PayoutStateSets = vm.PayoutStateSets.Where(set => set.Payouts?.Any() is true).ToList(); return View(vm); } } diff --git a/BTCPayServer/Extensions/ControllerBaseExtensions.cs b/BTCPayServer/Extensions/ControllerBaseExtensions.cs index 774608036..6d810a00f 100644 --- a/BTCPayServer/Extensions/ControllerBaseExtensions.cs +++ b/BTCPayServer/Extensions/ControllerBaseExtensions.cs @@ -4,6 +4,7 @@ using BTCPayServer.Models; using BTCPayServer.Models.InvoicingModels; using BTCPayServer.Models.PaymentRequestViewModels; using BTCPayServer.Models.ServerViewModels; +using BTCPayServer.Models.WalletViewModels; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; @@ -21,6 +22,8 @@ namespace BTCPayServer prop = typeof(UserPrefsCookie).GetProperty(nameof(UserPrefsCookie.PaymentRequestsQuery)); else if (model is UsersViewModel) prop = typeof(UserPrefsCookie).GetProperty(nameof(UserPrefsCookie.UsersQuery)); + else if (model is PayoutsModel) + prop = typeof(UserPrefsCookie).GetProperty(nameof(UserPrefsCookie.UsersQuery)); else throw new Exception("Unsupported BasePagingViewModel for cookie user preferences saving"); @@ -78,6 +81,7 @@ namespace BTCPayServer public ListQueryDataHolder PaymentRequestsQuery { get; set; } public ListQueryDataHolder UsersQuery { get; set; } + public ListQueryDataHolder PayoutsQuery { get; set; } } class ListQueryDataHolder diff --git a/BTCPayServer/Models/WalletViewModels/PayoutsModel.cs b/BTCPayServer/Models/WalletViewModels/PayoutsModel.cs index e2e26e663..f09934eb1 100644 --- a/BTCPayServer/Models/WalletViewModels/PayoutsModel.cs +++ b/BTCPayServer/Models/WalletViewModels/PayoutsModel.cs @@ -2,18 +2,21 @@ using System; using System.Collections.Generic; using System.Linq; using BTCPayServer.Client.Models; -using BTCPayServer.Data; using BTCPayServer.Payments; namespace BTCPayServer.Models.WalletViewModels { - public class PayoutsModel + public class PayoutsModel : BasePagingViewModel { public string PullPaymentId { get; set; } public string Command { get; set; } - public List PayoutStateSets{ get; set; } + public Dictionary PayoutStateCount { get; set; } public PaymentMethodId PaymentMethodId { get; set; } + public List Payouts { get; set; } + public PayoutState PayoutState { get; set; } + public string PullPaymentName { get; set; } + public class PayoutModel { public string PayoutId { get; set; } @@ -26,16 +29,9 @@ namespace BTCPayServer.Models.WalletViewModels public string ProofLink { get; set; } } - public class PayoutStateSet - { - public PayoutState State { get; set; } - public List Payouts { get; set; } - } - public string[] GetSelectedPayouts(PayoutState state) { - return PayoutStateSets.Where(set => set.State == state) - .SelectMany(set => set.Payouts.Where(model => model.Selected).Select(model => model.PayoutId)) + return Payouts.Where(model => model.Selected).Select(model => model.PayoutId) .ToArray(); } } diff --git a/BTCPayServer/Views/Wallets/Payouts.cshtml b/BTCPayServer/Views/Wallets/Payouts.cshtml index 324a0fd06..6dcda7f68 100644 --- a/BTCPayServer/Views/Wallets/Payouts.cshtml +++ b/BTCPayServer/Views/Wallets/Payouts.cshtml @@ -4,7 +4,22 @@ @inject IEnumerable PayoutHandlers; @{ Layout = "../Shared/_NavLayout.cshtml"; - ViewData.SetActivePageAndTitle(WalletsNavPages.Payouts, $"Manage {Model.PaymentMethodId.ToPrettyString()} payouts", Context.GetStoreData().StoreName); + ViewData.SetActivePageAndTitle(WalletsNavPages.Payouts, $"Manage {Model.PaymentMethodId.ToPrettyString()} payouts{(string.IsNullOrEmpty(Model.PullPaymentName) ? string.Empty : " for pull payment " + Model.PullPaymentName)}", Context.GetStoreData().StoreName); + + var stateActions = new List<(string Action, string Text)>(); + switch (Model.PayoutState) + { + case PayoutState.AwaitingApproval: + stateActions.Add(("approve", "Approve selected payouts")); + stateActions.Add(("approve-pay", "Approve & Send selected payouts")); + stateActions.Add(("cancel", "Cancel selected payouts")); + break; + case PayoutState.AwaitingPayment: + stateActions.Add(("pay", "Send selected payouts")); + stateActions.Add(("cancel", "Cancel selected payouts")); + stateActions.Add(("mark-paid", "Mark selected payouts as already paid")); + break; + } } @section PageFootContent { @@ -20,127 +35,102 @@ }
-

@ViewData["Title"]

- @if (!Model.PayoutStateSets.Any()) - { -

- There are no payouts yet. -

- } + + +

@ViewData["Title"]

+
- +
+ +
+ @if (Model.Payouts.Any() && stateActions.Any()) + { + + }
-
- @for (var index = 0; index < Model.PayoutStateSets.Count; index++) - { - var state = Model.PayoutStateSets[index]; - var stateActions = new List<(string Action, string Text)>(); - switch (state.State) - { - case PayoutState.AwaitingApproval: - stateActions.Add(("approve", "Approve selected payouts")); - stateActions.Add(("approve-pay", "Approve & Send selected payouts")); - stateActions.Add(("cancel", "Cancel selected payouts")); - break; - case PayoutState.AwaitingPayment: - stateActions.Add(("pay", "Send selected payouts")); - stateActions.Add(("cancel", "Cancel selected payouts")); - stateActions.Add(("mark-paid", "Mark selected payouts as already paid")); - break; - } -
- - - @if (state.Payouts.Any() && stateActions.Any()) +
+ @if (Model.Payouts.Any()) + { + + + + + + + + + @if (Model.PayoutState != PayoutState.AwaitingApproval) + { + + } + + + + @for (int i = 0; i < Model.Payouts.Count; i++) { - - } -
- @if (state.Payouts.Any()) - { -
+ + + Date + SourceDestinationAmountTransaction
- - - - - - - - @if (state.State != PayoutState.AwaitingApproval) + var pp = Model.Payouts[i]; + + + + + + + @if (Model.PayoutState != PayoutState.AwaitingApproval) + { + + Link } - - - - @for (int i = 0; i < state.Payouts.Count; i++) - { - var pp = state.Payouts[i]; - - - - - - - @if (state.State != PayoutState.AwaitingApproval) - { - - } - - } - -
- - - Date - SourceDestinationAmount
+ + + + + + @pp.Date.ToBrowserDate() + + @pp.PullPaymentName + + @pp.Destination + + @pp.Amount + + @if (!(pp.ProofLink is null)) { - Transaction
- - - - - - @pp.Date.ToBrowserDate() - - @pp.PullPaymentName - - @pp.Destination - - @pp.Amount - - @if (!(pp.ProofLink is null)) - { - Link - } -
- } - else - { -

No payouts.

- } -
-
+ + } + + } + + + } + else + { +

There are no payouts matching this criteria.

}
+