Merge pull request #1556 from Kukks/invoice-archive

Archive Invoice
This commit is contained in:
Nicolas Dorier 2020-05-10 00:51:11 +09:00 committed by GitHub
commit 9cc9a16bb9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 124 additions and 8 deletions

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@ -79,6 +79,8 @@ namespace BTCPayServer.Data
{
get; set;
}
public bool Archived { get; set; }
public List<PendingInvoiceData> PendingInvoices { get; set; }
}
}

View File

@ -0,0 +1,30 @@
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20200507092343_AddArchivedToInvoice")]
public class AddArchivedToInvoice : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "Archived",
table: "Invoices",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
if (this.SupportDropColumn(migrationBuilder.ActiveProvider))
{
migrationBuilder.DropColumn(
name: "Archived",
table: "Invoices");
}
}
}
}

View File

@ -190,6 +190,9 @@ namespace BTCPayServer.Migrations
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<bool>("Archived")
.HasColumnType("INTEGER");
b.Property<byte[]>("Blob")
.HasColumnType("BLOB");

View File

@ -240,10 +240,26 @@ namespace BTCPayServer.Tests
var storeUrl = s.Driver.Url;
s.ClickOnAllSideMenus();
s.GoToInvoices();
s.CreateInvoice(store);
var invoiceId = s.CreateInvoice(store);
s.AssertHappyMessage();
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
var invoiceUrl = s.Driver.Url;
//let's test archiving an invoice
Assert.DoesNotContain("Archived", s.Driver.FindElement(By.Id("btn-archive-toggle")).Text);
s.Driver.FindElement(By.Id("btn-archive-toggle")).Click();
s.AssertHappyMessage();
Assert.Contains("Archived", s.Driver.FindElement(By.Id("btn-archive-toggle")).Text);
//check that it no longer appears in list
s.GoToInvoices();
Assert.DoesNotContain(invoiceId, s.Driver.PageSource);
//ok, let's unarchive and see that it shows again
s.Driver.Navigate().GoToUrl(invoiceUrl);
s.Driver.FindElement(By.Id("btn-archive-toggle")).Click();
s.AssertHappyMessage();
Assert.DoesNotContain("Archived", s.Driver.FindElement(By.Id("btn-archive-toggle")).Text);
s.GoToInvoices();
Assert.Contains(invoiceId, s.Driver.PageSource);
// When logout we should not be able to access store and invoice details
s.Driver.FindElement(By.Id("Logout")).Click();

View File

@ -41,7 +41,8 @@ namespace BTCPayServer.Controllers
InvoiceId = new[] {invoiceId},
UserId = GetUserId(),
IncludeAddresses = true,
IncludeEvents = true
IncludeEvents = true,
IncludeArchived = true,
})).FirstOrDefault();
if (invoice == null)
return NotFound();
@ -71,7 +72,8 @@ namespace BTCPayServer.Controllers
ProductInformation = invoice.ProductInformation,
StatusException = invoice.ExceptionStatus,
Events = invoice.Events,
PosData = PosDataParser.ParsePosData(invoice.PosData)
PosData = PosDataParser.ParsePosData(invoice.PosData),
Archived = invoice.Archived
};
model.Addresses = invoice.HistoricalAddresses.Select(h =>
@ -91,6 +93,7 @@ namespace BTCPayServer.Controllers
private InvoiceDetailsModel InvoicePopulatePayments(InvoiceEntity invoice)
{
var model = new InvoiceDetailsModel();
model.Archived = invoice.Archived;
model.Payments = invoice.GetPayments();
foreach (var data in invoice.GetPaymentMethods())
{
@ -111,6 +114,26 @@ namespace BTCPayServer.Controllers
return model;
}
[HttpPost("invoices/{invoiceId}/archive")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[BitpayAPIConstraint(false)]
public async Task<IActionResult> ToggleArchive(string invoiceId)
{
var invoice = (await _InvoiceRepository.GetInvoices(new InvoiceQuery()
{
InvoiceId = new[] {invoiceId}, UserId = GetUserId(), IncludeAddresses = true, IncludeEvents = true, IncludeArchived = true,
})).FirstOrDefault();
if (invoice == null)
return NotFound();
await _InvoiceRepository.ToggleInvoiceArchival(invoiceId, !invoice.Archived);
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Success,
Message = invoice.Archived ? "The invoice has been unarchived and will appear in the invoice list by default again." : "The invoice has been archived and will no longer appear in the invoice list by default."
});
return RedirectToAction(nameof(invoice), new {invoiceId});
}
[HttpGet]
[Route("i/{invoiceId}")]
[Route("i/{invoiceId}/{paymentMethodId}")]
@ -437,7 +460,7 @@ namespace BTCPayServer.Controllers
AmountCurrency = _CurrencyNameTable.DisplayFormatCurrency(invoice.ProductInformation.Price, invoice.ProductInformation.Currency),
CanMarkInvalid = state.CanMarkInvalid(),
CanMarkComplete = state.CanMarkComplete(),
Details = InvoicePopulatePayments(invoice)
Details = InvoicePopulatePayments(invoice),
});
}
model.Total = await counting;
@ -452,6 +475,7 @@ namespace BTCPayServer.Controllers
TextSearch = fs.TextSearch,
UserId = GetUserId(),
Unusual = fs.GetFilterBool("unusual"),
IncludeArchived = fs.GetFilterBool("includearchived") ?? false,
Status = fs.GetFilterArray("status"),
ExceptionStatus = fs.GetFilterArray("exceptionstatus"),
StoreId = fs.GetFilterArray("storeid"),

