btcpayserver/BTCPayServer/Plugins/PointOfSale/PointOfSalePlugin.cs
Andrew Camilleri f74ea14d8b
Plugins can now build apps (#4608)
* Plugins can now build apps

* fix tests

* fixup

* pluginize existing apps

* Test fixes part 1

* Test fixes part 2

* Fix Crowdfund namespace

* Syntax

* More namespace fixes

* Markup

* Test fix

* upstream fixes

* Add plugin icon

* Fix nullable build warnings

* allow pre popualting app creation

* Fixes after merge

* Make link methods async

* Use AppData as parameter for ConfigureLink

* GetApps by AppType

* Use ConfigureLink on dashboard

* Rename method

* Add properties to indicate stats support

* Property updates

* Test fixes

* Clean up imports

* Fixes after merge

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2023-03-17 11:56:32 +09:00

135 lines
5.5 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Abstractions.Services;
using BTCPayServer.Configuration;
using BTCPayServer.Data;
using BTCPayServer.Plugins.PointOfSale.Controllers;
using BTCPayServer.Services;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices;
using Ganss.XSS;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace BTCPayServer.Plugins.PointOfSale
{
public class PointOfSalePlugin : BaseBTCPayServerPlugin
{
public override string Identifier => "BTCPayServer.Plugins.PointOfSale";
public override string Name => "Point Of Sale";
public override string Description => "Readily accept bitcoin without fees or a third-party, directly to your wallet.";
public override void Execute(IServiceCollection services)
{
services.AddSingleton<IUIExtension>(new UIExtension("PointOfSale/NavExtension", "apps-nav"));
services.AddSingleton<IApp,PointOfSaleApp>();
base.Execute(services);
}
}
public enum PosViewType
{
[Display(Name = "Product list")]
Static,
[Display(Name = "Product list with cart")]
Cart,
[Display(Name = "Keypad only")]
Light,
[Display(Name = "Print display")]
Print
}
public class PointOfSaleApp: IApp
{
private readonly LinkGenerator _linkGenerator;
private readonly IOptions<BTCPayServerOptions> _btcPayServerOptions;
private readonly DisplayFormatter _displayFormatter;
private readonly HtmlSanitizer _htmlSanitizer;
public const string AppType = "PointOfSale";
public string Description => "Point of Sale";
public string Type => AppType;
public bool SupportsSalesStats => true;
public bool SupportsItemStats => true;
public PointOfSaleApp(
LinkGenerator linkGenerator,
IOptions<BTCPayServerOptions> btcPayServerOptions,
DisplayFormatter displayFormatter,
HtmlSanitizer htmlSanitizer)
{
_linkGenerator = linkGenerator;
_btcPayServerOptions = btcPayServerOptions;
_displayFormatter = displayFormatter;
_htmlSanitizer = htmlSanitizer;
}
public Task<string> ConfigureLink(AppData app)
{
return Task.FromResult(_linkGenerator.GetPathByAction(nameof(UIPointOfSaleController.UpdatePointOfSale),
"UIPointOfSale", new { appId = app.Id }, _btcPayServerOptions.Value.RootPath));
}
public Task<SalesStats> GetSalesStats(AppData app, InvoiceEntity[] paidInvoices, int numberOfDays)
{
var posS = app.GetSettings<PointOfSaleSettings>();
var items = AppService.Parse(_htmlSanitizer, _displayFormatter, posS.Template, posS.Currency);
return AppService.GetSalesStatswithPOSItems(items, paidInvoices, numberOfDays);
}
public Task<IEnumerable<ItemStats>> GetItemStats(AppData appData, InvoiceEntity[] paidInvoices)
{
var settings = appData.GetSettings<PointOfSaleSettings>();
var items = AppService.Parse(_htmlSanitizer, _displayFormatter, settings.Template, settings.Currency);
var itemCount = paidInvoices
.Where(entity => entity.Currency.Equals(settings.Currency, StringComparison.OrdinalIgnoreCase) && (
// The POS data is present for the cart view, where multiple items can be bought
entity.Metadata.PosData is not null ||
// The item code should be present for all types other than the cart and keypad
!string.IsNullOrEmpty(entity.Metadata.ItemCode)
))
.Aggregate(new List<AppService.InvoiceStatsItem>(), AppService.AggregateInvoiceEntitiesForStats(items))
.GroupBy(entity => entity.ItemCode)
.Select(entities =>
{
var total = entities.Sum(entity => entity.FiatPrice);
var itemCode = entities.Key;
var item = items.FirstOrDefault(p => p.Id == itemCode);
return new ItemStats
{
ItemCode = itemCode,
Title = item?.Title ?? itemCode,
SalesCount = entities.Count(),
Total = total,
TotalFormatted = _displayFormatter.Currency(total, settings.Currency)
};
})
.OrderByDescending(stats => stats.SalesCount);
return Task.FromResult<IEnumerable<ItemStats>>(itemCount);
}
public Task<object> GetInfo(AppData appData)
{
throw new NotImplementedException();
}
public Task SetDefaultSettings(AppData appData, string defaultCurrency)
{
var empty = new PointOfSaleSettings { Currency = defaultCurrency };
appData.SetSettings(empty);
return Task.CompletedTask;
}
public Task<string> ViewLink(AppData app)
{
return Task.FromResult(_linkGenerator.GetPathByAction(nameof(UIPointOfSaleController.ViewPointOfSale),
"UIPointOfSale", new { appId = app.Id }, _btcPayServerOptions.Value.RootPath));
}
}
}