Add support for adding 'loose' items to transactions
All checks were successful
Docker Image CI / build (push) Successful in 3m45s

This commit is contained in:
2024-10-12 02:36:38 +01:00
parent dfcab40d70
commit 47d13ba922
13 changed files with 178 additions and 46 deletions

View File

@ -16,5 +16,5 @@
public required Transaction Transaction { get; set; }
[Parameter]
public TransactionItem? TransactionItem { get; set; }
public required TransactionItem TransactionItem { get; set; }
}

View File

@ -25,7 +25,7 @@
public required Transaction Transaction { get; set; }
[Parameter]
public TransactionItem? TransactionItem { get; set; }
public required TransactionItem TransactionItem { get; set; }
private string store = string.Empty;

View File

@ -16,9 +16,19 @@
<section class="card form-field">
<div class="card__header row">
<h2 class="row__fill">Items</h2>
<a class="button button--primary" href="/transactions/new/items/new" autofocus data-turbo-frame="modal">
New item
</a>
<div class="button-group dropdown">
<a class="button button--primary" href="/transactions/new/items/new" autofocus data-turbo-frame="modal">
New item
</a>
<button class="button button--primary dropdown__toggle" type="button" popovertarget="newItemMenu"></button>
<ul class="dropdown__menu" id="newItemMenu" popover>
<li>
<a class="button dropdown__item" href="/transactions/new/items/new?unit=kg" data-turbo-frame="modal">
New loose item
</a>
</li>
</ul>
</div>
</div>
<div class="card__content card__content--table">
@ -27,8 +37,8 @@
<TemplateTableColumn Title="Name" Fill="true" Context="item">
@itemNames.GetValueOrDefault(item.ItemId)
</TemplateTableColumn>
<PropertyTableColumn Property="i => i.Price" Format="c" />
<PropertyTableColumn Property="i => i.Quantity">
<PropertyTableColumn Property="i => i.Price" CompositeFormat='i => i.Unit == null ? "{0:c}" : ("{0:c}/" + i.Unit)' />
<PropertyTableColumn Property="i => i.Quantity" CompositeFormat='i => i.Unit == null ? "{0:f0}" : ("{0:f3}" + i.Unit)'>
<HeaderContent>
<abbr title="Quantity">Qty</abbr>
</HeaderContent>

View File

@ -36,34 +36,41 @@
<datalist id="itemNames">
@foreach (var item in items)
{
<option value="@item.Name" data-transaction-item-form-target="option" data-brand="@item.Brand" data-price="@item.Price" data-quantity="@item.Quantity" />
<option value="@item.Name" data-transaction-item-form-target="option" data-brand="@item.Brand" data-price="@item.Price" data-quantity="@(unit == null ? (int?)item.Quantity : item.Quantity)" />
}
</datalist>
</div>
</fieldset>
<div class="form-field">
<label class="form-field__label" for="transactionItemPrice">Price</label>
<label class="form-field__label" for="transactionItemPrice">
Price @if (unit != null) { <text>(per @unit)</text> }
</label>
<div class="form-field__control input">
@*<span class="input__inset">@CultureInfo.CurrentCulture.NumberFormat.CurrencySymbol</span>*@
<input class="input__control" id="transactionItemPrice" name="price" value="@price" type="number" min="0" step="0.01" required data-transaction-item-form-target="price" />
<input class="input__control" id="transactionItemPrice" name="price" value="@price" type="number" min="0" step="0.01" required />
</div>
</div>
<div class="form-field">
<label class="form-field__label" for="transactionItemQuantity">Quantity</label>
<label class="form-field__label" for="transactionItemQuantity">
Quantity @if (unit != null) { <text>(@unit)</text> }
</label>
<div class="form-field__control input">
<input class="input__control" id="transactionItemQuantity" name="quantity" value="@quantity" type="number" min="1" required data-transaction-item-form-target="quantity" />
@{ var step = unit == null ? "1" : "0.001"; }
<input class="input__control" id="transactionItemQuantity" name="quantity" value="@quantity" type="number" min="@step" step="@step" required />
</div>
</div>
<input type="hidden" name="unit" value="@unit" />
</div>
@ChildContent
</form>
@code {
[Parameter]
public TransactionItem? TransactionItem { get; set; }
[Parameter, EditorRequired]
public required TransactionItem TransactionItem { get; set; }
[Parameter]
public RenderFragment? ChildContent { get; set; }
@ -71,7 +78,7 @@
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object>? AdditionalAttributes { get; set; }
private record ItemModel(Guid Id, string Brand, string Name, decimal? Price, int? Quantity);
private record ItemModel(Guid Id, string Brand, string Name, decimal? Price, decimal? Quantity);
private ItemBarcode? barcode;
@ -79,11 +86,12 @@
private ItemModel? selectedItem;
private decimal? price;
private int quantity;
private decimal? quantity;
private string? unit;
protected override async Task OnParametersSetAsync()
{
barcode = TransactionItem?.Item?.Barcodes.FirstOrDefault();
barcode = TransactionItem.Item?.Barcodes.FirstOrDefault();
items = await DbContext.Items
.OrderBy(item => item.Brand)
@ -103,9 +111,15 @@
lastPurchase != null ? lastPurchase.Quantity : null))
.ToArrayAsync();
selectedItem = items.SingleOrDefault(item => item.Id == TransactionItem?.ItemId);
selectedItem = items.SingleOrDefault(item => item.Id == TransactionItem.ItemId);
price = TransactionItem?.Price >= 0 ? TransactionItem.Price : selectedItem?.Price;
quantity = TransactionItem?.Quantity >= 1 ? TransactionItem.Quantity : (selectedItem?.Quantity ?? 1);
price = TransactionItem.Price >= 0 ? TransactionItem.Price : selectedItem?.Price;
quantity = TransactionItem.Quantity >= 0 ? TransactionItem.Quantity : selectedItem?.Quantity;
unit = TransactionItem.Unit;
if (unit == null)
{
quantity ??= 1;
}
}
}