View File

@ -127,5 +127,6 @@ namespace BTCPayServer.Models.InvoicingModels
public string NotificationEmail { get; internal set; }
public Dictionary<string, object> PosData { get; set; }
public List<PaymentEntity> Payments { get; set; }
public bool Archived { get; set; }
}
}

View File

@ -371,6 +371,7 @@ namespace BTCPayServer.Services.Invoices
public bool ExtendedNotifications { get; set; }
public List<InvoiceEventData> Events { get; internal set; }
public double PaymentTolerance { get; set; }
public bool Archived { get; set; }
public bool IsExpired()
{

View File

@ -158,7 +158,8 @@ retry:
Status = invoice.StatusString,
#pragma warning restore CS0618 // Type or member is obsolete
ItemCode = invoice.ProductInformation.ItemCode,
CustomerEmail = invoice.RefundMail
CustomerEmail = invoice.RefundMail,
Archived = false
});
foreach (var paymentMethod in invoice.GetPaymentMethods())
@ -396,6 +397,17 @@ retry:
}
}
public async Task ToggleInvoiceArchival(string invoiceId, bool archived)
{
using (var context = _ContextFactory.CreateContext())
{
var invoiceData = await context.FindAsync<InvoiceData>(invoiceId).ConfigureAwait(false);
if (invoiceData == null || invoiceData.Archived == archived )
return;
invoiceData.Archived = archived;
await context.SaveChangesAsync().ConfigureAwait(false);
}
}
public async Task UpdatePaidInvoiceToInvalid(string invoiceId)
{
using (var context = _ContextFactory.CreateContext())
@ -499,6 +511,7 @@ retry:
{
entity.BuyerInformation.BuyerEmail = entity.RefundMail;
}
entity.Archived = invoice.Archived;
return entity;
}
@ -513,6 +526,11 @@ retry:
{
IQueryable<Data.InvoiceData> query = context.Invoices;
if (!queryObject.IncludeArchived)
{
query = query.Where(i => !i.Archived);
}
if (queryObject.InvoiceId != null && queryObject.InvoiceId.Length > 0)
{
var statusSet = queryObject.InvoiceId.ToHashSet().ToArray();
@ -838,5 +856,6 @@ retry:
public bool IncludeAddresses { get; set; }
public bool IncludeEvents { get; set; }
public bool IncludeArchived { get; set; } = true;
}
}

View File

@ -28,7 +28,21 @@
<div class="row">
<div class="col-lg-12 section-heading">
<h2>@ViewData["Title"]</h2>
<div class="d-flex justify-content-between">
<h2>@ViewData["Title"]</h2>
<form asp-action="ToggleArchive" asp-route-invoiceId="@Model.Id" method="post">
<button type="submit" class="@(Model.Archived ? "btn badge badge-warning" : "btn btn-link")" id="btn-archive-toggle">
@if (Model.Archived)
{
<span data-toggle="tooltip" title="Unarchive this invoice">Archived <i class=" ml-1 fa fa-close" ></i></span>
}
else
{
<span data-toggle="tooltip" title="Archive this invoice so that it does not appear in the invoice list by default">Archive</span>
}
</button>
</form>
</div>
<hr class="primary">
</div>
</div>

View File

@ -88,6 +88,7 @@
<a class="dropdown-item" href="/invoices?Count=@Model.Count&SearchTerm=exceptionstatus%3ApaidPartial@{@storeIds}">Paid Partial Invoices</a>
<a class="dropdown-item" href="/invoices?Count=@Model.Count&SearchTerm=exceptionstatus%3ApaidOver@{@storeIds}">Paid Over Invoices</a>
<a class="dropdown-item" href="/invoices?Count=@Model.Count&SearchTerm=unusual%3Atrue@{@storeIds}">Unusual Invoices</a>
<a class="dropdown-item" href="/invoices?Count=@Model.Count&SearchTerm=includearchived%3Atrue@{@storeIds}">Archived Invoices</a>
<div role="separator" class="dropdown-divider"></div>
<a class="dropdown-item last24" href="/invoices?Count=@Model.Count&timezoneoffset=0&SearchTerm=startDate%3Alast24@{@storeIds}">Last 24 hours</a>
<a class="dropdown-item last72" href="/invoices?Count=@Model.Count&timezoneoffset=0&SearchTerm=startDate%3Alast72@{@storeIds}">Last 3 days</a>
@ -220,7 +221,11 @@
}
</td>
<td>@invoice.InvoiceId</td>
<td>
<td>
@if(invoice.Details.Archived)
{
<span class="badge badge-warning" >archived</span>
}
@if (invoice.CanMarkStatus)
{
<div id="pavpill_@invoice.InvoiceId">
@ -364,6 +369,7 @@
$("a.last24").each(function () { this.href = this.href.replace("last24", getDateStringWithOffset(24)); });
$("a.last72").each(function () { this.href = this.href.replace("last72", getDateStringWithOffset(72)); });
$("a.last168").each(function () { this.href = this.href.replace("last168", getDateStringWithOffset(168)); });
});
function getDateStringWithOffset(hoursDiff) {