From 560c162ee1351eb9a58bfb53da33d60ebf645e9b Mon Sep 17 00:00:00 2001 From: James Chapman Date: Fri, 28 Jul 2023 20:23:31 +0100 Subject: [PATCH] Default to last purchased price and quantity when adding transaction item --- Groceries.Data/Items/ItemPurchase.cs | 1 + .../20230728185157_update_item_purchases.sql | 14 ++++ Groceries/Items/ItemsController.cs | 10 +-- .../Transactions/_TransactionItemForm.cshtml | 77 +++++++++++-------- .../wwwroot/js/controllers/list-filter.js | 17 ---- .../js/controllers/transaction-item-form.js | 39 ++++++++++ Groceries/wwwroot/js/main.js | 4 +- 7 files changed, 104 insertions(+), 58 deletions(-) create mode 100644 Groceries.Data/Migrations/20230728185157_update_item_purchases.sql delete mode 100644 Groceries/wwwroot/js/controllers/list-filter.js create mode 100644 Groceries/wwwroot/js/controllers/transaction-item-form.js diff --git a/Groceries.Data/Items/ItemPurchase.cs b/Groceries.Data/Items/ItemPurchase.cs index de44b8f..884496e 100644 --- a/Groceries.Data/Items/ItemPurchase.cs +++ b/Groceries.Data/Items/ItemPurchase.cs @@ -8,6 +8,7 @@ public class ItemPurchase public Guid StoreId { get; init; } public decimal Price { get; init; } public int Quantity { get; init; } + public bool IsLastPurchase { get; init; } public Item? Item { get; init; } public Transaction? Transaction { get; init; } diff --git a/Groceries.Data/Migrations/20230728185157_update_item_purchases.sql b/Groceries.Data/Migrations/20230728185157_update_item_purchases.sql new file mode 100644 index 0000000..5dc500d --- /dev/null +++ b/Groceries.Data/Migrations/20230728185157_update_item_purchases.sql @@ -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); diff --git a/Groceries/Items/ItemsController.cs b/Groceries/Items/ItemsController.cs index 2252690..cecc20e 100644 --- a/Groceries/Items/ItemsController.cs +++ b/Groceries/Items/ItemsController.cs @@ -25,19 +25,11 @@ public class ItemsController : Controller 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 .OrderBy(item => item.Brand) .ThenBy(item => item.Name) .GroupJoin( - lastPurchasesQuery, + dbContext.ItemPurchases.Where(purchase => purchase.IsLastPurchase), item => item.Id, lastPurchase => lastPurchase.ItemId, (item, lastPurchase) => new { item, lastPurchase }) diff --git a/Groceries/Transactions/_TransactionItemForm.cshtml b/Groceries/Transactions/_TransactionItemForm.cshtml index 1c6ad6c..7e20f10 100644 --- a/Groceries/Transactions/_TransactionItemForm.cshtml +++ b/Groceries/Transactions/_TransactionItemForm.cshtml @@ -7,44 +7,61 @@ var items = await dbContext.Items .OrderBy(item => item.Brand) .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(); var selectedItem = items.SingleOrDefault(item => item.Id == Model?.ItemId); } -
- Item -
- - +
+
+ Item +
+ + - - @foreach (var item in items.DistinctBy(item => item.Brand)) - { - + + @foreach (var item in items.DistinctBy(item => item.Brand)) + { + - - @foreach (var item in items) - { - + + @foreach (var item in items) + { + +
+
+ +
+ +
+ @*@CultureInfo.CurrentCulture.NumberFormat.CurrencySymbol*@ + +
-
-
- -
- @*@CultureInfo.CurrentCulture.NumberFormat.CurrencySymbol*@ - -
-
- -
- -
- +
+ +
+ +
diff --git a/Groceries/wwwroot/js/controllers/list-filter.js b/Groceries/wwwroot/js/controllers/list-filter.js deleted file mode 100644 index 3c6a2a0..0000000 --- a/Groceries/wwwroot/js/controllers/list-filter.js +++ /dev/null @@ -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; - } - } -} diff --git a/Groceries/wwwroot/js/controllers/transaction-item-form.js b/Groceries/wwwroot/js/controllers/transaction-item-form.js new file mode 100644 index 0000000..45e5a94 --- /dev/null +++ b/Groceries/wwwroot/js/controllers/transaction-item-form.js @@ -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"); + } + } + } +} diff --git a/Groceries/wwwroot/js/main.js b/Groceries/wwwroot/js/main.js index cfd8708..3126f46 100644 --- a/Groceries/wwwroot/js/main.js +++ b/Groceries/wwwroot/js/main.js @@ -1,13 +1,13 @@ import { Application } from "/lib/hotwired/stimulus/dist/stimulus.js"; -import ListFilterController from "./controllers/list-filter.js"; import ModalController from "./controllers/modal.js"; import SearchFormController from "./controllers/search-form.js"; +import TransactionItemFormController from "./controllers/transaction-item-form.js"; const app = Application.start(); -app.register("list-filter", ListFilterController); app.register("modal", ModalController); app.register("search-form", SearchFormController); +app.register("transaction-item-form", TransactionItemFormController); let timeout; document.addEventListener("turbo:visit", () => {