From bc4af89c95ed54a1d32c87859087049572267555 Mon Sep 17 00:00:00 2001 From: Kevin Tran <112021023+kevbang@users.noreply.github.com> Date: Tue, 10 Mar 2026 01:03:13 -0500 Subject: [PATCH] Revert "feat: add recipe entities + refactor codebase" --- .../Pages/Assignments/AdminAssignTask.razor | 12 +- .../Components/Pages/EmployeeView.razor | 64 - .../Components/Pages/Recipes/Create.razor | 31 + .../Components/Pages/Recipes/Edit.razor | 33 + .../Components/Pages/Recipes/Index.razor | 5 + .../Components/Pages/Recipes/RecipeForm.razor | 234 ++++ .../Components/Pages/Recipes/RecipeList.razor | 167 +++ .../Components/Pages/Recipes/RecipeView.razor | 133 ++ .../Pages/Recipes/RecipeView.razor.css | 10 + .../LocationSettings/ConfigureLocation.razor | 152 --- CulinaryCommandApp/Data/AppDbContext.cs | 118 +- .../Data/Entities/Ingredient.cs | 26 + CulinaryCommandApp/Data/Entities/Location.cs | 8 +- .../Data/Entities/MeasurementUnit.cs | 24 + CulinaryCommandApp/Data/Entities/Recipe.cs | 37 + .../Data/Entities/RecipeIngredient.cs | 37 + .../Data/Entities/RecipeStep.cs | 25 + CulinaryCommandApp/Data/Entities/Tasks.cs | 5 +- .../Data/Models/MeasurementUnitViewModel.cs | 15 + .../Inventory/DTOs/CreateIngredientDTO.cs | 2 +- .../Inventory/DTOs/InventoryCatalogDTO.cs | 2 +- .../Inventory/DTOs/InventoryItemDTO.cs | 3 +- .../Configurations/IngredientConfiguration.cs | 2 +- .../Inventory/Entities/Ingredient.cs | 2 +- .../Inventory/Entities/InventoryBatch.cs | 2 +- .../Entities/InventoryTransaction.cs | 2 +- .../Inventory/Entities/LocationUnit.cs | 19 - CulinaryCommandApp/Inventory/Entities/Unit.cs | 6 +- .../Pages/Inventory/InventoryCatalog.razor | 251 +--- .../Pages/Inventory/InventoryManagement.razor | 8 +- .../Inventory/Services/IngredientService.cs | 17 +- .../Services/Interfaces/IIngredientService.cs | 7 +- .../Interfaces/IInventoryManagementService.cs | 4 +- .../IInventoryTransactionService.cs | 13 +- .../Services/Interfaces/IUnitService.cs | 9 +- .../Services/InventoryManagementService.cs | 18 +- .../Services/InventoryTransactionService.cs | 75 +- .../Inventory/Services/UnitService.cs | 46 +- ...2_RecipeAndLocationUnitSupport.Designer.cs | 1121 ---------------- ...0227192322_RecipeAndLocationUnitSupport.cs | 346 ----- ...0305191150_AddRecipeRowVersion.Designer.cs | 1126 ----------------- .../20260305191150_AddRecipeRowVersion.cs | 31 - .../20260306015138_SeedUnits.Designer.cs | 1126 ----------------- .../Migrations/20260306015138_SeedUnits.cs | 48 - .../Migrations/AppDbContextModelSnapshot.cs | 804 ++++++------ CulinaryCommandApp/Program.cs | 8 +- .../Entities/PurchaseOrderLine.cs | 2 +- .../PurchaseOrder/Pages/Create.razor | 2 +- .../Components/ProduceRecipeDialog.razor | 247 ---- CulinaryCommandApp/Recipe/Entities/Recipe.cs | 45 - .../Recipe/Entities/RecipeIngredient.cs | 30 - .../Recipe/Entities/RecipeStep.cs | 32 - .../Recipe/Entities/RecipeSubRecipe.cs | 12 - .../Recipe/Pages/IngredientLineRow.razor | 117 -- .../Recipe/Pages/RecipeCreate.razor | 83 -- .../Recipe/Pages/RecipeEdit.razor | 103 -- .../Recipe/Pages/RecipeForm.razor | 820 ------------ .../Recipe/Pages/RecipeList.razor | 486 ------- .../Recipe/Pages/RecipeList.razor.css | 391 ------ .../Recipe/Pages/RecipeView.razor | 257 ---- .../Recipe/Pages/RecipeView.razor.css | 321 ----- .../Recipe/Pages/_Imports.razor | 10 - .../Services/Interfaces/IRecipeService.cs | 51 - .../Recipe/Services/RecipeService.cs | 151 --- CulinaryCommandApp/Services/RecipeService.cs | 68 + .../DTOs/CreateIngredientDTOTests.cs | 3 +- .../DTOs/InventoryCatalogDTOTests.cs | 2 +- .../Inventory/DTOs/InventoryItemDTOTests.cs | 2 +- .../Inventory/Entities/IngredientTest.cs | 2 +- .../Entities/InventoryTransactionTest.cs | 2 +- .../Inventory/Entities/UnitTest.cs | 2 +- 71 files changed, 1346 insertions(+), 8129 deletions(-) delete mode 100644 CulinaryCommandApp/Components/Pages/EmployeeView.razor create mode 100644 CulinaryCommandApp/Components/Pages/Recipes/Create.razor create mode 100644 CulinaryCommandApp/Components/Pages/Recipes/Edit.razor create mode 100644 CulinaryCommandApp/Components/Pages/Recipes/Index.razor create mode 100644 CulinaryCommandApp/Components/Pages/Recipes/RecipeForm.razor create mode 100644 CulinaryCommandApp/Components/Pages/Recipes/RecipeList.razor create mode 100644 CulinaryCommandApp/Components/Pages/Recipes/RecipeView.razor create mode 100644 CulinaryCommandApp/Components/Pages/Recipes/RecipeView.razor.css create mode 100644 CulinaryCommandApp/Data/Entities/Ingredient.cs create mode 100644 CulinaryCommandApp/Data/Entities/MeasurementUnit.cs create mode 100644 CulinaryCommandApp/Data/Entities/Recipe.cs create mode 100644 CulinaryCommandApp/Data/Entities/RecipeIngredient.cs create mode 100644 CulinaryCommandApp/Data/Entities/RecipeStep.cs create mode 100644 CulinaryCommandApp/Data/Models/MeasurementUnitViewModel.cs delete mode 100644 CulinaryCommandApp/Inventory/Entities/LocationUnit.cs delete mode 100644 CulinaryCommandApp/Migrations/20260227192322_RecipeAndLocationUnitSupport.Designer.cs delete mode 100644 CulinaryCommandApp/Migrations/20260227192322_RecipeAndLocationUnitSupport.cs delete mode 100644 CulinaryCommandApp/Migrations/20260305191150_AddRecipeRowVersion.Designer.cs delete mode 100644 CulinaryCommandApp/Migrations/20260305191150_AddRecipeRowVersion.cs delete mode 100644 CulinaryCommandApp/Migrations/20260306015138_SeedUnits.Designer.cs delete mode 100644 CulinaryCommandApp/Migrations/20260306015138_SeedUnits.cs delete mode 100644 CulinaryCommandApp/Recipe/Components/ProduceRecipeDialog.razor delete mode 100644 CulinaryCommandApp/Recipe/Entities/Recipe.cs delete mode 100644 CulinaryCommandApp/Recipe/Entities/RecipeIngredient.cs delete mode 100644 CulinaryCommandApp/Recipe/Entities/RecipeStep.cs delete mode 100644 CulinaryCommandApp/Recipe/Entities/RecipeSubRecipe.cs delete mode 100644 CulinaryCommandApp/Recipe/Pages/IngredientLineRow.razor delete mode 100644 CulinaryCommandApp/Recipe/Pages/RecipeCreate.razor delete mode 100644 CulinaryCommandApp/Recipe/Pages/RecipeEdit.razor delete mode 100644 CulinaryCommandApp/Recipe/Pages/RecipeForm.razor delete mode 100644 CulinaryCommandApp/Recipe/Pages/RecipeList.razor delete mode 100644 CulinaryCommandApp/Recipe/Pages/RecipeList.razor.css delete mode 100644 CulinaryCommandApp/Recipe/Pages/RecipeView.razor delete mode 100644 CulinaryCommandApp/Recipe/Pages/RecipeView.razor.css delete mode 100644 CulinaryCommandApp/Recipe/Pages/_Imports.razor delete mode 100644 CulinaryCommandApp/Recipe/Services/Interfaces/IRecipeService.cs delete mode 100644 CulinaryCommandApp/Recipe/Services/RecipeService.cs create mode 100644 CulinaryCommandApp/Services/RecipeService.cs diff --git a/CulinaryCommandApp/Components/Pages/Assignments/AdminAssignTask.razor b/CulinaryCommandApp/Components/Pages/Assignments/AdminAssignTask.razor index 9480631..3b73c89 100644 --- a/CulinaryCommandApp/Components/Pages/Assignments/AdminAssignTask.razor +++ b/CulinaryCommandApp/Components/Pages/Assignments/AdminAssignTask.razor @@ -4,16 +4,13 @@ @using CulinaryCommand.Services @using CulinaryCommand.Data.Enums; @using CulinaryCommand.Services.UserContextSpace -@using CulinaryCommandApp.Recipe.Services; -@using CulinaryCommandApp.Recipe.Services.Interfaces; -@using CulinaryCommandApp.Recipe.Entities; @inject IUserContextService UserCtx @inject NavigationManager Nav @inject ILocationService LocationService @inject IUserService UserService @inject LocationState LocationState @inject ITaskAssignmentService TaskService -@inject IRecipeService RecipeService +@inject RecipeService RecipeService @implements IDisposable @rendermode InteractiveServer @@ -349,8 +346,11 @@ else try { - recipes = await RecipeService.GetAllByLocationIdAsync(selectedLocationId.Value); - recipes = recipes.OrderBy(r => r.Title).ToList(); + var all = await RecipeService.GetAllAsync(); + recipes = all + .Where(r => r.LocationId == selectedLocationId.Value) + .OrderBy(r => r.Title) + .ToList(); } catch { diff --git a/CulinaryCommandApp/Components/Pages/EmployeeView.razor b/CulinaryCommandApp/Components/Pages/EmployeeView.razor deleted file mode 100644 index 82f700d..0000000 --- a/CulinaryCommandApp/Components/Pages/EmployeeView.razor +++ /dev/null @@ -1,64 +0,0 @@ -@rendermode InteractiveServer - -@using CulinaryCommand.Components.Custom -@using CulinaryCommand.Services.UserContextSpace -@using CulinaryCommand.Services - -@inject IUserContextService UserCtx -@inject LocationState LocationState - -
-

Employee Dashboard

- - @if (!_ready) - { -
Loading...
- } - else if (LocationState.CurrentLocation is null) - { -

No location assigned. Please contact your manager.

- } - else - { -

Welcome to @LocationState.CurrentLocation.Name.

- -
-
-
- - -
-
- - -
-
-
- } -
- -@code { - - private bool _ready; - - protected override async Task OnInitializedAsync() - { - _ready = true; - } -} diff --git a/CulinaryCommandApp/Components/Pages/Recipes/Create.razor b/CulinaryCommandApp/Components/Pages/Recipes/Create.razor new file mode 100644 index 0000000..8df7d49 --- /dev/null +++ b/CulinaryCommandApp/Components/Pages/Recipes/Create.razor @@ -0,0 +1,31 @@ +@page "/recipes/create" +@using CulinaryCommand.Data.Entities +@inject NavigationManager Nav +@inject RecipeService Recipes +@inject LocationState LocationState +@rendermode InteractiveServer + +

Create Recipe

+ + + +@code { + private Recipe model = new(); + + private async Task Save() + { + // Make sure we have a current location + if (LocationState.CurrentLocation is null) + { + // You can swap this for a nicer UI message later + Console.WriteLine("No current location selected – cannot save recipe."); + return; + } + + // πŸ”₯ attach recipe to the active location + model.LocationId = LocationState.CurrentLocation.Id; + + await Recipes.CreateAsync(model); + Nav.NavigateTo("/recipes"); + } +} \ No newline at end of file diff --git a/CulinaryCommandApp/Components/Pages/Recipes/Edit.razor b/CulinaryCommandApp/Components/Pages/Recipes/Edit.razor new file mode 100644 index 0000000..95b1205 --- /dev/null +++ b/CulinaryCommandApp/Components/Pages/Recipes/Edit.razor @@ -0,0 +1,33 @@ +@page "/recipes/edit/{id:int}" +@using CulinaryCommand.Data.Entities +@inject NavigationManager Nav +@inject RecipeService Recipes +@rendermode InteractiveServer + +

Edit Recipe

+ +@if (model == null) +{ +

Loading...

+} +else +{ + +} + +@code { + [Parameter] public int id { get; set; } + + private Recipe? model; + + protected override async Task OnInitializedAsync() + { + model = await Recipes.GetByIdAsync(id); + } + + private async Task Save() + { + await Recipes.UpdateAsync(model); + Nav.NavigateTo("/recipes"); + } +} diff --git a/CulinaryCommandApp/Components/Pages/Recipes/Index.razor b/CulinaryCommandApp/Components/Pages/Recipes/Index.razor new file mode 100644 index 0000000..7da5afe --- /dev/null +++ b/CulinaryCommandApp/Components/Pages/Recipes/Index.razor @@ -0,0 +1,5 @@ +@page "/recipes" +@using CulinaryCommand.Data.Entities +@rendermode InteractiveServer + + diff --git a/CulinaryCommandApp/Components/Pages/Recipes/RecipeForm.razor b/CulinaryCommandApp/Components/Pages/Recipes/RecipeForm.razor new file mode 100644 index 0000000..bc08ae7 --- /dev/null +++ b/CulinaryCommandApp/Components/Pages/Recipes/RecipeForm.razor @@ -0,0 +1,234 @@ +@using CulinaryCommand.Data.Entities +@using CulinaryCommand.Inventory.Services +@rendermode InteractiveServer +@inject NavigationManager Nav +@inject IngredientService Ingredients +@inject UnitService Units +@inject EnumService enumService + +
+
+

@FormTitle

+
+ +
+ + +
+
+ + +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + + Enter a value from 0–100% +
+ +
+ + +
+
+ +
+ + +
Ingredients
+ + @foreach (var item in Model.RecipeIngredients.OrderBy(i => i.SortOrder)) + { +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+ } + + + +
+ + +
Steps
+ + @foreach (var step in Model.Steps.OrderBy(s => s.StepNumber)) + { +
+ + + +
+ } + + + +
+ + + + + +
+
+ +@code { + [Parameter] public Recipe Model { get; set; } = new(); + [Parameter] public EventCallback OnValidSubmit { get; set; } + [Parameter] public string FormTitle { get; set; } = "Recipe"; + + public List IngredientCategories { get; set; } = new(); + private List recipeCategories = new (); + private List recipeTypes = new (); + private List yieldUnits = new(); + + protected override async Task OnInitializedAsync() + { + IngredientCategories = enumService.GetCategories(); + recipeCategories = enumService.GetCategories(); + recipeTypes = enumService.GetRecipeTypes(); + yieldUnits = enumService.GetUnits(); + } + + void AddIngredient() + { + Model.RecipeIngredients.Add(new RecipeIngredient + { + SortOrder = Model.RecipeIngredients.Count + 1, + AvailableIngredients = new(), + AvailableUnits = new() + }); + } + + void RemoveIngredient(RecipeIngredient item) + { + Model.RecipeIngredients.Remove(item); + } + + void AddStep() + { + Model.Steps.Add(new RecipeStep + { + StepNumber = Model.Steps.Count + 1 + }); + } + + void RemoveStep(RecipeStep step) + { + Model.Steps.Remove(step); + } + + async Task LoadIngredientsForCategory(RecipeIngredient item, string? category) + { + if (string.IsNullOrWhiteSpace(category)) + return; + + item.AvailableIngredients = await Ingredients.GetByCategoryAsync(category); + } + + async Task LoadUnitsForIngredient(RecipeIngredient item) + { + if (item.IngredientId <= 0) + return; + + item.AvailableUnits = await Units.GetUnitsForIngredient(item.IngredientId); + } + + void Cancel() => Nav.NavigateTo("/recipes"); + + async Task Save() => await OnValidSubmit.InvokeAsync(); +} \ No newline at end of file diff --git a/CulinaryCommandApp/Components/Pages/Recipes/RecipeList.razor b/CulinaryCommandApp/Components/Pages/Recipes/RecipeList.razor new file mode 100644 index 0000000..4705381 --- /dev/null +++ b/CulinaryCommandApp/Components/Pages/Recipes/RecipeList.razor @@ -0,0 +1,167 @@ +@* @page "/recipes" *@ +@rendermode InteractiveServer +@implements IDisposable + +@using CulinaryCommand.Data.Entities +@using CulinaryCommand.Services +@using CulinaryCommand.Services.UserContextSpace + +@inject NavigationManager Nav +@inject IJSRuntime Js +@inject RecipeService Recipes +@inject LocationState LocationState +@inject IUserContextService UserCtx + +

Recipes

+ +@if (priv) +{ + +} + +@if (recipes == null) +{ +

Loading...

+} +else +{ + + + + + + + + + + + + @foreach (var r in recipes) + { + + + + + + + + } + +
TitleCategoryTypeYield
@r.Title@r.Category@r.RecipeType@FormatYield(r) + + @if (priv) + { + + + } +
+} + +@code { + private UserContext? _ctx; + + private List? recipes; + private bool priv = false; + + protected override async Task OnInitializedAsync() + { + _ctx = await UserCtx.GetAsync(); + + // If not authenticated, bounce to Cognito login + if (_ctx.IsAuthenticated != true) + { + Nav.NavigateTo("/login", true); + return; + } + + // Invite-only: authenticated but not in DB + if (_ctx.User is null) + { + Nav.NavigateTo("/no-access", true); + return; + } + + // Ensure LocationState is hydrated if user deep-links here + if (LocationState.ManagedLocations.Count == 0 && _ctx.AccessibleLocations.Any()) + await LocationState.SetLocationsAsync(_ctx.AccessibleLocations); + + UpdatePriv(); + + LocationState.OnChange += RefreshRecipes; + + await LoadRecipesAsync(); + } + + private void UpdatePriv() + { + var role = _ctx?.User?.Role; + priv = + string.Equals(role, "Manager", StringComparison.OrdinalIgnoreCase) || + string.Equals(role, "Admin", StringComparison.OrdinalIgnoreCase); + } + + private async Task LoadRecipesAsync() + { + var loc = LocationState.CurrentLocation; + + if (loc == null) + { + recipes = new List(); + return; + } + + recipes = await Recipes.GetAllByLocationIdAsync(loc.Id); + } + + private void RefreshRecipes() + { + _ = InvokeAsync(async () => + { + await LoadRecipesAsync(); + StateHasChanged(); + }); + } + + void AddNew() => Nav.NavigateTo("/recipes/create"); + void Edit(int id) => Nav.NavigateTo($"/recipes/edit/{id}"); + void View(int id) => Nav.NavigateTo($"/recipes/view/{id}"); + + async Task Delete(int id) + { + if (await Js.InvokeAsync("confirm", $"Delete recipe #{id}?")) + { + await Recipes.DeleteAsync(id); + await LoadRecipesAsync(); + await InvokeAsync(StateHasChanged); + } + } + + private string FormatYield(Recipe r) + { + if (!r.YieldAmount.HasValue) + return string.IsNullOrWhiteSpace(r.YieldUnit) ? string.Empty : r.YieldUnit!; + + var amount = r.YieldAmount.Value; + + var amountText = decimal.Truncate(amount) == amount + ? amount.ToString("0") + : amount.ToString("0.##"); + + return string.IsNullOrWhiteSpace(r.YieldUnit) + ? amountText + : $"{amountText} {r.YieldUnit}"; + } + + public void Dispose() + { + LocationState.OnChange -= RefreshRecipes; + } +} diff --git a/CulinaryCommandApp/Components/Pages/Recipes/RecipeView.razor b/CulinaryCommandApp/Components/Pages/Recipes/RecipeView.razor new file mode 100644 index 0000000..ba7b904 --- /dev/null +++ b/CulinaryCommandApp/Components/Pages/Recipes/RecipeView.razor @@ -0,0 +1,133 @@ +@page "/recipes/view/{id:int}" +@using CulinaryCommand.Data.Entities +@inject RecipeService Recipes +@inject NavigationManager Nav + +
+ +
+

@recipe?.Title

+ +
+ + @if (recipe == null) + { +

Loading...

+ } + else + { +
+
+ + +
+
+
+
Category
+
@recipe.Category
+
+
+ +
+
+
Type
+
@recipe.RecipeType
+
+
+ +
+
+
Yield
+
@FormatYield(recipe)
+
+
+
+ +
+ + +

+ Ingredients +

+ + @if (recipe.RecipeIngredients.Count == 0) + { +

No ingredients added.

+ } + else + { +
    + @foreach (var ing in recipe.RecipeIngredients.OrderBy(i => i.SortOrder)) + { +
  • + @FormatQuantity(ing.Quantity) + @(" " + ing.Unit?.Name) + @(" β€” ") + @ing.Ingredient?.Name + + @if (!string.IsNullOrWhiteSpace(ing.PrepNote)) + { +
    @ing.PrepNote
    + } +
  • + } +
+ } + + +

+ Steps +

+ + @if (recipe.Steps.Count == 0) + { +

No steps added.

+ } + else + { +
    + @foreach (var step in recipe.Steps.OrderBy(s => s.StepNumber)) + { +
  1. +
    @step.Instructions
    +
  2. + } +
+ } + +
+
+ } +
+ +@code { + [Parameter] public int id { get; set; } + private Recipe? recipe; + + protected override async Task OnInitializedAsync() + { + recipe = await Recipes.GetByIdAsync(id); + } + + void GoBack() => Nav.NavigateTo("/recipes"); + + string FormatQuantity(decimal qty) + { + // remove trailing zeros + return qty.ToString("0.##"); + } + + string FormatYield(Recipe r) + { + if (!r.YieldAmount.HasValue) + return r.YieldUnit ?? ""; + + string amt = r.YieldAmount.Value.ToString("0.##"); + + return string.IsNullOrWhiteSpace(r.YieldUnit) + ? amt + : $"{amt} {r.YieldUnit}"; + } +} diff --git a/CulinaryCommandApp/Components/Pages/Recipes/RecipeView.razor.css b/CulinaryCommandApp/Components/Pages/Recipes/RecipeView.razor.css new file mode 100644 index 0000000..4669abd --- /dev/null +++ b/CulinaryCommandApp/Components/Pages/Recipes/RecipeView.razor.css @@ -0,0 +1,10 @@ +.detail-box { + background: #f8f9fa; + border: 1px solid #e3e3e3; +} + +.list-group-numbered > .list-group-item { + padding: 1rem; + border-radius: 6px; + margin-bottom: 6px; +} diff --git a/CulinaryCommandApp/Components/Pages/UserSettings/LocationSettings/ConfigureLocation.razor b/CulinaryCommandApp/Components/Pages/UserSettings/LocationSettings/ConfigureLocation.razor index 7cea837..dd09708 100644 --- a/CulinaryCommandApp/Components/Pages/UserSettings/LocationSettings/ConfigureLocation.razor +++ b/CulinaryCommandApp/Components/Pages/UserSettings/LocationSettings/ConfigureLocation.razor @@ -5,9 +5,6 @@ @using CulinaryCommand.Vendor.Services @using CulinaryCommand.Services @using CulinaryCommand.Services.UserContextSpace -@using CulinaryCommandApp.Inventory.Entities -@using CulinaryCommandApp.Inventory.Services -@using CulinaryCommandApp.Inventory.Services.Interfaces @using Microsoft.AspNetCore.Authorization @using System.Net.Http.Json @using System.Text.Json @@ -15,7 +12,6 @@ @inject IVendorService VendorService @inject ILocationService LocationService -@inject IUnitService UnitService @inject IUserContextService UserCtx @inject NavigationManager Nav @inject IConfiguration Config @@ -136,61 +132,6 @@ } - - -
-
-
Measurement Units
- -
- -
- @if (!_locationUnits.Any()) - { -
- - No units assigned to this location yet. Add one to get started. -
- } - else - { - - - - - - - - - - @foreach (var unit in _locationUnits) - { - - - - - - } - -
NameAbbreviation
@unit.Name@unit.Abbreviation - -
- } -
- - @if (_locationUnits.Any()) - { - - } -
} @@ -320,66 +261,15 @@ } - -@if (_showAddUnitModal) -{ - -} - @code { [Parameter] public int LocationId { get; set; } private Location? _location; private List _locationVendors = new(); - private List _locationUnits = new(); - private List _availableUnitsToAdd = new(); private bool _hydrated; private bool _showAddVendorModal; private bool _creatingVendor; - private bool _showAddUnitModal; - private bool _addingUnit; - private int _selectedUnitId; private Vendor _newVendor = new(); private int? _companyId; @@ -423,7 +313,6 @@ if (_location is not null && _companyId.HasValue) { _locationVendors = await VendorService.GetVendorsByLocationAsync(LocationId); - _locationUnits = await UnitService.GetByLocationAsync(LocationId); } _hydrated = true; @@ -556,45 +445,4 @@ _showAddVendorModal = false; StateHasChanged(); } - - // ── Units ───────────────────────────────────────────────────────────────── - - private async Task OpenAddUnitModal() - { - var all = await UnitService.GetAllAsync(); - var assignedIds = _locationUnits.Select(u => u.Id).ToHashSet(); - _availableUnitsToAdd = all.Where(u => !assignedIds.Contains(u.Id)).ToList(); - _selectedUnitId = 0; - _showAddUnitModal = true; - } - - private void CloseAddUnitModal() - { - _showAddUnitModal = false; - _selectedUnitId = 0; - } - - private async Task AddUnit() - { - if (_selectedUnitId == 0) return; - - _addingUnit = true; - - var currentIds = _locationUnits.Select(u => u.Id).ToList(); - currentIds.Add(_selectedUnitId); - await UnitService.SetLocationUnitsAsync(LocationId, currentIds); - _locationUnits = await UnitService.GetByLocationAsync(LocationId); - - _addingUnit = false; - _showAddUnitModal = false; - StateHasChanged(); - } - - private async Task RemoveUnit(int unitId) - { - var remaining = _locationUnits.Select(u => u.Id).Where(id => id != unitId); - await UnitService.SetLocationUnitsAsync(LocationId, remaining); - _locationUnits = await UnitService.GetByLocationAsync(LocationId); - StateHasChanged(); - } } diff --git a/CulinaryCommandApp/Data/AppDbContext.cs b/CulinaryCommandApp/Data/AppDbContext.cs index 3ddb33e..c37eb03 100644 --- a/CulinaryCommandApp/Data/AppDbContext.cs +++ b/CulinaryCommandApp/Data/AppDbContext.cs @@ -1,11 +1,9 @@ using Microsoft.EntityFrameworkCore; using CulinaryCommand.Data.Entities; -using CulinaryCommandApp.Inventory.Entities; -using CulinaryCommandApp.Recipe.Entities; +using CulinaryCommand.Inventory.Entities; using PO = CulinaryCommand.PurchaseOrder.Entities; using V = CulinaryCommand.Vendor.Entities; - namespace CulinaryCommand.Data { public class AppDbContext : DbContext @@ -19,20 +17,20 @@ public AppDbContext(DbContextOptions options) public DbSet Users => Set(); public DbSet Tasks => Set(); public DbSet Companies => Set(); - public DbSet Ingredients => Set(); - public DbSet Recipes => Set(); - public DbSet RecipeIngredients => Set(); - public DbSet RecipeSteps => Set(); - public DbSet RecipeSubRecipes => Set(); + public DbSet Ingredients => Set(); + public DbSet MeasurementUnits => Set(); + public DbSet Recipes => Set(); + public DbSet RecipeIngredients => Set(); + public DbSet RecipeSteps => Set(); public DbSet UserLocations => Set(); public DbSet ManagerLocations => Set(); public DbSet InventoryTransactions => Set(); - public DbSet Units => Set(); + public DbSet Units => Set(); public DbSet PurchaseOrders => Set(); public DbSet PurchaseOrderLines => Set(); public DbSet Vendors => Set(); public DbSet LocationVendors => Set(); - public DbSet LocationUnits => Set(); + protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -82,14 +80,14 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasForeignKey(ml => ml.LocationId); // Ingredient belongs to a Location - modelBuilder.Entity() + modelBuilder.Entity() .HasOne(i => i.Location) .WithMany() .HasForeignKey(i => i.LocationId) .OnDelete(DeleteBehavior.Cascade); // Ingredient optionally belongs to a Vendor - modelBuilder.Entity() + modelBuilder.Entity() .HasOne(i => i.Vendor) .WithMany() .HasForeignKey(i => i.VendorId) @@ -116,101 +114,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasOne(lv => lv.Vendor) .WithMany(v => v.LocationVendors) .HasForeignKey(lv => lv.VendorId); - - // Recipe belongs to a Location - modelBuilder.Entity() - .HasOne(r => r.Location) - .WithMany(l => l.Recipes) - .HasForeignKey(r => r.LocationId) - .OnDelete(DeleteBehavior.Cascade); - - // RowVersion is a MySQL timestamp(6) with DEFAULT/ON UPDATE CURRENT_TIMESTAMP(6). - // Mark it as database-generated so EF never sends DateTime.MinValue on INSERT/UPDATE. - modelBuilder.Entity() - .Property(r => r.RowVersion) - .ValueGeneratedOnAddOrUpdate() - .IsConcurrencyToken(); - - // Tasks optionally references a Recipe (prep task) - modelBuilder.Entity() - .HasOne(t => t.Recipe) - .WithMany() - .HasForeignKey(t => t.RecipeId) - .OnDelete(DeleteBehavior.SetNull) - .IsRequired(false); - - // RecipeIngredient to parent Recipe - modelBuilder.Entity() - .HasOne(ri => ri.Recipe) - .WithMany(r => r.RecipeIngredients) - .HasForeignKey(ri => ri.RecipeId) - .OnDelete(DeleteBehavior.Cascade); - - // RecipeIngredient - optional Ingredient (raw ingredient line) - modelBuilder.Entity() - .HasOne(ri => ri.Ingredient) - .WithMany() - .HasForeignKey(ri => ri.IngredientId) - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(false); - - // RecipeIngredient - optional sub-Recipe - modelBuilder.Entity() - .HasOne(ri => ri.SubRecipe) - .WithMany() - .HasForeignKey(ri => ri.SubRecipeId) - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(false); - - // RecipeIngredient - Unit - modelBuilder.Entity() - .HasOne(ri => ri.Unit) - .WithMany() - .HasForeignKey(ri => ri.UnitId) - .OnDelete(DeleteBehavior.Restrict); - - // RecipeStep - Recipe - modelBuilder.Entity() - .HasOne(rs => rs.Recipe) - .WithMany(r => r.Steps) - .HasForeignKey(rs => rs.RecipeId) - .OnDelete(DeleteBehavior.Cascade); - - // RecipeSubRecipe: composite PK - modelBuilder.Entity() - .HasKey(rs => new { rs.ParentRecipeId, rs.ChildRecipeId }); - - // RecipeSubRecipe - parent Recipe (cascade: deleting a parent removes its sub-recipe links) - modelBuilder.Entity() - .HasOne(rs => rs.ParentRecipe) - .WithMany(r => r.SubRecipeUsages) - .HasForeignKey(rs => rs.ParentRecipeId) - .OnDelete(DeleteBehavior.Cascade); - - // RecipeSubRecipe - child Recipe (restrict: cannot delete a sub-recipe still in use) - modelBuilder.Entity() - .HasOne(rs => rs.ChildRecipe) - .WithMany(r => r.UsedInRecipes) - .HasForeignKey(rs => rs.ChildRecipeId) - .OnDelete(DeleteBehavior.Restrict); - - ConfigureLocationUnit(modelBuilder); - } - - private void ConfigureLocationUnit(ModelBuilder modelBuilder) - { - modelBuilder.Entity() - .HasKey(lu => new { lu.LocationId, lu.UnitId }); - - modelBuilder.Entity() - .HasOne(lu => lu.Location) - .WithMany(l => l.LocationUnits) - .HasForeignKey(lu => lu.LocationId); - - modelBuilder.Entity() - .HasOne(lu => lu.Unit) - .WithMany(u => u.LocationUnits) - .HasForeignKey(lu => lu.UnitId); + } } } \ No newline at end of file diff --git a/CulinaryCommandApp/Data/Entities/Ingredient.cs b/CulinaryCommandApp/Data/Entities/Ingredient.cs new file mode 100644 index 0000000..944da5b --- /dev/null +++ b/CulinaryCommandApp/Data/Entities/Ingredient.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; + +namespace CulinaryCommand.Data.Entities +{ + public class Ingredient + { + [Key] + public int IngredientId { get; set; } + + [Required, MaxLength(128)] + public string Name { get; set; } + + [Required, MaxLength(128)] + public string Category { get; set; } + + [Required, MaxLength(128)] + public string DefaultUnit { get; set; } + + // Navigation + public ICollection RecipeIngredients { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/CulinaryCommandApp/Data/Entities/Location.cs b/CulinaryCommandApp/Data/Entities/Location.cs index 2025d93..8d84094 100644 --- a/CulinaryCommandApp/Data/Entities/Location.cs +++ b/CulinaryCommandApp/Data/Entities/Location.cs @@ -1,8 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; -using CulinaryCommandApp.Inventory.Entities; -using Rec = CulinaryCommandApp.Recipe.Entities; namespace CulinaryCommand.Data.Entities { @@ -33,7 +31,7 @@ public class Location public int CompanyId { get; set; } public Company Company { get; set; } - public ICollection Recipes { get; set; } = new List(); + public ICollection Recipes { get; set; } = new List(); // join table combining employees and locations [JsonIgnore] @@ -55,8 +53,6 @@ public class Location [JsonIgnore] public ICollection LocationVendors { get; set; } = new List(); - [JsonIgnore] - public ICollection LocationUnits { get; set; } = new List(); - } + } diff --git a/CulinaryCommandApp/Data/Entities/MeasurementUnit.cs b/CulinaryCommandApp/Data/Entities/MeasurementUnit.cs new file mode 100644 index 0000000..5413550 --- /dev/null +++ b/CulinaryCommandApp/Data/Entities/MeasurementUnit.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; + +namespace CulinaryCommand.Data.Entities +{ + public class MeasurementUnit + { + [Key] + public int UnitId { get; set; } + + [Required, MaxLength(128)] + public string Name { get; set; } // "Cup" + + [Required, MaxLength(32)] + public string Abbreviation { get; set; } // "c" + + // Navigation + public ICollection RecipeIngredients { get; set; } = new List(); + } + +} \ No newline at end of file diff --git a/CulinaryCommandApp/Data/Entities/Recipe.cs b/CulinaryCommandApp/Data/Entities/Recipe.cs new file mode 100644 index 0000000..60b15a8 --- /dev/null +++ b/CulinaryCommandApp/Data/Entities/Recipe.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; + +namespace CulinaryCommand.Data.Entities +{ + public class Recipe + { + [Key] + public int RecipeId { get; set; } + public int LocationId { get; set; } + + [Required, MaxLength(128)] + public string? Title { get; set; } + + [Required, MaxLength(128)] + public string Category { get; set; } + + [Required, MaxLength(128)] + public string RecipeType { get; set; } + + public decimal? YieldAmount { get; set; } + + [MaxLength(128)] + public string YieldUnit { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + // Navigation + public Location? Location { get; set; } + public ICollection RecipeIngredients { get; set; } = new List(); + public ICollection Steps { get; set; } = new List(); + } + +} \ No newline at end of file diff --git a/CulinaryCommandApp/Data/Entities/RecipeIngredient.cs b/CulinaryCommandApp/Data/Entities/RecipeIngredient.cs new file mode 100644 index 0000000..d166a1b --- /dev/null +++ b/CulinaryCommandApp/Data/Entities/RecipeIngredient.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Threading.Tasks; +using CulinaryCommand.Inventory.Entities; + +namespace CulinaryCommand.Data.Entities +{ + public class RecipeIngredient + { + [Key] + public int RecipeIngredientId { get; set; } + public int RecipeId { get; set; } + public int IngredientId { get; set; } + public int UnitId { get; set; } + + public decimal Quantity { get; set; } + + [MaxLength(256)] + public string? PrepNote { get; set; } + public int SortOrder { get; set; } + + // Navigation + public Recipe? Recipe { get; set; } + public CulinaryCommand.Inventory.Entities.Ingredient? Ingredient { get; set; } + public Unit? Unit { get; set; } + + [NotMapped] + public List AvailableIngredients { get; set; } = new(); + + [NotMapped] + public List AvailableUnits { get; set; } = new(); + } + +} \ No newline at end of file diff --git a/CulinaryCommandApp/Data/Entities/RecipeStep.cs b/CulinaryCommandApp/Data/Entities/RecipeStep.cs new file mode 100644 index 0000000..8a25b2e --- /dev/null +++ b/CulinaryCommandApp/Data/Entities/RecipeStep.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; + +namespace CulinaryCommand.Data.Entities +{ + public class RecipeStep + { + [Key] + public int StepId { get; set; } + public int RecipeId { get; set; } + + public int StepNumber { get; set; } // 1, 2, 3... + + [MaxLength(256)] + public string? Instructions { get; set; } + + // Navigation + public Recipe? Recipe { get; set; } + } + +} \ No newline at end of file diff --git a/CulinaryCommandApp/Data/Entities/Tasks.cs b/CulinaryCommandApp/Data/Entities/Tasks.cs index af8586d..4e5d78a 100644 --- a/CulinaryCommandApp/Data/Entities/Tasks.cs +++ b/CulinaryCommandApp/Data/Entities/Tasks.cs @@ -1,8 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using CulinaryCommand.Data.Enums; -using Rec = CulinaryCommandApp.Recipe.Entities; -using InvIngredient = CulinaryCommandApp.Inventory.Entities.Ingredient; +using InvIngredient = CulinaryCommand.Inventory.Entities.Ingredient; namespace CulinaryCommand.Data.Entities { @@ -53,7 +52,7 @@ public class Tasks // Link to recipe if this is a prep task public int? RecipeId { get; set; } - public Rec.Recipe? Recipe { get; set; } + public Recipe? Recipe { get; set; } // Link to ingredient (Inventory.Entities.Ingredient) public int? IngredientId { get; set; } diff --git a/CulinaryCommandApp/Data/Models/MeasurementUnitViewModel.cs b/CulinaryCommandApp/Data/Models/MeasurementUnitViewModel.cs new file mode 100644 index 0000000..d311c2f --- /dev/null +++ b/CulinaryCommandApp/Data/Models/MeasurementUnitViewModel.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace CulinaryCommand.Models +{ + public class MeasurementUnitViewModel + { + public int UnitId { get; set; } + public string UnitName { get; set; } = ""; + public string Abbreviation { get; set; } = ""; + } + +} \ No newline at end of file diff --git a/CulinaryCommandApp/Inventory/DTOs/CreateIngredientDTO.cs b/CulinaryCommandApp/Inventory/DTOs/CreateIngredientDTO.cs index 4443575..84abe90 100644 --- a/CulinaryCommandApp/Inventory/DTOs/CreateIngredientDTO.cs +++ b/CulinaryCommandApp/Inventory/DTOs/CreateIngredientDTO.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace CulinaryCommandApp.Inventory.DTOs +namespace CulinaryCommand.Inventory.DTOs { public class CreateIngredientDTO { diff --git a/CulinaryCommandApp/Inventory/DTOs/InventoryCatalogDTO.cs b/CulinaryCommandApp/Inventory/DTOs/InventoryCatalogDTO.cs index ac8398f..0aaf14d 100644 --- a/CulinaryCommandApp/Inventory/DTOs/InventoryCatalogDTO.cs +++ b/CulinaryCommandApp/Inventory/DTOs/InventoryCatalogDTO.cs @@ -1,6 +1,6 @@ using System; -namespace CulinaryCommandApp.Inventory.DTOs +namespace CulinaryCommand.Inventory.DTOs { public class InventoryCatalogDTO { diff --git a/CulinaryCommandApp/Inventory/DTOs/InventoryItemDTO.cs b/CulinaryCommandApp/Inventory/DTOs/InventoryItemDTO.cs index a15276f..0304346 100644 --- a/CulinaryCommandApp/Inventory/DTOs/InventoryItemDTO.cs +++ b/CulinaryCommandApp/Inventory/DTOs/InventoryItemDTO.cs @@ -1,6 +1,6 @@ using System; -namespace CulinaryCommandApp.Inventory.DTOs +namespace CulinaryCommand.Inventory.DTOs { public class InventoryItemDTO { @@ -10,7 +10,6 @@ public class InventoryItemDTO public string Category { get; set; } = string.Empty; public decimal CurrentQuantity { get; set; } public string Unit { get; set; } = "count"; - public int UnitId { get; set; } public decimal Price { get; set; } public decimal ReorderLevel { get; set; } public bool IsLowStock { get; set; } diff --git a/CulinaryCommandApp/Inventory/Data/Configurations/IngredientConfiguration.cs b/CulinaryCommandApp/Inventory/Data/Configurations/IngredientConfiguration.cs index d4cb11a..c4f06b7 100644 --- a/CulinaryCommandApp/Inventory/Data/Configurations/IngredientConfiguration.cs +++ b/CulinaryCommandApp/Inventory/Data/Configurations/IngredientConfiguration.cs @@ -1,6 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -using CulinaryCommandApp.Inventory.Entities; +using CulinaryCommand.Inventory.Entities; namespace CulinaryCommand.Inventory.Data.Configurations { diff --git a/CulinaryCommandApp/Inventory/Entities/Ingredient.cs b/CulinaryCommandApp/Inventory/Entities/Ingredient.cs index a58d097..11df568 100644 --- a/CulinaryCommandApp/Inventory/Entities/Ingredient.cs +++ b/CulinaryCommandApp/Inventory/Entities/Ingredient.cs @@ -1,7 +1,7 @@ using System; using CulinaryCommand.Data.Entities; -namespace CulinaryCommandApp.Inventory.Entities +namespace CulinaryCommand.Inventory.Entities { public class Ingredient { diff --git a/CulinaryCommandApp/Inventory/Entities/InventoryBatch.cs b/CulinaryCommandApp/Inventory/Entities/InventoryBatch.cs index ff34b02..57c3a09 100644 --- a/CulinaryCommandApp/Inventory/Entities/InventoryBatch.cs +++ b/CulinaryCommandApp/Inventory/Entities/InventoryBatch.cs @@ -2,7 +2,7 @@ using CulinaryCommand.Data.Entities; -namespace CulinaryCommandApp.Inventory.Entities +namespace CulinaryCommand.Inventory.Entities { public class InventoryBatch { diff --git a/CulinaryCommandApp/Inventory/Entities/InventoryTransaction.cs b/CulinaryCommandApp/Inventory/Entities/InventoryTransaction.cs index 4ed294d..3f327d3 100644 --- a/CulinaryCommandApp/Inventory/Entities/InventoryTransaction.cs +++ b/CulinaryCommandApp/Inventory/Entities/InventoryTransaction.cs @@ -1,6 +1,6 @@ using System; -namespace CulinaryCommandApp.Inventory.Entities +namespace CulinaryCommand.Inventory.Entities { public class InventoryTransaction diff --git a/CulinaryCommandApp/Inventory/Entities/LocationUnit.cs b/CulinaryCommandApp/Inventory/Entities/LocationUnit.cs deleted file mode 100644 index 7fe3928..0000000 --- a/CulinaryCommandApp/Inventory/Entities/LocationUnit.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Text.Json.Serialization; -using CulinaryCommand.Data.Entities; -using CulinaryCommandApp.Inventory.Entities; - -namespace CulinaryCommandApp.Inventory.Entities -{ - public class LocationUnit - { - [JsonIgnore] - public Location Location { get; set; } = default!; - public int LocationId { get; set; } - - [JsonIgnore] - public Unit Unit { get; set; } = default!; - public int UnitId { get; set; } - - public DateTime AssignedAt { get; set; } = DateTime.UtcNow; - } -} \ No newline at end of file diff --git a/CulinaryCommandApp/Inventory/Entities/Unit.cs b/CulinaryCommandApp/Inventory/Entities/Unit.cs index 676f666..171f42a 100644 --- a/CulinaryCommandApp/Inventory/Entities/Unit.cs +++ b/CulinaryCommandApp/Inventory/Entities/Unit.cs @@ -1,8 +1,7 @@ using System; using System.Collections.Generic; -using System.Text.Json.Serialization; -namespace CulinaryCommandApp.Inventory.Entities +namespace CulinaryCommand.Inventory.Entities { public class Unit { @@ -23,8 +22,5 @@ public class Unit // list of inventory transactions that use this unit public ICollection InventoryTransaction { get; set; } = new List(); - [JsonIgnore] - public ICollection LocationUnits { get; set; } = new List(); - } } \ No newline at end of file diff --git a/CulinaryCommandApp/Inventory/Pages/Inventory/InventoryCatalog.razor b/CulinaryCommandApp/Inventory/Pages/Inventory/InventoryCatalog.razor index 6e02cab..c8efc8b 100644 --- a/CulinaryCommandApp/Inventory/Pages/Inventory/InventoryCatalog.razor +++ b/CulinaryCommandApp/Inventory/Pages/Inventory/InventoryCatalog.razor @@ -1,51 +1,16 @@ @page "/inventory-catalog" @rendermode InteractiveServer -@implements IDisposable - -@using CulinaryCommandApp.Inventory.DTOs -@using CulinaryCommandApp.Inventory.Entities -@using CulinaryCommandApp.Inventory.Services -@using CulinaryCommandApp.Inventory.Services.Interfaces -@using CulinaryCommand.Services -@using CulinaryCommand.Services.UserContextSpace - -@inject IInventoryManagementService InventoryService -@inject IUnitService UnitService -@inject LocationState LocationState -@inject EnumService EnumService -@inject IUserContextService UserCtx -@inject NavigationManager Nav + +@using CulinaryCommand.Inventory.DTOs
- @if (!_ready) - { -
Loading...
- } - else if (!_allowed) - { -
- You need Admin or Manager permissions to access the inventory catalog. -
- } - else - {
-

Inventory Catalog

-

@(LocationState.CurrentLocation is not null ? $"{LocationState.CurrentLocation.Name} β€” ingredient catalog" : "Manage your ingredient catalog")

+

Inventory Catalog - testing deployment change

+

Manage your ingredient catalog

- @if (LocationState.CurrentLocation is null) - { - - } - else - { -
- - } @* end @if (LocationState.CurrentLocation is not null) *@ - - } @* end else (_allowed) *@ -
@@ -192,7 +152,7 @@