Default to last purchased price and quantity when adding transaction item
This commit is contained in:
parent
4fc1052829
commit
560c162ee1
@ -8,6 +8,7 @@ public class ItemPurchase
|
|||||||
public Guid StoreId { get; init; }
|
public Guid StoreId { get; init; }
|
||||||
public decimal Price { get; init; }
|
public decimal Price { get; init; }
|
||||||
public int Quantity { get; init; }
|
public int Quantity { get; init; }
|
||||||
|
public bool IsLastPurchase { get; init; }
|
||||||
|
|
||||||
public Item? Item { get; init; }
|
public Item? Item { get; init; }
|
||||||
public Transaction? Transaction { get; init; }
|
public Transaction? Transaction { get; init; }
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
CREATE OR REPLACE VIEW item_purchases AS
|
||||||
|
SELECT
|
||||||
|
item_id,
|
||||||
|
transaction_id,
|
||||||
|
created_at,
|
||||||
|
store_id,
|
||||||
|
price,
|
||||||
|
quantity,
|
||||||
|
CASE ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY created_at DESC)
|
||||||
|
WHEN 1 THEN true
|
||||||
|
ELSE false
|
||||||
|
END AS is_last_purchase
|
||||||
|
FROM transaction_items
|
||||||
|
JOIN transactions USING (transaction_id);
|
@ -25,19 +25,11 @@ public class ItemsController : Controller
|
|||||||
itemsQuery = itemsQuery.Where(item => EF.Functions.ILike(item.Brand + ' ' + item.Name, searchPattern));
|
itemsQuery = itemsQuery.Where(item => EF.Functions.ILike(item.Brand + ' ' + item.Name, searchPattern));
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastPurchasesQuery = dbContext.ItemPurchases
|
|
||||||
.GroupBy(purchase => purchase.ItemId)
|
|
||||||
.Select(purchases => new
|
|
||||||
{
|
|
||||||
ItemId = purchases.Key,
|
|
||||||
CreatedAt = purchases.Max(purchase => purchase.CreatedAt),
|
|
||||||
});
|
|
||||||
|
|
||||||
var items = await itemsQuery
|
var items = await itemsQuery
|
||||||
.OrderBy(item => item.Brand)
|
.OrderBy(item => item.Brand)
|
||||||
.ThenBy(item => item.Name)
|
.ThenBy(item => item.Name)
|
||||||
.GroupJoin(
|
.GroupJoin(
|
||||||
lastPurchasesQuery,
|
dbContext.ItemPurchases.Where(purchase => purchase.IsLastPurchase),
|
||||||
item => item.Id,
|
item => item.Id,
|
||||||
lastPurchase => lastPurchase.ItemId,
|
lastPurchase => lastPurchase.ItemId,
|
||||||
(item, lastPurchase) => new { item, lastPurchase })
|
(item, lastPurchase) => new { item, lastPurchase })
|
||||||
|
@ -7,44 +7,61 @@
|
|||||||
var items = await dbContext.Items
|
var items = await dbContext.Items
|
||||||
.OrderBy(item => item.Brand)
|
.OrderBy(item => item.Brand)
|
||||||
.ThenBy(item => item.Name)
|
.ThenBy(item => item.Name)
|
||||||
|
.GroupJoin(
|
||||||
|
dbContext.ItemPurchases.Where(purchase => purchase.IsLastPurchase),
|
||||||
|
item => item.Id,
|
||||||
|
lastPurchase => lastPurchase.ItemId,
|
||||||
|
(item, lastPurchase) => new { item, lastPurchase })
|
||||||
|
.SelectMany(
|
||||||
|
group => group.lastPurchase.DefaultIfEmpty(),
|
||||||
|
(group, lastPurchase) => new
|
||||||
|
{
|
||||||
|
group.item.Id,
|
||||||
|
group.item.Brand,
|
||||||
|
group.item.Name,
|
||||||
|
Price = lastPurchase != null ? lastPurchase.Price : (decimal?)null,
|
||||||
|
Quantity = lastPurchase != null ? lastPurchase.Quantity : (int?)null,
|
||||||
|
})
|
||||||
.ToArrayAsync();
|
.ToArrayAsync();
|
||||||
|
|
||||||
var selectedItem = items.SingleOrDefault(item => item.Id == Model?.ItemId);
|
var selectedItem = items.SingleOrDefault(item => item.Id == Model?.ItemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
<fieldset class="form-field">
|
<div data-controller="transaction-item-form">
|
||||||
<legend class="form-field__label">Item</legend>
|
<fieldset class="form-field">
|
||||||
<div class="form-field__control input" data-controller="list-filter">
|
<legend class="form-field__label">Item</legend>
|
||||||
<input class="input__control flex-2" name="brand" value="@selectedItem?.Brand" placeholder="Brand" list="itemBrands" autocomplete="off" required autofocus data-action="list-filter#filter" />
|
<div class="form-field__control input">
|
||||||
<input class="input__control flex-5" name="name" value="@selectedItem?.Name" placeholder="Name" list="itemNames" autocomplete="off" required />
|
<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-5" name="name" value="@selectedItem?.Name" placeholder="Name" list="itemNames" autocomplete="off" required data-action="transaction-item-form#setPriceAndQuantity" />
|
||||||
|
|
||||||
<datalist id="itemBrands">
|
<datalist id="itemBrands">
|
||||||
@foreach (var item in items.DistinctBy(item => item.Brand))
|
@foreach (var item in items.DistinctBy(item => item.Brand))
|
||||||
{
|
{
|
||||||
<option value="@item.Brand" />
|
<option value="@item.Brand" />
|
||||||
}
|
}
|
||||||
</datalist>
|
</datalist>
|
||||||
|
|
||||||
<datalist id="itemNames">
|
<datalist id="itemNames">
|
||||||
@foreach (var item in items)
|
@foreach (var item in items)
|
||||||
{
|
{
|
||||||
<option value="@item.Name" data-list-filter-target="option" data-list-filter-value="@item.Brand" />
|
<option value="@item.Name" data-transaction-item-form-target="option" data-brand="@item.Brand" data-price="@item.Price" data-quantity="@item.Quantity" />
|
||||||
}
|
}
|
||||||
</datalist>
|
</datalist>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<div class="form-field">
|
||||||
|
<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" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div class="form-field">
|
<div class="form-field">
|
||||||
<label class="form-field__label" for="transactionItemPrice">Price</label>
|
<label class="form-field__label" for="transactionItemQuantity">Quantity</label>
|
||||||
<div class="form-field__control input">
|
<div class="form-field__control input">
|
||||||
@*<span class="input__inset">@CultureInfo.CurrentCulture.NumberFormat.CurrencySymbol</span>*@
|
<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="transactionItemPrice" name="price" value="@Model?.Price" type="number" min="0" step="0.01" required />
|
</div>
|
||||||
</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 />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
import { Controller } from "/lib/hotwired/stimulus/dist/stimulus.js";
|
|
||||||
|
|
||||||
export default class ListFilterController extends Controller {
|
|
||||||
static targets = ["option"];
|
|
||||||
|
|
||||||
filter(event) {
|
|
||||||
for (const option of this.optionTargets) {
|
|
||||||
if (!event.target.value) {
|
|
||||||
option.disabled = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = option.getAttribute("data-list-filter-value");
|
|
||||||
option.disabled = value !== event.target.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
39
Groceries/wwwroot/js/controllers/transaction-item-form.js
Normal file
39
Groceries/wwwroot/js/controllers/transaction-item-form.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { Controller } from "/lib/hotwired/stimulus/dist/stimulus.js";
|
||||||
|
|
||||||
|
export default class TransactionItemFormController extends Controller {
|
||||||
|
static targets = ["option", "price", "quantity"];
|
||||||
|
|
||||||
|
filterNames(event) {
|
||||||
|
for (const option of this.optionTargets) {
|
||||||
|
if (!event.target.value) {
|
||||||
|
option.disabled = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = option.getAttribute("data-brand");
|
||||||
|
option.disabled = value !== event.target.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setPriceAndQuantity(event) {
|
||||||
|
const { brand, name } = event.target.form.elements;
|
||||||
|
if (!brand.value || !name.value) {
|
||||||
|
this.priceTarget.value = "";
|
||||||
|
this.quantityTarget.value = "1";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const option = this.optionTargets.find(option =>
|
||||||
|
option.getAttribute("data-brand") === brand.value &&
|
||||||
|
option.value === name.value);
|
||||||
|
|
||||||
|
if (option != null) {
|
||||||
|
if (!this.priceTarget.value) {
|
||||||
|
this.priceTarget.value = option.getAttribute("data-price");
|
||||||
|
}
|
||||||
|
if (!this.quantityTarget.value || this.quantityTarget.value === "1") {
|
||||||
|
this.quantityTarget.value = option.getAttribute("data-quantity");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,13 @@
|
|||||||
import { Application } from "/lib/hotwired/stimulus/dist/stimulus.js";
|
import { Application } from "/lib/hotwired/stimulus/dist/stimulus.js";
|
||||||
|
|
||||||
import ListFilterController from "./controllers/list-filter.js";
|
|
||||||
import ModalController from "./controllers/modal.js";
|
import ModalController from "./controllers/modal.js";
|
||||||
import SearchFormController from "./controllers/search-form.js";
|
import SearchFormController from "./controllers/search-form.js";
|
||||||
|
import TransactionItemFormController from "./controllers/transaction-item-form.js";
|
||||||
|
|
||||||
const app = Application.start();
|
const app = Application.start();
|
||||||
app.register("list-filter", ListFilterController);
|
|
||||||
app.register("modal", ModalController);
|
app.register("modal", ModalController);
|
||||||
app.register("search-form", SearchFormController);
|
app.register("search-form", SearchFormController);
|
||||||
|
app.register("transaction-item-form", TransactionItemFormController);
|
||||||
|
|
||||||
let timeout;
|
let timeout;
|
||||||
document.addEventListener("turbo:visit", () => {
|
document.addEventListener("turbo:visit", () => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user