View File

@ -60,17 +60,17 @@ public class TransactionsController : Controller
}
[HttpGet("new/items/new")]
public async Task<IResult> NewTransactionItem(long? barcodeData, string? barcodeFormat)
public async Task<IResult> NewTransactionItem(string? unit, long? barcodeData, string? barcodeFormat)
{
if (TempData.Peek("NewTransaction") is not string json || JsonSerializer.Deserialize<Transaction>(json) is not Transaction transaction)
{
return Results.LocalRedirect("/transactions/new");
}
TransactionItem? transactionItem = null;
Item? item = null;
if (barcodeData != null && barcodeFormat != null)
{
var item = await dbContext.Items
item = await dbContext.Items
.Where(item => item.Barcodes.Any(barcode => barcode.BarcodeData == barcodeData))
.OrderByDescending(item => item.UpdatedAt)
.FirstOrDefaultAsync();
@ -86,11 +86,11 @@ public class TransactionsController : Controller
dbContext.Update(barcode);
await dbContext.SaveChangesAsync();
}
// TODO: Fix `MinValue` hack - view models?
transactionItem = new TransactionItem(item.Id, decimal.MinValue, int.MinValue) { Item = item };
}
// TODO: Fix `MinValue` hack - view models?
var transactionItem = new TransactionItem(item?.Id ?? default, decimal.MinValue, decimal.MinValue, unit) { Item = item };
var parameters = new { Transaction = transaction, TransactionItem = transactionItem };
return Request.IsTurboFrameRequest("modal")
? new RazorComponentResult<NewTransactionItemModal>(parameters)
@ -98,7 +98,7 @@ public class TransactionsController : Controller
}
[HttpPost("new/items/new")]
public async Task<IResult> NewTransactionItem(string brand, string name, decimal price, int quantity, long? barcodeData, string? barcodeFormat)
public async Task<IResult> NewTransactionItem(string brand, string name, decimal price, decimal quantity, string? unit, long? barcodeData, string? barcodeFormat)
{
if (TempData.Peek("NewTransaction") is not string json || JsonSerializer.Deserialize<Transaction>(json) is not Transaction transaction)
{
@ -127,7 +127,7 @@ public class TransactionsController : Controller
// TODO: Handle item already in transaction - merge, replace, error?
var transactionItem = new TransactionItem(item.Id, price, quantity) { Item = item };
var transactionItem = new TransactionItem(item.Id, price, quantity, unit) { Item = item };
transaction.Items.Add(transactionItem);
TempData["NewTransaction"] = JsonSerializer.Serialize(transaction);

View File

@ -51,7 +51,7 @@
CreatedAt = transaction.CreatedAt,
Store = string.Concat(transaction.Store!.Retailer!.Name, " ", transaction.Store.Name),
TotalAmount = transactionTotal.Total,
TotalItems = transaction.Items.Sum(item => item.Quantity),
TotalItems = transaction.Items.Sum(item => item.Unit == null ? (int)item.Quantity : 1),
})
.OrderByDescending(transaction => transaction.CreatedAt);
}