Add ability to add transaction items using barcode scanner

This commit is contained in:
2023-11-05 23:10:54 +00:00
parent eae1833e2b
commit 929eddd9e8
9 changed files with 145 additions and 38 deletions

View File

@ -1,5 +1,5 @@
@using Groceries.Data;
@using Microsoft.EntityFrameworkCore;
@using Groceries.Data
@using Microsoft.EntityFrameworkCore
@model (Transaction Transaction, TransactionItem TransactionItem)
@inject AppDbContext dbContext

View File

@ -1,23 +1,25 @@
@using Groceries.Data;
@using Microsoft.EntityFrameworkCore;
@using Groceries.Data
@using Microsoft.EntityFrameworkCore
@model Transaction
@model (Transaction Transaction, TransactionItem? TransactionItem)
@inject AppDbContext dbContext
@{
ViewBag.Title = "New Transaction Item";
var store = await dbContext.Stores
.Where(store => store.Id == Model.StoreId)
.Where(store => store.Id == Model.Transaction.StoreId)
.Select(store => string.Concat(store.Retailer!.Name, " ", store.Name))
.SingleAsync();
}
<h1>New Transaction Item</h1>
<div class="form-field">@Model.CreatedAt.ToShortDateString() @Model.CreatedAt.ToShortTimeString() &ndash; @store</div>
<div class="form-field">
@Model.Transaction.CreatedAt.ToShortDateString() @Model.Transaction.CreatedAt.ToShortTimeString() &ndash; @store
</div>
<form method="post" asp-action="NewTransactionItem">
<partial name="_TransactionItemForm" model="null" />
<partial name="_TransactionItemForm" model="Model.TransactionItem" />
<div class="row">
<button class="button button--primary" type="submit">Add</button>

View File

@ -1,13 +1,13 @@
@using Groceries.Data
@model Transaction
@model (Transaction Transaction, TransactionItem? TransactionItem)
@{
Layout = "_Modal";
ViewBag.Title = "New Transaction Item";
}
<form class="card__content" id="newTransactionItem" method="post" asp-action="NewTransactionItem" data-action="turbo:submit-end->modal#close">
<partial name="_TransactionItemForm" model="null" />
<partial name="_TransactionItemForm" model="Model.TransactionItem" />
</form>
<footer class="card__footer card__footer--shaded row">

View File

