2020-06-28 21:44:35 -05:00
|
|
|
using System;
|
2019-02-19 13:04:58 +09:00
|
|
|
using System.Collections.Generic;
|
2023-02-25 23:34:49 +09:00
|
|
|
using System.Diagnostics.CodeAnalysis;
|
2019-02-19 13:04:58 +09:00
|
|
|
using System.Globalization;
|
|
|
|
using System.IO;
|
|
|
|
using System.Linq;
|
|
|
|
using System.Threading.Tasks;
|
2023-05-26 16:49:32 +02:00
|
|
|
using BTCPayServer.Client;
|
2019-02-19 13:04:58 +09:00
|
|
|
using BTCPayServer.Data;
|
|
|
|
using BTCPayServer.Models.AppViewModels;
|
2023-03-17 03:56:32 +01:00
|
|
|
using BTCPayServer.Plugins.Crowdfund;
|
|
|
|
using BTCPayServer.Plugins.PointOfSale;
|
2022-07-18 20:51:53 +02:00
|
|
|
using BTCPayServer.Plugins.PointOfSale.Models;
|
2019-02-19 13:04:58 +09:00
|
|
|
using BTCPayServer.Services.Invoices;
|
|
|
|
using BTCPayServer.Services.Rates;
|
2019-05-30 07:02:52 +00:00
|
|
|
using BTCPayServer.Services.Stores;
|
2019-02-19 13:04:58 +09:00
|
|
|
using Ganss.XSS;
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
2019-09-02 15:37:52 +02:00
|
|
|
using NBitcoin;
|
|
|
|
using NBitcoin.DataEncoders;
|
2022-06-28 07:05:02 +02:00
|
|
|
using Newtonsoft.Json;
|
2019-09-02 15:37:52 +02:00
|
|
|
using Newtonsoft.Json.Linq;
|
2020-07-24 09:40:37 +02:00
|
|
|
using StoreData = BTCPayServer.Data.StoreData;
|
2019-02-19 13:04:58 +09:00
|
|
|
|
|
|
|
namespace BTCPayServer.Services.Apps
|
|
|
|
{
|
|
|
|
public class AppService
|
|
|
|
{
|
2023-03-20 10:39:26 +09:00
|
|
|
private readonly Dictionary<string, AppBaseType> _appTypes;
|
2023-05-23 02:18:57 +02:00
|
|
|
static AppService()
|
|
|
|
{
|
|
|
|
_defaultSerializer = new JsonSerializerSettings()
|
|
|
|
{
|
|
|
|
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver(),
|
|
|
|
Formatting = Formatting.None
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
private static JsonSerializerSettings _defaultSerializer;
|
|
|
|
|
2020-06-28 22:07:48 -05:00
|
|
|
readonly ApplicationDbContextFactory _ContextFactory;
|
2019-02-19 13:04:58 +09:00
|
|
|
private readonly InvoiceRepository _InvoiceRepository;
|
2020-06-28 22:07:48 -05:00
|
|
|
readonly CurrencyNameTable _Currencies;
|
2023-03-13 02:12:58 +01:00
|
|
|
private readonly DisplayFormatter _displayFormatter;
|
2019-05-30 07:02:52 +00:00
|
|
|
private readonly StoreRepository _storeRepository;
|
2019-02-19 13:04:58 +09:00
|
|
|
private readonly HtmlSanitizer _HtmlSanitizer;
|
|
|
|
public CurrencyNameTable Currencies => _Currencies;
|
2023-03-17 03:56:32 +01:00
|
|
|
public AppService(
|
2023-03-20 10:39:26 +09:00
|
|
|
IEnumerable<AppBaseType> apps,
|
2023-03-17 03:56:32 +01:00
|
|
|
ApplicationDbContextFactory contextFactory,
|
|
|
|
InvoiceRepository invoiceRepository,
|
|
|
|
CurrencyNameTable currencies,
|
|
|
|
DisplayFormatter displayFormatter,
|
|
|
|
StoreRepository storeRepository,
|
|
|
|
HtmlSanitizer htmlSanitizer)
|
2019-02-19 13:04:58 +09:00
|
|
|
{
|
2023-04-10 11:07:03 +09:00
|
|
|
_appTypes = apps.ToDictionary(a => a.Type, a => a);
|
2019-02-19 13:04:58 +09:00
|
|
|
_ContextFactory = contextFactory;
|
|
|
|
_InvoiceRepository = invoiceRepository;
|
|
|
|
_Currencies = currencies;
|
2019-05-30 07:02:52 +00:00
|
|
|
_storeRepository = storeRepository;
|
2019-02-19 13:04:58 +09:00
|
|
|
_HtmlSanitizer = htmlSanitizer;
|
2023-03-13 02:12:58 +01:00
|
|
|
_displayFormatter = displayFormatter;
|
2019-02-19 13:04:58 +09:00
|
|
|
}
|
2023-03-20 10:39:26 +09:00
|
|
|
#nullable enable
|
2023-03-17 03:56:32 +01:00
|
|
|
public Dictionary<string, string> GetAvailableAppTypes()
|
2019-02-19 13:04:58 +09:00
|
|
|
{
|
2023-03-20 10:39:26 +09:00
|
|
|
return _appTypes.ToDictionary(app => app.Key, app => app.Value.Description);
|
2022-04-12 09:55:10 +02:00
|
|
|
}
|
|
|
|
|
2023-03-20 10:39:26 +09:00
|
|
|
public AppBaseType? GetAppType(string appType)
|
2022-04-12 09:55:10 +02:00
|
|
|
{
|
2023-03-20 10:39:26 +09:00
|
|
|
_appTypes.TryGetValue(appType, out var a);
|
|
|
|
return a;
|
2022-04-12 09:55:10 +02:00
|
|
|
}
|
2023-03-20 10:39:26 +09:00
|
|
|
|
|
|
|
public async Task<object?> GetInfo(string appId)
|
2022-04-12 09:55:10 +02:00
|
|
|
{
|
2023-03-17 03:56:32 +01:00
|
|
|
var appData = await GetApp(appId, null);
|
|
|
|
if (appData is null)
|
|
|
|
return null;
|
2023-03-20 10:39:26 +09:00
|
|
|
var appType = GetAppType(appData.AppType);
|
|
|
|
if (appType is null)
|
2023-03-17 03:56:32 +01:00
|
|
|
return null;
|
2023-03-20 10:39:26 +09:00
|
|
|
return appType.GetInfo(appData);
|
2022-04-12 09:55:10 +02:00
|
|
|
}
|
2023-03-20 10:39:26 +09:00
|
|
|
|
2022-06-28 07:05:02 +02:00
|
|
|
public async Task<IEnumerable<ItemStats>> GetItemStats(AppData appData)
|
2022-04-12 09:55:10 +02:00
|
|
|
{
|
2023-03-20 10:39:26 +09:00
|
|
|
if (GetAppType(appData.AppType) is not IHasItemStatsAppType salesType)
|
|
|
|
throw new InvalidOperationException("This app isn't a SalesAppBaseType");
|
2023-04-10 11:07:03 +09:00
|
|
|
var paidInvoices = await GetInvoicesForApp(_InvoiceRepository, appData,
|
|
|
|
null, new[]
|
2022-06-28 07:05:02 +02:00
|
|
|
{
|
2023-03-17 03:56:32 +01:00
|
|
|
InvoiceState.ToString(InvoiceStatusLegacy.Paid),
|
|
|
|
InvoiceState.ToString(InvoiceStatusLegacy.Confirmed),
|
|
|
|
InvoiceState.ToString(InvoiceStatusLegacy.Complete)
|
|
|
|
});
|
2023-03-20 10:39:26 +09:00
|
|
|
return await salesType.GetItemStats(appData, paidInvoices);
|
2022-06-28 07:05:02 +02:00
|
|
|
}
|
|
|
|
|
2023-03-17 03:56:32 +01:00
|
|
|
public static Task<SalesStats> GetSalesStatswithPOSItems(ViewPointOfSaleViewModel.Item[] items,
|
2023-04-10 11:07:03 +09:00
|
|
|
InvoiceEntity[] paidInvoices, int numberOfDays)
|
2022-06-28 07:05:02 +02:00
|
|
|
{
|
2022-04-12 09:55:10 +02:00
|
|
|
var series = paidInvoices
|
2022-06-28 07:05:02 +02:00
|
|
|
.Aggregate(new List<InvoiceStatsItem>(), AggregateInvoiceEntitiesForStats(items))
|
|
|
|
.GroupBy(entity => entity.Date)
|
2022-04-12 09:55:10 +02:00
|
|
|
.Select(entities => new SalesStatsItem
|
|
|
|
{
|
|
|
|
Date = entities.Key,
|
|
|
|
Label = entities.Key.ToString("MMM dd", CultureInfo.InvariantCulture),
|
|
|
|
SalesCount = entities.Count()
|
|
|
|
});
|
|
|
|
|
|
|
|
// fill up the gaps
|
|
|
|
foreach (var i in Enumerable.Range(0, numberOfDays))
|
|
|
|
{
|
|
|
|
var date = (DateTimeOffset.UtcNow - TimeSpan.FromDays(i)).Date;
|
|
|
|
if (!series.Any(e => e.Date == date))
|
|
|
|
{
|
|
|
|
series = series.Append(new SalesStatsItem
|
|
|
|
{
|
|
|
|
Date = date,
|
|
|
|
Label = date.ToString("MMM dd", CultureInfo.InvariantCulture)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2023-01-06 14:18:07 +01:00
|
|
|
|
2023-03-17 03:56:32 +01:00
|
|
|
return Task.FromResult(new SalesStats
|
2022-04-12 09:55:10 +02:00
|
|
|
{
|
2022-06-28 07:05:02 +02:00
|
|
|
SalesCount = series.Sum(i => i.SalesCount),
|
2022-04-12 09:55:10 +02:00
|
|
|
Series = series.OrderBy(i => i.Label)
|
2023-03-17 03:56:32 +01:00
|
|
|
});
|
|
|
|
}
|
2023-04-10 11:07:03 +09:00
|
|
|
|
2023-03-17 03:56:32 +01:00
|
|
|
public async Task<SalesStats> GetSalesStats(AppData app, int numberOfDays = 7)
|
|
|
|
{
|
2023-03-20 10:39:26 +09:00
|
|
|
if (GetAppType(app.AppType) is not IHasSaleStatsAppType salesType)
|
|
|
|
throw new InvalidOperationException("This app isn't a SalesAppBaseType");
|
2023-03-17 03:56:32 +01:00
|
|
|
var paidInvoices = await GetInvoicesForApp(_InvoiceRepository, app, DateTimeOffset.UtcNow - TimeSpan.FromDays(numberOfDays),
|
2023-04-10 11:07:03 +09:00
|
|
|
new[]
|
2023-03-17 03:56:32 +01:00
|
|
|
{
|
|
|
|
InvoiceState.ToString(InvoiceStatusLegacy.Paid),
|
|
|
|
InvoiceState.ToString(InvoiceStatusLegacy.Confirmed),
|
|
|
|
InvoiceState.ToString(InvoiceStatusLegacy.Complete)
|
|
|
|
});
|
2023-04-10 11:07:03 +09:00
|
|
|
|
2023-03-20 10:39:26 +09:00
|
|
|
return await salesType.GetSalesStats(app, paidInvoices, numberOfDays);
|
2022-04-12 09:55:10 +02:00
|
|
|
}
|
|
|
|
|
2023-03-17 03:56:32 +01:00
|
|
|
public class InvoiceStatsItem
|
2022-06-28 07:05:02 +02:00
|
|
|
{
|
2023-03-20 10:39:26 +09:00
|
|
|
public string ItemCode { get; set; } = string.Empty;
|
2022-06-28 07:05:02 +02:00
|
|
|
public decimal FiatPrice { get; set; }
|
|
|
|
public DateTime Date { get; set; }
|
|
|
|
}
|
2023-01-06 14:18:07 +01:00
|
|
|
|
2023-03-17 03:56:32 +01:00
|
|
|
public static Func<List<InvoiceStatsItem>, InvoiceEntity, List<InvoiceStatsItem>> AggregateInvoiceEntitiesForStats(ViewPointOfSaleViewModel.Item[] items)
|
2022-06-28 07:05:02 +02:00
|
|
|
{
|
|
|
|
return (res, e) =>
|
|
|
|
{
|
2023-02-25 23:34:49 +09:00
|
|
|
if (e.Metadata.PosData != null)
|
2022-06-28 07:05:02 +02:00
|
|
|
{
|
|
|
|
// flatten single items from POS data
|
2023-02-25 23:34:49 +09:00
|
|
|
var data = e.Metadata.PosData.ToObject<PosAppData>();
|
2023-01-06 14:18:07 +01:00
|
|
|
if (data is not { Cart.Length: > 0 })
|
|
|
|
return res;
|
2022-06-28 07:05:02 +02:00
|
|
|
foreach (var lineItem in data.Cart)
|
|
|
|
{
|
|
|
|
var item = items.FirstOrDefault(p => p.Id == lineItem.Id);
|
2023-01-06 14:18:07 +01:00
|
|
|
if (item == null)
|
|
|
|
continue;
|
|
|
|
|
2022-06-28 07:05:02 +02:00
|
|
|
for (var i = 0; i < lineItem.Count; i++)
|
|
|
|
{
|
|
|
|
res.Add(new InvoiceStatsItem
|
|
|
|
{
|
|
|
|
ItemCode = item.Id,
|
2023-06-02 09:34:55 +02:00
|
|
|
FiatPrice = lineItem.Price,
|
2022-06-28 07:05:02 +02:00
|
|
|
Date = e.InvoiceTime.Date
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-07-06 05:40:16 +02:00
|
|
|
else
|
2023-07-19 18:47:32 +09:00
|
|
|
{;
|
2022-07-06 05:40:16 +02:00
|
|
|
res.Add(new InvoiceStatsItem
|
|
|
|
{
|
|
|
|
ItemCode = e.Metadata.ItemCode,
|
2023-07-19 18:47:32 +09:00
|
|
|
FiatPrice = e.PaidAmount.Net,
|
2022-07-06 05:40:16 +02:00
|
|
|
Date = e.InvoiceTime.Date
|
|
|
|
});
|
|
|
|
}
|
2022-06-28 07:05:02 +02:00
|
|
|
return res;
|
|
|
|
};
|
|
|
|
}
|
2023-04-10 11:07:03 +09:00
|
|
|
|
2023-03-17 03:56:32 +01:00
|
|
|
public static string GetAppOrderId(AppData app) => GetAppOrderId(app.AppType, app.Id);
|
|
|
|
public static string GetAppOrderId(string appType, string appId) =>
|
|
|
|
appType switch
|
2022-06-28 07:05:02 +02:00
|
|
|
{
|
2023-03-20 10:39:26 +09:00
|
|
|
CrowdfundAppType.AppType => $"crowdfund-app_{appId}",
|
|
|
|
PointOfSaleAppType.AppType => $"pos-app_{appId}",
|
2023-03-17 03:56:32 +01:00
|
|
|
_ => $"{appType}_{appId}"
|
2022-06-28 07:05:02 +02:00
|
|
|
};
|
|
|
|
|
2019-02-19 13:04:58 +09:00
|
|
|
public static string GetAppInternalTag(string appId) => $"APP#{appId}";
|
2019-02-25 16:15:45 +09:00
|
|
|
public static string[] GetAppInternalTags(InvoiceEntity invoice)
|
2019-02-19 13:04:58 +09:00
|
|
|
{
|
2019-02-25 16:15:45 +09:00
|
|
|
return invoice.GetInternalTags("APP#");
|
2019-02-19 13:04:58 +09:00
|
|
|
}
|
2023-01-06 14:18:07 +01:00
|
|
|
|
2023-04-10 11:07:03 +09:00
|
|
|
public static async Task<InvoiceEntity[]> GetInvoicesForApp(InvoiceRepository invoiceRepository, AppData appData, DateTimeOffset? startDate = null, string[]? status = null)
|
2019-02-19 13:04:58 +09:00
|
|
|
{
|
2023-03-17 03:56:32 +01:00
|
|
|
var invoices = await invoiceRepository.GetInvoices(new InvoiceQuery
|
2019-02-19 13:04:58 +09:00
|
|
|
{
|
2023-03-17 03:56:32 +01:00
|
|
|
StoreId = new[] { appData.StoreDataId },
|
2022-06-28 07:05:02 +02:00
|
|
|
OrderId = appData.TagAllInvoices ? null : new[] { GetAppOrderId(appData) },
|
2023-04-10 11:07:03 +09:00
|
|
|
Status = status ?? new[]{
|
2020-11-23 15:57:05 +09:00
|
|
|
InvoiceState.ToString(InvoiceStatusLegacy.New),
|
|
|
|
InvoiceState.ToString(InvoiceStatusLegacy.Paid),
|
|
|
|
InvoiceState.ToString(InvoiceStatusLegacy.Confirmed),
|
|
|
|
InvoiceState.ToString(InvoiceStatusLegacy.Complete)},
|
2019-02-19 13:04:58 +09:00
|
|
|
StartDate = startDate
|
|
|
|
});
|
|
|
|
|
|
|
|
// Old invoices may have invoices which were not tagged
|
2021-12-31 16:59:02 +09:00
|
|
|
invoices = invoices.Where(inv => appData.TagAllInvoices || inv.Version < InvoiceEntity.InternalTagSupport_Version ||
|
2019-02-19 13:04:58 +09:00
|
|
|
inv.InternalTags.Contains(GetAppInternalTag(appData.Id))).ToArray();
|
|
|
|
return invoices;
|
|
|
|
}
|
|
|
|
|
|
|
|
public async Task<bool> DeleteApp(AppData appData)
|
|
|
|
{
|
2023-03-17 03:56:32 +01:00
|
|
|
await using var ctx = _ContextFactory.CreateContext();
|
2022-01-14 17:50:29 +09:00
|
|
|
ctx.Apps.Add(appData);
|
|
|
|
ctx.Entry(appData).State = EntityState.Deleted;
|
|
|
|
return await ctx.SaveChangesAsync() == 1;
|
2019-02-19 13:04:58 +09:00
|
|
|
}
|
|
|
|
|
2023-03-20 10:39:26 +09:00
|
|
|
public async Task<ListAppsViewModel.ListAppViewModel[]> GetAllApps(string? userId, bool allowNoUser = false, string? storeId = null)
|
2019-02-19 13:04:58 +09:00
|
|
|
{
|
2023-03-17 03:56:32 +01:00
|
|
|
await using var ctx = _ContextFactory.CreateContext();
|
2023-05-26 16:49:32 +02:00
|
|
|
var listApps = (await ctx.UserStore
|
2022-01-14 17:50:29 +09:00
|
|
|
.Where(us =>
|
|
|
|
(allowNoUser && string.IsNullOrEmpty(userId) || us.ApplicationUserId == userId) &&
|
|
|
|
(storeId == null || us.StoreDataId == storeId))
|
2023-05-26 16:49:32 +02:00
|
|
|
.Include(store => store.StoreRole)
|
|
|
|
.Include(store => store.StoreData)
|
|
|
|
.Join(ctx.Apps, us => us.StoreDataId, app => app.StoreDataId, (us, app) => new { us, app })
|
|
|
|
.OrderBy(b => b.app.Created)
|
|
|
|
.ToArrayAsync()).Select(arg => new ListAppsViewModel.ListAppViewModel
|
|
|
|
{
|
|
|
|
Role = StoreRepository.ToStoreRole(arg.us.StoreRole),
|
|
|
|
StoreId = arg.us.StoreDataId,
|
|
|
|
StoreName = arg.us.StoreData.StoreName,
|
|
|
|
AppName = arg.app.Name,
|
|
|
|
AppType = arg.app.AppType,
|
|
|
|
Id = arg.app.Id,
|
|
|
|
Created = arg.app.Created,
|
|
|
|
App = arg.app
|
|
|
|
}).ToArray();
|
2023-04-10 11:07:03 +09:00
|
|
|
|
2023-02-02 12:53:42 +01:00
|
|
|
// allowNoUser can lead to apps being included twice, unify them with distinct
|
|
|
|
if (allowNoUser)
|
|
|
|
{
|
|
|
|
listApps = listApps.DistinctBy(a => a.Id).ToArray();
|
|
|
|
}
|
2022-01-14 17:50:29 +09:00
|
|
|
|
|
|
|
foreach (ListAppsViewModel.ListAppViewModel app in listApps)
|
2019-02-19 13:04:58 +09:00
|
|
|
{
|
2023-03-17 03:56:32 +01:00
|
|
|
app.ViewStyle = GetAppViewStyle(app.App, app.AppType);
|
2019-02-19 13:04:58 +09:00
|
|
|
}
|
2022-01-14 17:50:29 +09:00
|
|
|
|
|
|
|
return listApps;
|
2019-02-19 13:04:58 +09:00
|
|
|
}
|
2021-12-31 16:59:02 +09:00
|
|
|
|
2023-03-17 03:56:32 +01:00
|
|
|
public string GetAppViewStyle(AppData app, string appType)
|
2021-11-02 14:55:31 -04:00
|
|
|
{
|
|
|
|
string style;
|
2023-03-17 03:56:32 +01:00
|
|
|
switch (appType)
|
2021-11-02 14:55:31 -04:00
|
|
|
{
|
2023-03-20 10:39:26 +09:00
|
|
|
case PointOfSaleAppType.AppType:
|
2023-03-17 03:56:32 +01:00
|
|
|
var settings = app.GetSettings<PointOfSaleSettings>();
|
2021-11-02 14:55:31 -04:00
|
|
|
string posViewStyle = (settings.EnableShoppingCart ? PosViewType.Cart : settings.DefaultView).ToString();
|
|
|
|
style = typeof(PosViewType).DisplayName(posViewStyle);
|
|
|
|
break;
|
2023-04-10 11:07:03 +09:00
|
|
|
|
2021-11-02 14:55:31 -04:00
|
|
|
default:
|
|
|
|
style = string.Empty;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return style;
|
|
|
|
}
|
2020-06-28 17:55:27 +09:00
|
|
|
|
2019-07-09 11:20:38 +02:00
|
|
|
public async Task<List<AppData>> GetApps(string[] appIds, bool includeStore = false)
|
|
|
|
{
|
2023-03-17 03:56:32 +01:00
|
|
|
await using var ctx = _ContextFactory.CreateContext();
|
2022-01-14 17:50:29 +09:00
|
|
|
var query = ctx.Apps
|
2023-03-17 03:56:32 +01:00
|
|
|
.Where(app => appIds.Contains(app.Id));
|
2022-01-14 17:50:29 +09:00
|
|
|
if (includeStore)
|
|
|
|
{
|
|
|
|
query = query.Include(data => data.StoreData);
|
2019-07-09 11:20:38 +02:00
|
|
|
}
|
2022-01-14 17:50:29 +09:00
|
|
|
return await query.ToListAsync();
|
2019-07-09 11:20:38 +02:00
|
|
|
}
|
2019-02-19 13:04:58 +09:00
|
|
|
|
2023-03-17 03:56:32 +01:00
|
|
|
public async Task<List<AppData>> GetApps(string appType)
|
2019-02-19 13:04:58 +09:00
|
|
|
{
|
2023-03-17 03:56:32 +01:00
|
|
|
await using var ctx = _ContextFactory.CreateContext();
|
|
|
|
var query = ctx.Apps
|
|
|
|
.Where(app => app.AppType == appType);
|
|
|
|
return await query.ToListAsync();
|
|
|
|
}
|
|
|
|
|
2023-03-20 10:39:26 +09:00
|
|
|
public async Task<AppData?> GetApp(string appId, string? appType, bool includeStore = false)
|
2023-03-17 03:56:32 +01:00
|
|
|
{
|
|
|
|
await using var ctx = _ContextFactory.CreateContext();
|
2022-01-14 17:50:29 +09:00
|
|
|
var query = ctx.Apps
|
|
|
|
.Where(us => us.Id == appId &&
|
2023-03-17 03:56:32 +01:00
|
|
|
(appType == null || us.AppType == appType));
|
2019-02-19 13:04:58 +09:00
|
|
|
|
2022-01-14 17:50:29 +09:00
|
|
|
if (includeStore)
|
|
|
|
{
|
|
|
|
query = query.Include(data => data.StoreData);
|
2019-02-19 13:04:58 +09:00
|
|
|
}
|
2022-01-14 17:50:29 +09:00
|
|
|
return await query.FirstOrDefaultAsync();
|
2019-02-19 13:04:58 +09:00
|
|
|
}
|
|
|
|
|
2023-03-20 10:39:26 +09:00
|
|
|
public Task<StoreData?> GetStore(AppData app)
|
2019-02-19 13:04:58 +09:00
|
|
|
{
|
2019-05-30 07:02:52 +00:00
|
|
|
return _storeRepository.FindStore(app.StoreDataId);
|
2019-02-19 13:04:58 +09:00
|
|
|
}
|
2023-05-23 02:18:57 +02:00
|
|
|
|
2019-02-19 13:04:58 +09:00
|
|
|
|
2023-05-23 02:18:57 +02:00
|
|
|
public static string SerializeTemplate(ViewPointOfSaleViewModel.Item[] items)
|
2023-03-17 03:56:32 +01:00
|
|
|
{
|
2023-05-23 02:18:57 +02:00
|
|
|
return JsonConvert.SerializeObject(items, Formatting.Indented, _defaultSerializer);
|
2023-03-17 03:56:32 +01:00
|
|
|
}
|
2023-05-23 02:18:57 +02:00
|
|
|
public static ViewPointOfSaleViewModel.Item[] Parse(string template, bool includeDisabled = true)
|
2019-02-19 13:04:58 +09:00
|
|
|
{
|
|
|
|
if (string.IsNullOrWhiteSpace(template))
|
|
|
|
return Array.Empty<ViewPointOfSaleViewModel.Item>();
|
2021-10-11 12:46:05 +02:00
|
|
|
|
2023-05-23 02:18:57 +02:00
|
|
|
return JsonConvert.DeserializeObject<ViewPointOfSaleViewModel.Item[]>(template, _defaultSerializer)!.Where(item => includeDisabled || !item.Disabled).ToArray();
|
2019-02-19 13:04:58 +09:00
|
|
|
}
|
2023-03-20 10:39:26 +09:00
|
|
|
#nullable restore
|
|
|
|
#nullable enable
|
|
|
|
public async Task<AppData?> GetAppDataIfOwner(string userId, string appId, string? type = null)
|
2019-02-19 13:04:58 +09:00
|
|
|
{
|
|
|
|
if (userId == null || appId == null)
|
|
|
|
return null;
|
2023-03-17 03:56:32 +01:00
|
|
|
await using var ctx = _ContextFactory.CreateContext();
|
2022-01-14 17:50:29 +09:00
|
|
|
var app = await ctx.UserStore
|
2023-05-26 16:49:32 +02:00
|
|
|
.Include(store => store.StoreRole)
|
|
|
|
.Where(us => us.ApplicationUserId == userId && us.StoreRole.Permissions.Contains(Policies.CanModifyStoreSettings))
|
2022-01-14 17:50:29 +09:00
|
|
|
.SelectMany(us => us.StoreData.Apps.Where(a => a.Id == appId))
|
|
|
|
.FirstOrDefaultAsync();
|
|
|
|
if (app == null)
|
|
|
|
return null;
|
2023-03-17 03:56:32 +01:00
|
|
|
if (type != null && type != app.AppType)
|
2022-01-14 17:50:29 +09:00
|
|
|
return null;
|
|
|
|
return app;
|
2019-02-19 13:04:58 +09:00
|
|
|
}
|
2019-09-02 15:37:52 +02:00
|
|
|
|
|
|
|
public async Task UpdateOrCreateApp(AppData app)
|
|
|
|
{
|
2023-03-17 03:56:32 +01:00
|
|
|
await using var ctx = _ContextFactory.CreateContext();
|
2022-01-14 17:50:29 +09:00
|
|
|
if (string.IsNullOrEmpty(app.Id))
|
2019-09-02 15:37:52 +02:00
|
|
|
{
|
2022-01-14 17:50:29 +09:00
|
|
|
app.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(20));
|
|
|
|
app.Created = DateTimeOffset.UtcNow;
|
|
|
|
await ctx.Apps.AddAsync(app);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ctx.Apps.Update(app);
|
|
|
|
ctx.Entry(app).Property(data => data.Created).IsModified = false;
|
|
|
|
ctx.Entry(app).Property(data => data.Id).IsModified = false;
|
|
|
|
ctx.Entry(app).Property(data => data.AppType).IsModified = false;
|
2019-09-02 15:37:52 +02:00
|
|
|
}
|
2022-01-14 17:50:29 +09:00
|
|
|
await ctx.SaveChangesAsync();
|
2019-09-02 15:37:52 +02:00
|
|
|
}
|
2020-06-28 17:55:27 +09:00
|
|
|
|
2023-03-20 10:39:26 +09:00
|
|
|
private static bool TryParseJson(string json, [MaybeNullWhen(false)] out JObject result)
|
2019-09-02 15:37:52 +02:00
|
|
|
{
|
|
|
|
result = null;
|
|
|
|
try
|
|
|
|
{
|
|
|
|
result = JObject.Parse(json);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
catch
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2023-02-25 23:34:49 +09:00
|
|
|
#nullable enable
|
|
|
|
public static bool TryParsePosCartItems(JObject? posData, [MaybeNullWhen(false)] out Dictionary<string, int> cartItems)
|
2019-09-02 15:37:52 +02:00
|
|
|
{
|
|
|
|
cartItems = null;
|
2023-02-25 23:34:49 +09:00
|
|
|
if (posData is null)
|
|
|
|
return false;
|
|
|
|
if (!posData.TryGetValue("cart", out var cartObject))
|
|
|
|
return false;
|
|
|
|
if (cartObject is null)
|
2020-06-28 17:55:27 +09:00
|
|
|
return false;
|
2023-02-25 23:34:49 +09:00
|
|
|
|
|
|
|
cartItems = new();
|
|
|
|
foreach (var o in cartObject.OfType<JObject>())
|
|
|
|
{
|
|
|
|
var id = o.GetValue("id", StringComparison.InvariantCulture)?.ToString();
|
|
|
|
if (id != null)
|
|
|
|
{
|
|
|
|
var countStr = o.GetValue("count", StringComparison.InvariantCulture)?.ToString() ?? string.Empty;
|
|
|
|
if (int.TryParse(countStr, out var count))
|
|
|
|
{
|
|
|
|
cartItems.TryAdd(id, count);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-09-02 15:37:52 +02:00
|
|
|
return true;
|
|
|
|
}
|
2023-03-17 03:56:32 +01:00
|
|
|
|
|
|
|
public async Task SetDefaultSettings(AppData appData, string defaultCurrency)
|
|
|
|
{
|
2023-03-20 10:39:26 +09:00
|
|
|
var app = GetAppType(appData.AppType);
|
2023-03-17 03:56:32 +01:00
|
|
|
if (app is null)
|
|
|
|
{
|
|
|
|
appData.SetSettings(null);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
await app.SetDefaultSettings(appData, defaultCurrency);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async Task<string?> ViewLink(AppData app)
|
|
|
|
{
|
2023-03-20 10:39:26 +09:00
|
|
|
var appType = GetAppType(app.AppType);
|
2023-03-17 03:56:32 +01:00
|
|
|
return await appType?.ViewLink(app)!;
|
|
|
|
}
|
2023-02-25 23:34:49 +09:00
|
|
|
#nullable restore
|
2019-02-19 13:04:58 +09:00
|
|
|
}
|
2022-04-12 09:55:10 +02:00
|
|
|
|
|
|
|
public class ItemStats
|
|
|
|
{
|
|
|
|
public string ItemCode { get; set; }
|
|
|
|
public string Title { get; set; }
|
|
|
|
public int SalesCount { get; set; }
|
|
|
|
public decimal Total { get; set; }
|
|
|
|
public string TotalFormatted { get; set; }
|
|
|
|
}
|
2023-01-06 14:18:07 +01:00
|
|
|
|
2022-04-12 09:55:10 +02:00
|
|
|
public class SalesStats
|
|
|
|
{
|
|
|
|
public int SalesCount { get; set; }
|
|
|
|
public IEnumerable<SalesStatsItem> Series { get; set; }
|
|
|
|
}
|
2023-01-06 14:18:07 +01:00
|
|
|
|
2022-04-12 09:55:10 +02:00
|
|
|
public class SalesStatsItem
|
|
|
|
{
|
|
|
|
public DateTime Date { get; set; }
|
|
|
|
public string Label { get; set; }
|
|
|
|
public int SalesCount { get; set; }
|
|
|
|
}
|
2019-02-19 13:04:58 +09:00
|
|
|
}
|