From 31adcadb460788aae3b94ff088339a0fb6e88174 Mon Sep 17 00:00:00 2001 From: Logan Silvers Date: Sat, 2 May 2026 18:56:50 -0500 Subject: [PATCH 1/3] pack-a-box changes (broken) fixed ui.. works after a couple clicks but it is not smooth ... --- .../Inventory/TransferPages/Create.razor | 422 ++++++++++-------- .../TransferPages/ProductPickerDialog.razor | 109 +++++ Models/TransferBoxItemModel.cs | 7 + 3 files changed, 347 insertions(+), 191 deletions(-) create mode 100644 Components/Pages/Inventory/TransferPages/ProductPickerDialog.razor create mode 100644 Models/TransferBoxItemModel.cs diff --git a/Components/Pages/Inventory/TransferPages/Create.razor b/Components/Pages/Inventory/TransferPages/Create.razor index acfd5ca..ac1a560 100644 --- a/Components/Pages/Inventory/TransferPages/Create.razor +++ b/Components/Pages/Inventory/TransferPages/Create.razor @@ -1,221 +1,255 @@ @page "/inventory/transfer/create" @using Microsoft.EntityFrameworkCore @using PuppetFestAPP.Web.Data +@using PuppetFestAPP.Web.Models @rendermode InteractiveServer @inject IDbContextFactory DbFactory @inject NavigationManager Navigation +@inject IDialogService DialogService +@inject ISnackbar Snackbar - - -Create Transfer Box - - - Create Transfer Box - - - - - - @* ── Locations Section ── *@ - - - - Select Source... - @foreach (var loc in Locations) + + @* --- STEP 2 --- *@ + else if (currentStep == 2) + { + + BACK + + Pack a Box + + + @Form.BoxTitle + + + @GetLocationName(Form.FromLocationId) + + + + @GetLocationName(Form.ToLocationId) + + + + + Box Contents + + + ADD PRODUCTS + + + + + @foreach (var item in Form.Items.ToList()) { - @loc.Name (@loc.Type) + + + + + +
+ @GetProductName(item.ProductId) + Qty: @item.Quantity +
+
+ +
} -
-
- - - - - Select Destination... - @foreach (var loc in Locations) + + @if (!Form.Items.Any()) { - @loc.Name (@loc.Type) + + No products added.
Click "Add Products" to add products to this box. +
} -
-
+ - - - + + CREATE BOX → + + } + + @* --- STEP 3: Confirm --- *@ + else if (currentStep == 3) + { + + BACK + + Confirm Box Details - @* ── Current Box Items ── *@ - @if (Form.Items.Any()) + + Box Title + @Form.BoxTitle + + + + @GetLocationName(Form.FromLocationId) + + + + @GetLocationName(Form.ToLocationId) + + + + @if (Form.IsUrgent) { - - Box Items - - - Product - Quantity - - - - @GetProductName(item.ProductId) - @item.Quantity - - - - - - + + Urgent + } + - @* ── Add Product Section ── *@ - - - Add Product - - - - Select Product... - @foreach (var prod in Products) - { - @prod.Name @(prod.Size != ProductSize.NA ? $"({prod.Size})" : "") - } - - @if (selectedProductId > 0) - { - In stock here: @maxAvailable - } - - - - - - - Add to Box - + + Items (@Form.Items.Count) + + + + @foreach (var item in Form.Items) + { + + + + + + @GetProductName(item.ProductId) + + x@item.Quantity - - - @* ── Form Actions ── *@ - - - - Create Transfer Box - - Cancel - - -
-
+ } + + + + @if (isSubmitting) + { + + CREATING... + } + else + { + CONFIRM + } + + } +
@code { - public TransferBoxFormModel Form { get; set; } = new(); - private int? selectedProductId; - private int selectedQuantity; + private int currentStep = 1; + private bool isSubmitting = false; private List Locations = new(); - - // NEW: Computed Property. This calculates the live stock every time the page refreshes. Used so when they remove - //Item from box, the count within the dropdown refreshes INSTANTLY - private int maxAvailable => selectedProductId.HasValue ? GetAvailableStock(selectedProductId.Value) : 0; + private List AvailableInventory = new(); - private List AvailableInventory = new(); - private List Products => AvailableInventory.Select(ai => ai.Product!).ToList(); + public TransferBoxFormModel Form { get; set; } = new(); protected override async Task OnInitializedAsync() { using var context = await DbFactory.CreateDbContextAsync(); Locations = await context.Locations.Where(l => l.IsActive).ToListAsync(); + Form.BoxTitle = "Box #" + (await context.StockTransferBoxes.CountAsync() + 1); } - private async Task OnFromLocationChanged(int? newLocationId) + private async Task OpenPicker() { - Form.FromLocationId = newLocationId; - Form.Items.Clear(); - Form.ToLocationId = null; - selectedProductId = null; - - if (newLocationId.HasValue && newLocationId > 0) + // Safety reload in case state got out of sync (fixes double-click bug) + if (!AvailableInventory.Any() && Form.FromLocationId.HasValue) { using var context = await DbFactory.CreateDbContextAsync(); AvailableInventory = await context.ProductLocations - .Where(pl => pl.LocationId == newLocationId && pl.Quantity > 0) .Include(pl => pl.Product) + .Where(pl => pl.LocationId == Form.FromLocationId && pl.Quantity > 0) .ToListAsync(); } - else + + var parameters = new DialogParameters { - AvailableInventory.Clear(); - } - } + { "Inventory", AvailableInventory }, + { "Items", Form.Items } + }; - private void OnProductSelected(int? productId) - { - selectedProductId = productId; - selectedQuantity = 1; - + var options = new DialogOptions + { + FullWidth = true, + CloseButton = false, + MaxWidth = MaxWidth.Small + }; + + await DialogService.ShowAsync("Add Products to Box", parameters, options); + StateHasChanged(); } - private int GetAvailableStock(int productId) + private void OpenConfirmDialog() { - var totalInLocation = AvailableInventory.FirstOrDefault(p => p.ProductId == productId)?.Quantity ?? 0; - var alreadyInBox = Form.Items.Where(i => i.ProductId == productId).Sum(i => i.Quantity); - return totalInLocation - alreadyInBox; + currentStep = 3; } - private void AddItem() + private async Task OnFromLocationChanged(int? newId) { - if (selectedProductId.HasValue && selectedQuantity > 0 && selectedQuantity <= maxAvailable) - { - var existingItem = Form.Items.FirstOrDefault(i => i.ProductId == selectedProductId); - if (existingItem != null) - { - existingItem.Quantity += selectedQuantity; - } - else - { - Form.Items.Add(new TransferBoxItemModel { ProductId = selectedProductId.Value, Quantity = selectedQuantity }); - } + Form.FromLocationId = newId; + Form.Items.Clear(); + AvailableInventory.Clear(); - selectedQuantity = 1; - StateHasChanged(); + if (newId.HasValue) + { + using var context = await DbFactory.CreateDbContextAsync(); + AvailableInventory = await context.ProductLocations + .Include(pl => pl.Product) + .Where(pl => pl.LocationId == newId && pl.Quantity > 0) + .ToListAsync(); } + + await InvokeAsync(StateHasChanged); } private void RemoveItem(TransferBoxItemModel item) @@ -224,44 +258,53 @@ StateHasChanged(); } - private string GetProductName(int productId) => Products.FirstOrDefault(p => p.Id == productId)?.Name ?? "Selected Item"; + private string GetLocationName(int? id) => + Locations.FirstOrDefault(l => l.Id == id)?.Name ?? "Unknown"; + + private string GetProductName(int id) => + AvailableInventory.FirstOrDefault(i => i.ProductId == id)?.Product?.Name ?? "Item"; private async Task CreateTransfer() { if (!Form.FromLocationId.HasValue || !Form.ToLocationId.HasValue || !Form.Items.Any()) return; + isSubmitting = true; + StateHasChanged(); + using var context = await DbFactory.CreateDbContextAsync(); using var transaction = await context.Database.BeginTransactionAsync(); try { - var transferBox = new StockTransferBox { - FromLocationId = Form.FromLocationId.Value, - ToLocationId = Form.ToLocationId.Value, - Notes = Form.Notes, - CreatedAt = DateTime.UtcNow, + var transferBox = new StockTransferBox + { + FromLocationId = Form.FromLocationId.Value, + ToLocationId = Form.ToLocationId.Value, + CreatedAt = DateTime.UtcNow, IsDelivered = false }; - + context.StockTransferBoxes.Add(transferBox); await context.SaveChangesAsync(); foreach (var item in Form.Items) { - context.StockTransferBoxItems.Add(new StockTransferBoxItem { - StockTransferBoxId = transferBox.Id, - ProductId = item.ProductId, - Quantity = item.Quantity + context.StockTransferBoxItems.Add(new StockTransferBoxItem + { + StockTransferBoxId = transferBox.Id, + ProductId = item.ProductId, + Quantity = item.Quantity }); var fromStock = await context.ProductLocations - .FirstOrDefaultAsync(pl => pl.ProductId == item.ProductId && pl.LocationId == Form.FromLocationId.Value); - + .FirstOrDefaultAsync(pl => pl.ProductId == item.ProductId + && pl.LocationId == Form.FromLocationId.Value); if (fromStock != null) { fromStock.Quantity -= item.Quantity; fromStock.LastUpdated = DateTime.UtcNow; } } + await context.SaveChangesAsync(); await transaction.CommitAsync(); Navigation.NavigateTo("/inventory/transfers"); @@ -269,22 +312,19 @@ catch { await transaction.RollbackAsync(); - throw; + isSubmitting = false; + Snackbar.Add("Failed to create transfer. Please try again.", Severity.Error); + currentStep = 2; + StateHasChanged(); } } public class TransferBoxFormModel { + public string BoxTitle { get; set; } = ""; public int? FromLocationId { get; set; } public int? ToLocationId { get; set; } - public string? Notes { get; set; } + public bool IsUrgent { get; set; } public List Items { get; set; } = new(); - public bool IsValid() => FromLocationId.HasValue && ToLocationId.HasValue && Items.Any() && FromLocationId != ToLocationId; - } - - public class TransferBoxItemModel - { - public int ProductId { get; set; } - public int Quantity { get; set; } } } \ No newline at end of file diff --git a/Components/Pages/Inventory/TransferPages/ProductPickerDialog.razor b/Components/Pages/Inventory/TransferPages/ProductPickerDialog.razor new file mode 100644 index 0000000..03bcf46 --- /dev/null +++ b/Components/Pages/Inventory/TransferPages/ProductPickerDialog.razor @@ -0,0 +1,109 @@ +@using PuppetFestAPP.Web.Data +@using PuppetFestAPP.Web.Models +@rendermode InteractiveServer +@inject ISnackbar Snackbar + + + + @* Sticky header with search + filter *@ + + + CONFIRM SELECTION + + + + + + Filter + + + + + @* Scrollable product list *@ +
+ @foreach (var inv in FilteredInventory) + { + var existing = Items.FirstOrDefault(i => i.ProductId == inv.ProductId); + var isAdded = existing != null; + var currentQtyInBox = existing?.Quantity ?? 0; + var available = inv.Quantity - currentQtyInBox; + +
+ + @* Expand chevron placeholder (for future qty editing) *@ + + + @* Thumbnail *@ + + + + + @* Name *@ + @inv.Product?.Name + + @* Badge *@ + @if (isAdded) + { + Added + } + else if (available <= 0) + { + Out of stock + } + else + { + Unadded + } +
+ } + + @if (!FilteredInventory.Any()) + { + + No products found. + + } +
+ +
+ +@code { + [CascadingParameter] IMudDialogInstance MudDialog { get; set; } = default!; + [Parameter] public List Inventory { get; set; } = new(); + [Parameter] public List Items { get; set; } = new(); + + private string searchString = ""; + + private IEnumerable FilteredInventory => + Inventory.Where(x => string.IsNullOrWhiteSpace(searchString) || + (x.Product?.Name.Contains(searchString, StringComparison.OrdinalIgnoreCase) ?? false)); + + private void ToggleItem(ProductLocation inv) + { + var existing = Items.FirstOrDefault(i => i.ProductId == inv.ProductId); + if (existing != null) + { + Items.Remove(existing); + } + else + { + var available = inv.Quantity; + if (available <= 0) return; + Items.Add(new TransferBoxItemModel { ProductId = inv.ProductId, Quantity = 1 }); + } + StateHasChanged(); + } +} \ No newline at end of file diff --git a/Models/TransferBoxItemModel.cs b/Models/TransferBoxItemModel.cs new file mode 100644 index 0000000..efc82a3 --- /dev/null +++ b/Models/TransferBoxItemModel.cs @@ -0,0 +1,7 @@ +namespace PuppetFestAPP.Web.Models; + +public class TransferBoxItemModel +{ + public int ProductId { get; set; } + public int Quantity { get; set; } +} \ No newline at end of file From 006ae2cd2e4e05921d41ca235201a354727a1747 Mon Sep 17 00:00:00 2001 From: T King Date: Wed, 6 May 2026 15:36:17 -0500 Subject: [PATCH 2/3] Add or update the Azure App Service build and deployment workflow config --- .../livebranch_ccpuppetfest-backstage.yml | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 .github/workflows/livebranch_ccpuppetfest-backstage.yml diff --git a/.github/workflows/livebranch_ccpuppetfest-backstage.yml b/.github/workflows/livebranch_ccpuppetfest-backstage.yml new file mode 100644 index 0000000..8eaa154 --- /dev/null +++ b/.github/workflows/livebranch_ccpuppetfest-backstage.yml @@ -0,0 +1,65 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Build and deploy ASP.Net Core app to Azure Web App - ccPuppetFest-BackStage + +on: + push: + branches: + - LiveBranch + workflow_dispatch: + +jobs: + build: + runs-on: windows-latest + permissions: + contents: read #This is required for actions/checkout + + steps: + - uses: actions/checkout@v4 + + - name: Set up .NET Core + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.x' + + - name: Build with dotnet + run: dotnet build --configuration Release + + - name: dotnet publish + run: dotnet publish -c Release -o "${{env.DOTNET_ROOT}}/myapp" + + - name: Upload artifact for deployment job + uses: actions/upload-artifact@v4 + with: + name: .net-app + path: ${{env.DOTNET_ROOT}}/myapp + + deploy: + runs-on: windows-latest + needs: build + permissions: + id-token: write #This is required for requesting the JWT + contents: read #This is required for actions/checkout + + steps: + - name: Download artifact from build job + uses: actions/download-artifact@v4 + with: + name: .net-app + + - name: Login to Azure + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_F929E9398293422AACA3F625F8DB7626 }} + tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_C97255633A26438EBF70221DFB316415 }} + subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_5BEF94648B864CB9B4305809684E5FE3 }} + + - name: Deploy to Azure Web App + id: deploy-to-webapp + uses: azure/webapps-deploy@v3 + with: + app-name: 'ccPuppetFest-BackStage' + slot-name: 'Production' + package: . + \ No newline at end of file From 623c5dcbd3349137395548695688deb7551bd36c Mon Sep 17 00:00:00 2001 From: LuisMartinez-port Date: Wed, 6 May 2026 22:50:04 -0500 Subject: [PATCH 3/3] Fixed Pack A Box and Locations page. Catalog fixes and Admin page fixes are next. Alongside deletes for all pages --- .../Inventory/TransferPages/Create.razor | 138 ++++++----- .../TransferPages/ProductPickerDialog.razor | 234 +++++++++++++----- Components/Pages/Inventory/Transfers.razor | 57 +++++ Components/Pages/LocationPages/Create.razor | 30 ++- Components/Pages/Locations.razor | 64 ++++- Data/app.db | Bin 204800 -> 204800 bytes 6 files changed, 381 insertions(+), 142 deletions(-) diff --git a/Components/Pages/Inventory/TransferPages/Create.razor b/Components/Pages/Inventory/TransferPages/Create.razor index ac1a560..7b95581 100644 --- a/Components/Pages/Inventory/TransferPages/Create.razor +++ b/Components/Pages/Inventory/TransferPages/Create.razor @@ -21,26 +21,35 @@ - - Select Source... - @foreach (var loc in Locations) - { - @loc.Name - } - - - - Select Destination... - @foreach (var loc in Locations) - { - @loc.Name - } - + + + Select Source... + @foreach (var loc in Locations) + { + @loc.Name + } + + + + Select Destination... + @foreach (var loc in Locations) + { + @loc.Name + } + + + Mark this box as urgent to expedite delivery. @@ -154,18 +163,22 @@ @foreach (var item in Form.Items) - { - - - - - - @GetProductName(item.ProductId) - - x@item.Quantity - - } + { + + + + + +
+ @GetProductName(item.ProductId) + +
+
+ + QTY: @item.Quantity + +
+ }
pl.Product) - .Where(pl => pl.LocationId == Form.FromLocationId && pl.Quantity > 0) - .ToListAsync(); + Snackbar.Add("Please select a source location first.", Severity.Warning); + return; } + + using var context = await DbFactory.CreateDbContextAsync(); + AvailableInventory = await context.ProductLocations + .Include(pl => pl.Product) + .ThenInclude(p => p.ParentProduct) + .Where(pl => pl.LocationId == Form.FromLocationId && pl.Quantity > 0) + .ToListAsync(); + var parameters = new DialogParameters { { "Inventory", AvailableInventory }, @@ -225,7 +242,10 @@ MaxWidth = MaxWidth.Small }; - await DialogService.ShowAsync("Add Products to Box", parameters, options); + var dialog = await DialogService.ShowAsync("Add Products to Box", parameters, options); + var result = await dialog.Result; + + StateHasChanged(); } @@ -235,22 +255,12 @@ } private async Task OnFromLocationChanged(int? newId) - { - Form.FromLocationId = newId; - Form.Items.Clear(); - AvailableInventory.Clear(); - - if (newId.HasValue) - { - using var context = await DbFactory.CreateDbContextAsync(); - AvailableInventory = await context.ProductLocations - .Include(pl => pl.Product) - .Where(pl => pl.LocationId == newId && pl.Quantity > 0) - .ToListAsync(); - } - - await InvokeAsync(StateHasChanged); - } +{ + Form.FromLocationId = newId; + Form.Items.Clear(); + AvailableInventory.Clear(); + StateHasChanged(); +} private void RemoveItem(TransferBoxItemModel item) { @@ -261,9 +271,17 @@ private string GetLocationName(int? id) => Locations.FirstOrDefault(l => l.Id == id)?.Name ?? "Unknown"; - private string GetProductName(int id) => - AvailableInventory.FirstOrDefault(i => i.ProductId == id)?.Product?.Name ?? "Item"; - + private string GetProductName(int id) + { + var item = AvailableInventory.FirstOrDefault(i => i.ProductId == id); + if (item?.Product == null) return "Unknown Item"; + + // Use the Parent Name if it exists, otherwise fallback to the Product Name + var name = item.Product.ParentProduct?.Name ?? item.Product.Name; + + // Add the variant details (Size/Color) so the user knows exactly which one is in the box + return $"{name} ({item.Product.Size} / {item.Product.Color})"; + } private async Task CreateTransfer() { if (!Form.FromLocationId.HasValue || !Form.ToLocationId.HasValue || !Form.Items.Any()) return; diff --git a/Components/Pages/Inventory/TransferPages/ProductPickerDialog.razor b/Components/Pages/Inventory/TransferPages/ProductPickerDialog.razor index 03bcf46..911f2fc 100644 --- a/Components/Pages/Inventory/TransferPages/ProductPickerDialog.razor +++ b/Components/Pages/Inventory/TransferPages/ProductPickerDialog.razor @@ -19,64 +19,96 @@ Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" Immediate="true" Variant="Variant.Outlined" Margin="Margin.Dense" Style="flex:1" /> - - Filter - + + Clear Filters + Low Stock +
@* Scrollable product list *@ -
- @foreach (var inv in FilteredInventory) - { - var existing = Items.FirstOrDefault(i => i.ProductId == inv.ProductId); - var isAdded = existing != null; - var currentQtyInBox = existing?.Quantity ?? 0; - var available = inv.Quantity - currentQtyInBox; - -
- - @* Expand chevron placeholder (for future qty editing) *@ - - - @* Thumbnail *@ - - - - - @* Name *@ - @inv.Product?.Name - - @* Badge *@ - @if (isAdded) - { - Added - } - else if (available <= 0) - { - Out of stock - } - else - { - Unadded - } -
- } +
+ + + @foreach (var parent in GroupedInventory) + { + + + +
+ + + + @parent.ParentName +
+
+ + + @foreach (var v in parent.Variants) + { + + { + var existing = Items.FirstOrDefault(i => i.ProductId == v.ProductId); + var currentQty = existing?.Quantity ?? 0; - @if (!FilteredInventory.Any()) - { - - No products found. +
+
+ @v.VariantName + Available: @v.StockAvailable +
+ +
+ @if (currentQty > 0) + { + + } + + + + +
+
+ } + } +
+
+
+ } + + + @if (!GroupedInventory.Any()) + { +
+ + + No products found matching
"@searchString"
- } -
+
+ } +
@@ -87,23 +119,99 @@ private string searchString = ""; - private IEnumerable FilteredInventory => - Inventory.Where(x => string.IsNullOrWhiteSpace(searchString) || - (x.Product?.Name.Contains(searchString, StringComparison.OrdinalIgnoreCase) ?? false)); + private IEnumerable GroupedInventory => Inventory + .Where(x => string.IsNullOrWhiteSpace(searchString) || + (x.Product?.Name.Contains(searchString, StringComparison.OrdinalIgnoreCase) ?? false)) + + .GroupBy(x => x.Product?.ParentProductId ?? x.Product?.Id ?? 0) + .Select(g => { + var firstItem = g.First().Product; + + var cleanParentName = firstItem?.ParentProduct?.Name ?? firstItem?.Name ?? "Unknown Product"; - private void ToggleItem(ProductLocation inv) + return new GroupedProduct { + ParentName = cleanParentName, + Variants = g.Select(v => new VariantInfo { + ProductId = v.ProductId, + + VariantName = $"{v.Product?.Size} / {v.Product?.Color}", + StockAvailable = v.Quantity + }).ToList() + }; + }); + + private void UpdateQuantity(VariantInfo variant, int change) + { + var existing = Items.FirstOrDefault(i => i.ProductId == variant.ProductId); + + if (existing == null && change > 0) + { + // Not in the box yet? Add it with quantity 1 + Items.Add(new TransferBoxItemModel + { + ProductId = variant.ProductId, + Quantity = 1 + }); + } + else if (existing != null) + { + // Already in the box? Update the number + existing.Quantity += change; + + // If user subtracts down to 0, kick it out of the list + if (existing.Quantity <= 0) + { + Items.Remove(existing); + } + + // Safety: Don't let them add more than what's on the shelf + if (existing.Quantity > variant.StockAvailable) + { + existing.Quantity = variant.StockAvailable; + Snackbar.Add($"Cannot exceed available stock ({variant.StockAvailable})", Severity.Warning); + } + } + + StateHasChanged(); + } + + private void HandleNumericChange(VariantInfo variant, int newValue) +{ + var existing = Items.FirstOrDefault(i => i.ProductId == variant.ProductId); + + if (newValue <= 0) { - var existing = Items.FirstOrDefault(i => i.ProductId == inv.ProductId); - if (existing != null) + if (existing != null) Items.Remove(existing); + } + else + { + // Cap the value at the maximum available stock + if (newValue > variant.StockAvailable) + { + newValue = variant.StockAvailable; + Snackbar.Add($"Capped at available stock ({variant.StockAvailable})", Severity.Warning); + } + + if (existing == null) { - Items.Remove(existing); + Items.Add(new TransferBoxItemModel { ProductId = variant.ProductId, Quantity = newValue }); } else { - var available = inv.Quantity; - if (available <= 0) return; - Items.Add(new TransferBoxItemModel { ProductId = inv.ProductId, Quantity = 1 }); + existing.Quantity = newValue; } - StateHasChanged(); + } + StateHasChanged(); +} + + public class GroupedProduct { + public string ParentName { get; set; } = ""; + public List Variants { get; set; } = new(); + } + + public class VariantInfo { + public int ProductId { get; set; } + public string VariantName { get; set; } = ""; + public int StockAvailable { get; set; } } } \ No newline at end of file diff --git a/Components/Pages/Inventory/Transfers.razor b/Components/Pages/Inventory/Transfers.razor index 7854bd0..6ba358e 100644 --- a/Components/Pages/Inventory/Transfers.razor +++ b/Components/Pages/Inventory/Transfers.razor @@ -2,7 +2,10 @@ @using Microsoft.EntityFrameworkCore @using PuppetFestAPP.Web.Data @using System.Linq +@using MudBlazor @rendermode InteractiveServer +@inject ISnackbar Snackbar +@inject NavigationManager Navigation @inject IDbContextFactory DbFactory Transfer Boxes @@ -61,6 +64,12 @@ Href="@($"/inventory/transfer/details/{box.Id}")"> View + + b.Items) + .FirstOrDefaultAsync(b => b.Id == id); + + if (box == null) return; + + try + { + foreach (var item in box.Items) + { + var sourceStock = await context.ProductLocations + .FirstOrDefaultAsync(pl => pl.LocationId == box.FromLocationId && pl.ProductId == item.ProductId); + + if (sourceStock != null) + { + sourceStock.Quantity += item.Quantity; + } + else + { + context.ProductLocations.Add(new ProductLocation + { + LocationId = box.FromLocationId, + ProductId = item.ProductId, + Quantity = item.Quantity, + LastUpdated = DateTime.UtcNow + }); + } + } + + context.StockTransferBoxes.Remove(box); + await context.SaveChangesAsync(); + + Snackbar.Add("Transfer deleted and stock refunded.", Severity.Info); + await LoadData(); + } + catch (Exception ex) + { + Snackbar.Add("Error: Could not delete transfer.", Severity.Error); + } +} + } \ No newline at end of file diff --git a/Components/Pages/LocationPages/Create.razor b/Components/Pages/LocationPages/Create.razor index a046898..79269ba 100644 --- a/Components/Pages/LocationPages/Create.razor +++ b/Components/Pages/LocationPages/Create.razor @@ -3,20 +3,26 @@ @using Microsoft.EntityFrameworkCore @inject IDbContextFactory DbFactory @inject NavigationManager Navigation - -Create Location +@rendermode InteractiveServer Create Location - - + + - + @foreach (var type in Enum.GetValues()) @@ -35,18 +41,24 @@ @code { - [SupplyParameterFromForm] + public LocationFormModel Form { get; set; } = new(); private async Task CreateLocation() { + + if (string.IsNullOrWhiteSpace(Form.Name)) return; + using var context = await DbFactory.CreateDbContextAsync(); - context.Locations.Add(new Location + + var newLocation = new Location { Name = Form.Name, Address = Form.Address, Type = Form.Type - }); + }; + + context.Locations.Add(newLocation); await context.SaveChangesAsync(); Navigation.NavigateTo("/Locations"); } @@ -57,4 +69,4 @@ public string Address { get; set; } = ""; public LocationType Type { get; set; } = LocationType.Store; } -} +} \ No newline at end of file diff --git a/Components/Pages/Locations.razor b/Components/Pages/Locations.razor index 4c2004c..cb0959e 100644 --- a/Components/Pages/Locations.razor +++ b/Components/Pages/Locations.razor @@ -4,6 +4,7 @@ @using System.Linq @rendermode InteractiveServer @inject IDbContextFactory DbFactory +@inject ISnackbar Snackbar Locations @@ -38,9 +39,23 @@ else @loc.Address
- - -
+ + + + + + @@ -62,9 +77,23 @@ else @loc.Address
- - -
+ + + + + + @@ -77,16 +106,31 @@ else @code { private List? AllLocations; - // Filters for the groups + private IEnumerable MainLocations => - AllLocations?.Where(l => l.Name.Contains("410 S Michigan", StringComparison.OrdinalIgnoreCase)) ?? Enumerable.Empty(); + AllLocations?.Where(l => l.Type == LocationType.Store) ?? Enumerable.Empty(); private IEnumerable OtherLocations => - AllLocations?.Where(l => !l.Name.Contains("410 S Michigan", StringComparison.OrdinalIgnoreCase)) ?? Enumerable.Empty(); + AllLocations?.Where(l => l.Type != LocationType.Store) ?? Enumerable.Empty(); + + protected override async Task OnInitializedAsync() => await LoadData(); - protected override async Task OnInitializedAsync() + private async Task LoadData() { using var db = await DbFactory.CreateDbContextAsync(); AllLocations = await db.Locations.ToListAsync(); } + + private async Task DeleteLocation(int id) + { + using var db = await DbFactory.CreateDbContextAsync(); + var loc = await db.Locations.FindAsync(id); + if (loc != null) + { + db.Locations.Remove(loc); + await db.SaveChangesAsync(); + await LoadData(); // Refresh the UI + Snackbar.Add("Location deleted", Severity.Info); + } + } } \ No newline at end of file diff --git a/Data/app.db b/Data/app.db index d449eb810b8d0a155f9ee619774a637c23552b27..00228c4d8ac546d650247ed6e77bfbce918c278d 100644 GIT binary patch delta 997 zcma))O=uHA6vtixNRkkNvYa@EU>@$nMc4sr z#YJ&i94q;}Cq+Z%*q)$5lmyX;q9`P;$~_$e1H1G4GyNyi1KlI3Y%13~oIjqeVvh#L-)(6veUjE=KPOHi_yCXLHXMcoxWsvJlKy>Ll-k)I{NG=5rTHz^F&rAt zO%QKbVA%-XlkM*DU#ZA~*trIrsI8929Rp``xeSdo zI+#73PZ!ep0$YcjXan$^DwF8J=-y2KVCF>ca1=fxq0T8dks$CK&y8=MDfxWCI-X0V z&*Td!`Z8aLJ}l#@O+o~%bN8|C%P zJ!z8>thlo=mlI*kZ{ycjvTr9^`$}^ZH7O?~L)8)#1uo zk{FVv8=9%>O`(5KE1B!MsmE2t9s=gh2sevrr)mv6Yl}tZDXzWL((DS#GQN7oc3xl( zVNbO2_oP~PyYOo3S_kIsBV)`Xj}vC$2|S@=at|7~kb{;R2i+{q%wMs1hI550mwbq^ z-%c>9w+5DAspKoafZ7_C$AQWbvX*9;mKs=qchvJ7tn3f$a0MJJ#t!Ox$+S4(Bh0}Z ml_|rj%Dp>8B|Ln6#b(0R;(Mm`FDAIm1ecjm*k1g?)RUj880jkj delta 559 zcmY*W&ubG=5PtL0Y?3DXW>ZsZZ8f_Mkrtcyb~nE^lzLFH2NguDAPNmV*g#B*dXu&h zd(ev*BP@7H{{pF9&EhFJD0mR0w~|^d^pbUQ&YrAL|g@#rOCUZ(;@o6|xV^=}g;m5Uk&8a3v^vJW;q*nJ+vj zR?7D!XQOdlkqI8fwwPMLDNKkNil+uaA`=+!r*y7fQ!f|i7m9ZZWfgxA9L7Lkwor~) zmRg@`IpN5J{J(0W4eMuw?GwRGird)47kC$EFouL}vnH#t61&L88Bj5^Su15~K8XMX&IUC5>M{)4(G{?IeXls0 zp0-OXP>IKccOg1QhYfCI5jB+umxlV_V%2vY zb@ULUBZqkMxYsQqeYNO6ApPt8IXG}xm41ZUnl>O!O1&qpF2#LAx(-j{?#?4R3+}DQ z^liuqw?Cu%wLw#gC_89ZeuLNEZ&B?p-EGl6`{x%rCg>L0-S2d~SGnDNdW`=8X2X=P