mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-10 09:19:24 +01:00
POS: Handle flexible price items in cart view (#5238)
This commit is contained in:
parent
19d360a543
commit
d67ebd957e
7 changed files with 166 additions and 68 deletions
|
@ -663,7 +663,7 @@ donation:
|
|||
Assert.Equal(3, vmview.Items.Length);
|
||||
Assert.Equal("good apple", vmview.Items[0].Title);
|
||||
Assert.Equal("orange", vmview.Items[1].Title);
|
||||
Assert.Equal(10.0m, vmview.Items[1].Price.Value);
|
||||
Assert.Equal(10.0m, vmview.Items[1].Price);
|
||||
Assert.Equal("{0} Purchase", vmview.ButtonText);
|
||||
Assert.Equal("Nicolas Sexy Hair", vmview.CustomButtonText);
|
||||
Assert.Equal("Wanna tip?", vmview.CustomTipText);
|
||||
|
@ -680,7 +680,7 @@ donation:
|
|||
Assert.IsType<RedirectToActionResult>(publicApps
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 0, choiceKey: "apple").Result);
|
||||
|
||||
invoices = user.BitPay.GetInvoices();
|
||||
invoices = await user.BitPay.GetInvoicesAsync();
|
||||
var appleInvoice = invoices.SingleOrDefault(invoice => invoice.ItemCode.Equals("apple"));
|
||||
Assert.NotNull(appleInvoice);
|
||||
Assert.Equal("good apple", appleInvoice.ItemDesc);
|
||||
|
@ -689,7 +689,7 @@ donation:
|
|||
var action = Assert.IsType<RedirectToActionResult>(publicApps
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 6.6m, choiceKey: "donation").Result);
|
||||
Assert.Equal(nameof(UIInvoiceController.Checkout), action.ActionName);
|
||||
invoices = user.BitPay.GetInvoices();
|
||||
invoices = await user.BitPay.GetInvoicesAsync();
|
||||
var donationInvoice = invoices.Single(i => i.Price == 6.6m);
|
||||
Assert.NotNull(donationInvoice);
|
||||
Assert.Equal("CAD", donationInvoice.Currency);
|
||||
|
|
|
@ -2064,7 +2064,6 @@ namespace BTCPayServer.Tests
|
|||
using var s = CreateSeleniumTester();
|
||||
s.Server.ActivateLightning();
|
||||
await s.StartAsync();
|
||||
|
||||
await s.Server.EnsureChannelsSetup();
|
||||
|
||||
s.RegisterNewUser(true);
|
||||
|
@ -2101,7 +2100,6 @@ namespace BTCPayServer.Tests
|
|||
using var s = CreateSeleniumTester();
|
||||
s.Server.ActivateLightning();
|
||||
await s.StartAsync();
|
||||
|
||||
await s.Server.EnsureChannelsSetup();
|
||||
|
||||
s.RegisterNewUser(true);
|
||||
|
@ -2176,7 +2174,6 @@ namespace BTCPayServer.Tests
|
|||
using var s = CreateSeleniumTester();
|
||||
s.Server.ActivateLightning();
|
||||
await s.StartAsync();
|
||||
|
||||
await s.Server.EnsureChannelsSetup();
|
||||
|
||||
s.RegisterNewUser(true);
|
||||
|
@ -2199,6 +2196,7 @@ namespace BTCPayServer.Tests
|
|||
s.Driver.SwitchTo().Window(windows[1]);
|
||||
s.Driver.WaitForElement(By.Id("PosItems"));
|
||||
Assert.Empty(s.Driver.FindElements(By.CssSelector("#CartItems tr")));
|
||||
var posUrl = s.Driver.Url;
|
||||
|
||||
// Select and clear
|
||||
s.Driver.FindElement(By.CssSelector(".posItem:nth-child(1) .btn-primary")).Click();
|
||||
|
@ -2207,34 +2205,81 @@ namespace BTCPayServer.Tests
|
|||
Thread.Sleep(250);
|
||||
Assert.Empty(s.Driver.FindElements(By.CssSelector("#CartItems tr")));
|
||||
|
||||
// Select items
|
||||
s.Driver.FindElement(By.CssSelector(".posItem:nth-child(2) .btn-primary")).Click();
|
||||
Thread.Sleep(250);
|
||||
// Select simple items
|
||||
s.Driver.FindElement(By.CssSelector(".posItem:nth-child(1) .btn-primary")).Click();
|
||||
Thread.Sleep(250);
|
||||
Assert.Single(s.Driver.FindElements(By.CssSelector("#CartItems tr")));
|
||||
s.Driver.FindElement(By.CssSelector(".posItem:nth-child(2) .btn-primary")).Click();
|
||||
Thread.Sleep(250);
|
||||
s.Driver.FindElement(By.CssSelector(".posItem:nth-child(2) .btn-primary")).Click();
|
||||
Thread.Sleep(250);
|
||||
Assert.Equal(2, s.Driver.FindElements(By.CssSelector("#CartItems tr")).Count);
|
||||
Assert.Equal("3,00 €", s.Driver.FindElement(By.Id("CartTotal")).Text);
|
||||
|
||||
// Select item with inventory - two of it
|
||||
Assert.Equal("5 left", s.Driver.FindElement(By.CssSelector(".posItem:nth-child(3) .badge.inventory")).Text);
|
||||
s.Driver.FindElement(By.CssSelector(".posItem:nth-child(3) .btn-primary")).Click();
|
||||
Thread.Sleep(250);
|
||||
s.Driver.FindElement(By.CssSelector(".posItem:nth-child(3) .btn-primary")).Click();
|
||||
Thread.Sleep(250);
|
||||
Assert.Equal(3, s.Driver.FindElements(By.CssSelector("#CartItems tr")).Count);
|
||||
Assert.Equal("5,40 €", s.Driver.FindElement(By.Id("CartTotal")).Text);
|
||||
|
||||
// Select items with minimum amount
|
||||
s.Driver.FindElement(By.CssSelector(".posItem:nth-child(5) .btn-primary")).Click();
|
||||
Thread.Sleep(250);
|
||||
Assert.Equal(4, s.Driver.FindElements(By.CssSelector("#CartItems tr")).Count);
|
||||
Assert.Equal("7,20 €", s.Driver.FindElement(By.Id("CartTotal")).Text);
|
||||
|
||||
// Select items with adjusted minimum amount
|
||||
s.Driver.FindElement(By.CssSelector(".posItem:nth-child(5) input[name='amount']")).Clear();
|
||||
s.Driver.FindElement(By.CssSelector(".posItem:nth-child(5) input[name='amount']")).SendKeys("2.3");
|
||||
s.Driver.FindElement(By.CssSelector(".posItem:nth-child(5) .btn-primary")).Click();
|
||||
Thread.Sleep(250);
|
||||
Assert.Equal(5, s.Driver.FindElements(By.CssSelector("#CartItems tr")).Count);
|
||||
Assert.Equal("9,50 €", s.Driver.FindElement(By.Id("CartTotal")).Text);
|
||||
|
||||
// Select items with custom amount
|
||||
s.Driver.FindElement(By.CssSelector(".posItem:nth-child(6) input[name='amount']")).Clear();
|
||||
s.Driver.FindElement(By.CssSelector(".posItem:nth-child(6) input[name='amount']")).SendKeys(".2");
|
||||
s.Driver.FindElement(By.CssSelector(".posItem:nth-child(6) .btn-primary")).Click();
|
||||
Thread.Sleep(250);
|
||||
Assert.Equal(6, s.Driver.FindElements(By.CssSelector("#CartItems tr")).Count);
|
||||
Assert.Equal("9,70 €", s.Driver.FindElement(By.Id("CartTotal")).Text);
|
||||
|
||||
// Select items with another custom amount
|
||||
s.Driver.FindElement(By.CssSelector(".posItem:nth-child(6) input[name='amount']")).Clear();
|
||||
s.Driver.FindElement(By.CssSelector(".posItem:nth-child(6) input[name='amount']")).SendKeys(".3");
|
||||
s.Driver.FindElement(By.CssSelector(".posItem:nth-child(6) .btn-primary")).Click();
|
||||
Thread.Sleep(250);
|
||||
Assert.Equal(7, s.Driver.FindElements(By.CssSelector("#CartItems tr")).Count);
|
||||
Assert.Equal("10,00 €", s.Driver.FindElement(By.Id("CartTotal")).Text);
|
||||
|
||||
// Discount: 10%
|
||||
s.Driver.ElementDoesNotExist(By.Id("CartDiscount"));
|
||||
s.Driver.FindElement(By.Id("Discount")).SendKeys("10");
|
||||
Assert.Contains("10% = 0,30 €", s.Driver.FindElement(By.Id("CartDiscount")).Text);
|
||||
Assert.Equal("2,70 €", s.Driver.FindElement(By.Id("CartTotal")).Text);
|
||||
Assert.Contains("10% = 1,00 €", s.Driver.FindElement(By.Id("CartDiscount")).Text);
|
||||
Assert.Equal("9,00 €", s.Driver.FindElement(By.Id("CartTotal")).Text);
|
||||
|
||||
// Tip: 10%
|
||||
s.Driver.ElementDoesNotExist(By.Id("CartTip"));
|
||||
s.Driver.FindElement(By.Id("Tip-10")).Click();
|
||||
Assert.Contains("10% = 0,27 €", s.Driver.FindElement(By.Id("CartTip")).Text);
|
||||
Assert.Equal("2,97 €", s.Driver.FindElement(By.Id("CartTotal")).Text);
|
||||
Assert.Contains("10% = 0,90 €", s.Driver.FindElement(By.Id("CartTip")).Text);
|
||||
Assert.Equal("9,90 €", s.Driver.FindElement(By.Id("CartTotal")).Text);
|
||||
|
||||
// Pay
|
||||
// Check values on checkout page
|
||||
s.Driver.FindElement(By.Id("CartSubmit")).Click();
|
||||
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
|
||||
s.Driver.FindElement(By.Id("DetailsToggle")).Click();
|
||||
s.Driver.WaitForElement(By.Id("PaymentDetails-TotalFiat"));
|
||||
Assert.Contains("2,97 €", s.Driver.FindElement(By.Id("PaymentDetails-TotalFiat")).Text);
|
||||
Assert.Contains("9,90 €", s.Driver.FindElement(By.Id("PaymentDetails-TotalFiat")).Text);
|
||||
|
||||
// Pay
|
||||
s.PayInvoice();
|
||||
|
||||
// Check inventory got updated and is now 3 instead of 5
|
||||
s.Driver.Navigate().GoToUrl(posUrl);
|
||||
Assert.Equal("3 left", s.Driver.FindElement(By.CssSelector(".posItem:nth-child(3) .badge.inventory")).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
@ -50,45 +51,47 @@ namespace BTCPayServer.HostedServices
|
|||
}
|
||||
}).Where(tuple => tuple.Data != null && tuple.Items.Any(item =>
|
||||
item.Inventory.HasValue &&
|
||||
updateAppInventory.Items.ContainsKey(item.Id)));
|
||||
foreach (var valueTuple in apps)
|
||||
updateAppInventory.Items.FirstOrDefault(i => i.Id == item.Id) != null));
|
||||
foreach (var app in apps)
|
||||
{
|
||||
foreach (var item1 in valueTuple.Items.Where(item =>
|
||||
updateAppInventory.Items.ContainsKey(item.Id)))
|
||||
foreach (var cartItem in updateAppInventory.Items)
|
||||
{
|
||||
var item = app.Items.FirstOrDefault(item => item.Id == cartItem.Id);
|
||||
if (item == null) continue;
|
||||
|
||||
if (updateAppInventory.Deduct)
|
||||
{
|
||||
item1.Inventory -= updateAppInventory.Items[item1.Id];
|
||||
item.Inventory -= cartItem.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
item1.Inventory += updateAppInventory.Items[item1.Id];
|
||||
item.Inventory += cartItem.Count;
|
||||
}
|
||||
}
|
||||
|
||||
switch (valueTuple.Data.AppType)
|
||||
switch (app.Data.AppType)
|
||||
{
|
||||
case PointOfSaleAppType.AppType:
|
||||
((PointOfSaleSettings)valueTuple.Settings).Template =
|
||||
AppService.SerializeTemplate(valueTuple.Items);
|
||||
((PointOfSaleSettings)app.Settings).Template =
|
||||
AppService.SerializeTemplate(app.Items);
|
||||
break;
|
||||
case CrowdfundAppType.AppType:
|
||||
((CrowdfundSettings)valueTuple.Settings).PerksTemplate =
|
||||
AppService.SerializeTemplate(valueTuple.Items);
|
||||
((CrowdfundSettings)app.Settings).PerksTemplate =
|
||||
AppService.SerializeTemplate(app.Items);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
valueTuple.Data.SetSettings(valueTuple.Settings);
|
||||
await _appService.UpdateOrCreateApp(valueTuple.Data);
|
||||
app.Data.SetSettings(app.Settings);
|
||||
await _appService.UpdateOrCreateApp(app.Data);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else if (evt is InvoiceEvent invoiceEvent)
|
||||
{
|
||||
Dictionary<string, int> cartItems = null;
|
||||
List<PosCartItem> cartItems = null;
|
||||
bool deduct;
|
||||
switch (invoiceEvent.Name)
|
||||
{
|
||||
|
@ -104,8 +107,8 @@ namespace BTCPayServer.HostedServices
|
|||
return;
|
||||
}
|
||||
|
||||
if ((!string.IsNullOrEmpty(invoiceEvent.Invoice.Metadata.ItemCode) ||
|
||||
AppService.TryParsePosCartItems(invoiceEvent.Invoice.Metadata.PosData, out cartItems)))
|
||||
if (!string.IsNullOrEmpty(invoiceEvent.Invoice.Metadata.ItemCode) ||
|
||||
AppService.TryParsePosCartItems(invoiceEvent.Invoice.Metadata.PosData, out cartItems))
|
||||
{
|
||||
var appIds = AppService.GetAppInternalTags(invoiceEvent.Invoice);
|
||||
|
||||
|
@ -114,13 +117,18 @@ namespace BTCPayServer.HostedServices
|
|||
return;
|
||||
}
|
||||
|
||||
var items = cartItems ?? new Dictionary<string, int>();
|
||||
var items = cartItems?.ToList() ?? new List<PosCartItem>();
|
||||
if (!string.IsNullOrEmpty(invoiceEvent.Invoice.Metadata.ItemCode))
|
||||
{
|
||||
items.TryAdd(invoiceEvent.Invoice.Metadata.ItemCode, 1);
|
||||
items.Add(new PosCartItem
|
||||
{
|
||||
Id = invoiceEvent.Invoice.Metadata.ItemCode,
|
||||
Count = 1,
|
||||
Price = invoiceEvent.Invoice.Price
|
||||
});
|
||||
}
|
||||
|
||||
_eventAggregator.Publish(new UpdateAppInventory()
|
||||
_eventAggregator.Publish(new UpdateAppInventory
|
||||
{
|
||||
Deduct = deduct,
|
||||
Items = items,
|
||||
|
@ -134,7 +142,7 @@ namespace BTCPayServer.HostedServices
|
|||
public class UpdateAppInventory
|
||||
{
|
||||
public string[] AppId { get; set; }
|
||||
public Dictionary<string, int> Items { get; set; }
|
||||
public List<PosCartItem> Items { get; set; }
|
||||
public bool Deduct { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
|
|
|
@ -171,7 +171,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
|||
decimal? price;
|
||||
Dictionary<string, InvoiceSupportedTransactionCurrency> paymentMethods = null;
|
||||
ViewPointOfSaleViewModel.Item choice = null;
|
||||
Dictionary<string, int> cartItems = null;
|
||||
List<PosCartItem> cartItems = null;
|
||||
ViewPointOfSaleViewModel.Item[] choices = null;
|
||||
if (!string.IsNullOrEmpty(choiceKey))
|
||||
{
|
||||
|
@ -208,16 +208,15 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
|||
return NotFound();
|
||||
|
||||
title = settings.Title;
|
||||
//if cart IS enabled and we detect posdata that matches the cart system's, check inventory for the items
|
||||
// if cart IS enabled and we detect posdata that matches the cart system's, check inventory for the items
|
||||
price = amount;
|
||||
if (currentView == PosViewType.Cart &&
|
||||
AppService.TryParsePosCartItems(jposData, out cartItems))
|
||||
if (currentView == PosViewType.Cart && AppService.TryParsePosCartItems(jposData, out cartItems))
|
||||
{
|
||||
price = 0.0m;
|
||||
choices = AppService.Parse(settings.Template, false);
|
||||
foreach (var cartItem in cartItems)
|
||||
{
|
||||
var itemChoice = choices.FirstOrDefault(c => c.Id == cartItem.Key);
|
||||
var itemChoice = choices.FirstOrDefault(item => item.Id == cartItem.Id);
|
||||
if (itemChoice == null)
|
||||
return NotFound();
|
||||
|
||||
|
@ -225,20 +224,21 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
|||
{
|
||||
switch (itemChoice.Inventory)
|
||||
{
|
||||
case int i when i <= 0:
|
||||
case <= 0:
|
||||
return RedirectToAction(nameof(ViewPointOfSale), new { appId });
|
||||
case int inventory when inventory < cartItem.Value:
|
||||
case { } inventory when inventory < cartItem.Count:
|
||||
return RedirectToAction(nameof(ViewPointOfSale), new { appId });
|
||||
}
|
||||
}
|
||||
|
||||
decimal expectedCartItemPrice = 0;
|
||||
if (itemChoice.PriceType != ViewPointOfSaleViewModel.ItemPriceType.Topup)
|
||||
{
|
||||
expectedCartItemPrice = itemChoice.Price ?? 0;
|
||||
}
|
||||
var expectedCartItemPrice = itemChoice.PriceType != ViewPointOfSaleViewModel.ItemPriceType.Topup
|
||||
? itemChoice.Price ?? 0
|
||||
: 0;
|
||||
|
||||
if (cartItem.Price < expectedCartItemPrice)
|
||||
cartItem.Price = expectedCartItemPrice;
|
||||
|
||||
price += expectedCartItemPrice * cartItem.Value;
|
||||
price += cartItem.Price * cartItem.Count;
|
||||
}
|
||||
if (customAmount is { } c)
|
||||
price += c;
|
||||
|
@ -315,7 +315,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
|||
{
|
||||
Amount = price,
|
||||
Currency = settings.Currency,
|
||||
Metadata = new InvoiceMetadata()
|
||||
Metadata = new InvoiceMetadata
|
||||
{
|
||||
ItemCode = choice?.Id,
|
||||
ItemDesc = title,
|
||||
|
@ -358,17 +358,19 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
|||
receiptData = new JObject();
|
||||
if (cartItems is not null && choices is not null)
|
||||
{
|
||||
var selectedChoices = choices.Where(item => cartItems.Keys.Contains(item.Id))
|
||||
var posCartItems = cartItems.ToList();
|
||||
var selectedChoices = choices
|
||||
.Where(item => posCartItems.Any(cartItem => cartItem.Id == item.Id))
|
||||
.ToDictionary(item => item.Id);
|
||||
var cartData = new JObject();
|
||||
foreach (KeyValuePair<string, int> cartItem in cartItems)
|
||||
foreach (PosCartItem cartItem in posCartItems)
|
||||
{
|
||||
if (selectedChoices.TryGetValue(cartItem.Key, out var selectedChoice))
|
||||
{
|
||||
cartData.Add(selectedChoice.Title ?? selectedChoice.Id,
|
||||
$"{(selectedChoice.Price is null ? "Any price" : $"{_displayFormatter.Currency((decimal)selectedChoice.Price.Value, settings.Currency, DisplayFormatter.CurrencyFormat.Symbol)}")} x {cartItem.Value} = {(selectedChoice.Price is null ? "Any price" : $"{_displayFormatter.Currency(((decimal)selectedChoice.Price.Value) * cartItem.Value, settings.Currency, DisplayFormatter.CurrencyFormat.Symbol)}")}");
|
||||
|
||||
}
|
||||
if (!selectedChoices.TryGetValue(cartItem.Id, out var selectedChoice)) continue;
|
||||
var singlePrice = _displayFormatter.Currency(cartItem.Price, settings.Currency, DisplayFormatter.CurrencyFormat.Symbol);
|
||||
var totalPrice = _displayFormatter.Currency(cartItem.Price * cartItem.Count, settings.Currency, DisplayFormatter.CurrencyFormat.Symbol);
|
||||
var ident = selectedChoice.Title ?? selectedChoice.Id;
|
||||
var key = selectedChoice.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Fixed ? ident : $"{ident} ({singlePrice})";
|
||||
cartData.Add(key, $"{singlePrice} x {cartItem.Count} = {totalPrice}");
|
||||
}
|
||||
receiptData.Add("Cart", cartData);
|
||||
}
|
||||
|
@ -621,7 +623,6 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
|||
return View("PointOfSale/UpdatePointOfSale", vm);
|
||||
}
|
||||
|
||||
var storeBlob = GetCurrentStore().GetStoreBlob();
|
||||
var settings = new PointOfSaleSettings
|
||||
{
|
||||
Title = vm.Title,
|
||||
|
@ -640,11 +641,10 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
|||
RedirectUrl = vm.RedirectUrl,
|
||||
Description = vm.Description,
|
||||
EmbeddedCSS = vm.EmbeddedCSS,
|
||||
RedirectAutomatically =
|
||||
string.IsNullOrEmpty(vm.RedirectAutomatically) ? null : bool.Parse(vm.RedirectAutomatically)
|
||||
RedirectAutomatically = string.IsNullOrEmpty(vm.RedirectAutomatically) ? null : bool.Parse(vm.RedirectAutomatically),
|
||||
FormId = vm.FormId
|
||||
};
|
||||
|
||||
settings.FormId = vm.FormId;
|
||||
app.Name = vm.AppName;
|
||||
app.SetSettings(settings);
|
||||
await _appService.UpdateOrCreateApp(app);
|
||||
|
|
|
@ -411,7 +411,6 @@ namespace BTCPayServer.Services.Apps
|
|||
return false;
|
||||
if (cartObject is null)
|
||||
return false;
|
||||
|
||||
cartItems = new();
|
||||
foreach (var o in cartObject.OfType<JObject>())
|
||||
{
|
||||
|
@ -427,6 +426,29 @@ namespace BTCPayServer.Services.Apps
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryParsePosCartItems(JObject? posData, [MaybeNullWhen(false)] out List<PosCartItem> cartItems)
|
||||
{
|
||||
cartItems = null;
|
||||
if (posData is null)
|
||||
return false;
|
||||
if (!posData.TryGetValue("cart", out var cartObject))
|
||||
return false;
|
||||
|
||||
cartItems = new List<PosCartItem>();
|
||||
foreach (var o in cartObject.OfType<JObject>())
|
||||
{
|
||||
var id = o.GetValue("id", StringComparison.InvariantCulture)?.ToString();
|
||||
if (id == null) continue;
|
||||
var countStr = o.GetValue("count", StringComparison.InvariantCulture)?.ToString() ?? string.Empty;
|
||||
var price = o.GetValue("price")?.Value<decimal>() ?? 0m;
|
||||
if (int.TryParse(countStr, out var count))
|
||||
{
|
||||
cartItems.Add(new PosCartItem { Id = id, Count = count, Price = price });
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task SetDefaultSettings(AppData appData, string defaultCurrency)
|
||||
{
|
||||
|
@ -449,6 +471,13 @@ namespace BTCPayServer.Services.Apps
|
|||
#nullable restore
|
||||
}
|
||||
|
||||
public class PosCartItem
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public int Count { get; set; }
|
||||
public decimal Price { get; set; }
|
||||
}
|
||||
|
||||
public class ItemStats
|
||||
{
|
||||
public string ItemCode { get; set; }
|
||||
|
|
|
@ -83,7 +83,7 @@
|
|||
}
|
||||
@if (item.Inventory.HasValue)
|
||||
{
|
||||
<span class="badge text-bg-warning" v-text="inventoryText(@index)">
|
||||
<span class="badge text-bg-warning inventory" v-text="inventoryText(@index)">
|
||||
@(item.Inventory > 0 ? $"{item.Inventory} left" : "Sold out")
|
||||
</span>
|
||||
}
|
||||
|
@ -95,11 +95,18 @@
|
|||
</div>
|
||||
@if (inStock)
|
||||
{
|
||||
<div class="card-footer bg-transparent border-0 pt-0 pb-3">
|
||||
<form class="card-footer bg-transparent border-0 pt-0 pb-3">
|
||||
@if (item.PriceType != ViewPointOfSaleViewModel.ItemPriceType.Fixed)
|
||||
{
|
||||
<div class="input-group mb-2">
|
||||
<span class="input-group-text">@Model.CurrencySymbol</span>
|
||||
<input class="form-control" type="number" min="@(item.Price ?? 0)" step="@Model.Step" name="amount" placeholder="Amount" value="@item.Price" required v-on:click.stop>
|
||||
</div>
|
||||
}
|
||||
<button type="button" class="btn btn-primary w-100" :disabled="!inStock(@index)">
|
||||
@Safe.RawEncode(buttonText)
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="posItem-added"><vc:icon symbol="checkmark" /></div>
|
||||
}
|
||||
</div>
|
||||
|
@ -141,7 +148,7 @@
|
|||
</td>
|
||||
<td class="align-middle">
|
||||
<div class="d-flex align-items-center gap-2 justify-content-end quantity">
|
||||
<span class="badge text-bg-warning" v-if="item.inventory">
|
||||
<span class="badge text-bg-warning inventory" v-if="item.inventory">
|
||||
{{ item.inventory > 0 ? `${item.inventory} left` : "Sold out" }}
|
||||
</span>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
|
|
|
@ -121,7 +121,17 @@ document.addEventListener("DOMContentLoaded",function () {
|
|||
if (!this.inStock(index)) return false;
|
||||
|
||||
const item = this.items[index];
|
||||
let itemInCart = this.cart.find(lineItem => lineItem.id === item.id);
|
||||
const $posItem = this.$refs.posItems.querySelectorAll('.posItem')[index];
|
||||
|
||||
// Check if price is needed
|
||||
const isFixedPrice = item.priceType.toLowerCase() === 'fixed';
|
||||
if (!isFixedPrice) {
|
||||
const $amount = $posItem.querySelector('input[name="amount"]');
|
||||
if (!$amount.reportValidity()) return false;
|
||||
item.price = parseFloat($amount.value);
|
||||
}
|
||||
|
||||
let itemInCart = this.cart.find(lineItem => lineItem.id === item.id && lineItem.price === item.price);
|
||||
|
||||
// Add new item because it doesn't exist yet
|
||||
if (!itemInCart) {
|
||||
|
@ -138,7 +148,6 @@ document.addEventListener("DOMContentLoaded",function () {
|
|||
itemInCart.count += 1;
|
||||
|
||||
// Animate
|
||||
const $posItem = this.$refs.posItems.querySelectorAll('.posItem')[index];
|
||||
if(!$posItem.classList.contains(POS_ITEM_ADDED_CLASS)) $posItem.classList.add(POS_ITEM_ADDED_CLASS);
|
||||
|
||||
return true;
|
||||
|
|
Loading…
Add table
Reference in a new issue