@ -99,20 +99,35 @@ public class TransactionsController : Controller
}
[HttpGet("new/items/new")]
public IActionResult NewTransactionItem()
public async Task<IActionResult> NewTransactionItem(long? barcodeData, string? barcodeFormat)
{
if (TempData.Peek("NewTransaction") is not string json || JsonSerializer.Deserialize<Transaction>(json) is not Transaction transaction)
{
return RedirectToAction(nameof(NewTransaction));
}
TransactionItem? transactionItem = null;
if (barcodeData != null && barcodeFormat != null)
{
var item = await dbContext.Items
.Where(item => item.Barcodes.Any(barcode => barcode.BarcodeData == barcodeData))
.FirstOrDefaultAsync();
item ??= new Item(id: default);
item.Barcodes.Add(new ItemBarcode(item.Id, barcodeData.Value, barcodeFormat));
// TODO: Fix `MinValue` hack - view models?
transactionItem = new TransactionItem(item.Id, decimal.MinValue, int.MinValue) { Item = item };
}
var model = (transaction, transactionItem);
return Request.IsTurboFrameRequest("modal")
? View($"{nameof(NewTransactionItem)}_Modal", transaction)
: View(transaction);
? View($"{nameof(NewTransactionItem)}_Modal", model)
: View(model);
}
[HttpPost("new/items/new")]
public async Task<IActionResult> NewTransactionItem(string brand, string name, decimal price, int quantity)
public async Task<IActionResult> NewTransactionItem(string brand, string name, decimal price, int quantity, long? barcodeData, string? barcodeFormat)
{
if (TempData.Peek("NewTransaction") is not string json || JsonSerializer.Deserialize<Transaction>(json) is not Transaction transaction)
{
@ -124,17 +139,18 @@ public class TransactionsController : Controller
.Select(item => item.Id)
.SingleOrDefaultAsync();
if (itemId == default)
var item = new Item(itemId, brand, name);
if (barcodeData != null && barcodeFormat != null)
{
var item = new Item(brand, name);
dbContext.Items.Add(item);
await dbContext.SaveChangesAsync();
itemId = item.Id;
item.Barcodes.Add(new ItemBarcode(itemId, barcodeData.Value, barcodeFormat));
}
dbContext.Items.Attach(item);
await dbContext.SaveChangesAsync();
// TODO: Handle item already in transaction - merge, replace, error?
var transactionItem = new TransactionItem(itemId, price, quantity);
var transactionItem = new TransactionItem(item.Id, price, quantity) { Item = item };
transaction.Items.Add(transactionItem);
TempData["NewTransaction"] = JsonSerializer.Serialize(transaction);
@ -183,18 +199,18 @@ public class TransactionsController : Controller
.Select(item => item.Id)
.SingleOrDefaultAsync();
if (itemId == default)
{
var item = new Item(brand, name);
dbContext.Items.Add(item);
await dbContext.SaveChangesAsync();
itemId = item.Id;
}
var item = new Item(itemId, brand, name);
transactionItem.ItemId = itemId;
dbContext.Items.Attach(item);
await dbContext.SaveChangesAsync();
transactionItem.Item = item;
transactionItem.ItemId = item.Id;
transactionItem.Price = price;
transactionItem.Quantity = quantity;
// TODO: Handle barcode when editing item - replace, disable?
TempData["NewTransaction"] = JsonSerializer.Serialize(transaction);
return Request.IsTurboFrameRequest("modal")
@ -243,7 +259,10 @@ public class TransactionsController : Controller
}
// Work around EF trying to insert items by explicitly tracking them as unchanged
dbContext.Items.AttachRange(transaction.Promotions.SelectMany(promotion => promotion.Items));
dbContext.Items.AttachRange(
transaction.Items
.Select(item => item.Item!)
.Concat(transaction.Promotions.SelectMany(promotion => promotion.Items)));
dbContext.Transactions.Add(transaction);
await dbContext.SaveChangesAsync();

View File

@ -25,13 +25,30 @@
.ToArrayAsync();
var selectedItem = items.SingleOrDefault(item => item.Id == Model?.ItemId);
var barcode = Model?.Item?.Barcodes.FirstOrDefault();
var price = Model?.Price >= 0 ? Model.Price : selectedItem?.Price;
var quantity = Model?.Quantity >= 1 ? Model.Quantity : (selectedItem?.Quantity ?? 1);
}
<div data-controller="transaction-item-form">
<div class="form-field" data-transaction-item-form-target="barcodeFormField" hidden>
<label class="form-field__label" for="transactionItemBarcode">Barcode</label>
<div class="form-field__control input">
<input type="hidden" name="barcodeFormat" value="@barcode?.Format" data-transaction-item-form-target="barcodeFormat" />
<input class="input__control" id="transactionItemBarcode" name="barcodeData" value="@barcode?.BarcodeData" data-transaction-item-form-target="barcodeData" />
<button class="input__addon button" formmethod="get" formnovalidate data-action="transaction-item-form#scanBarcode" data-transaction-item-form-target="barcodeButton">
@* Barcode scanner icon *@
<svg class="icon icon--sm" xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M40-120v-200h80v120h120v80H40Zm680 0v-80h120v-120h80v200H720ZM160-240v-480h80v480h-80Zm120 0v-480h40v480h-40Zm120 0v-480h80v480h-80Zm120 0v-480h120v480H520Zm160 0v-480h40v480h-40Zm80 0v-480h40v480h-40ZM40-640v-200h200v80H120v120H40Zm800 0v-120H720v-80h200v200h-80Z" /></svg>
</button>
</div>
</div>
<fieldset class="form-field">
<legend class="form-field__label">Item</legend>
<div class="form-field__control input">
<input class="input__control flex-2" name="brand" value="@selectedItem?.Brand" placeholder="Brand" list="itemBrands" autocomplete="off" required autofocus data-action="transaction-item-form#filterNames transaction-item-form#setPriceAndQuantity" />
<input class="input__control flex-2" name="brand" value="@selectedItem?.Brand" placeholder="Brand" list="itemBrands" autocomplete="off" required autofocus data-action="transaction-item-form#filterNames transaction-item-form#setPriceAndQuantity" data-transaction-item-form-target="brand" />
<input class="input__control flex-5" name="name" value="@selectedItem?.Name" placeholder="Name" list="itemNames" autocomplete="off" required data-action="transaction-item-form#setPriceAndQuantity" />
<datalist id="itemBrands">
@ -54,14 +71,14 @@
<label class="form-field__label" for="transactionItemPrice">Price</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="@Model?.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 data-transaction-item-form-target="price" />
</div>
</div>
<div class="form-field">
<label class="form-field__label" for="transactionItemQuantity">Quantity</label>
<div class="form-field__control input">
<input class="input__control" id="transactionItemQuantity" name="quantity" value="@(Model?.Quantity ?? 1)" type="number" min="1" required data-transaction-item-form-target="quantity" />
<input class="input__control" id="transactionItemQuantity" name="quantity" value="@quantity" type="number" min="1" required data-transaction-item-form-target="quantity" />
</div>
</div>
</div>