Compare commits
5 Commits
8e73d630d5
...
5cc088ea6b
| Author | SHA1 | Date | |
|---|---|---|---|
| 5cc088ea6b | |||
| 6797d1e4fd | |||
| c4fc88f7d2 | |||
| 9929d82768 | |||
| 0ded77ce8b |
@@ -23,6 +23,11 @@
|
|||||||
<span class="bi bi-box-nav-menu" aria-hidden="true"></span> Materials
|
<span class="bi bi-box-nav-menu" aria-hidden="true"></span> Materials
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="nav-item px-3">
|
||||||
|
<NavLink class="nav-link" href="stock">
|
||||||
|
<span class="bi bi-boxes-nav-menu" aria-hidden="true"></span> Stock Items
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
<div class="nav-item px-3">
|
<div class="nav-item px-3">
|
||||||
<NavLink class="nav-link" href="suppliers">
|
<NavLink class="nav-link" href="suppliers">
|
||||||
<span class="bi bi-building-nav-menu" aria-hidden="true"></span> Suppliers
|
<span class="bi bi-building-nav-menu" aria-hidden="true"></span> Suppliers
|
||||||
|
|||||||
371
CutList.Web/Components/Pages/Stock/Edit.razor
Normal file
371
CutList.Web/Components/Pages/Stock/Edit.razor
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
@page "/stock/new"
|
||||||
|
@page "/stock/{Id:int}"
|
||||||
|
@inject StockItemService StockItemService
|
||||||
|
@inject MaterialService MaterialService
|
||||||
|
@inject SupplierService SupplierService
|
||||||
|
@inject NavigationManager Navigation
|
||||||
|
@using CutList.Core.Formatting
|
||||||
|
|
||||||
|
<PageTitle>@(IsNew ? "Add Stock Item" : "Edit Stock Item")</PageTitle>
|
||||||
|
|
||||||
|
<h1>@(IsNew ? "Add Stock Item" : $"{stockItem.Material?.DisplayName} - {ArchUnits.FormatFromInches((double)stockItem.LengthInches)}")</h1>
|
||||||
|
|
||||||
|
@if (loading)
|
||||||
|
{
|
||||||
|
<p><em>Loading...</em></p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-6 mb-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0">Stock Item Details</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<EditForm Model="stockItem" OnValidSubmit="SaveStockItemAsync">
|
||||||
|
<DataAnnotationsValidator />
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Material</label>
|
||||||
|
<select class="form-select" @bind="stockItem.MaterialId" disabled="@(!IsNew)">
|
||||||
|
<option value="0">-- Select Material --</option>
|
||||||
|
@foreach (var material in materials)
|
||||||
|
{
|
||||||
|
<option value="@material.Id">@material.DisplayName</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Length</label>
|
||||||
|
@if (IsNew)
|
||||||
|
{
|
||||||
|
<LengthInput @bind-Value="stockItem.LengthInches" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<input type="text" class="form-control" value="@ArchUnits.FormatFromInches((double)stockItem.LengthInches)" readonly />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Name (optional)</label>
|
||||||
|
<InputText class="form-control" @bind-Value="stockItem.Name" placeholder="Custom display name" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(errorMessage))
|
||||||
|
{
|
||||||
|
<div class="alert alert-danger">@errorMessage</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button type="submit" class="btn btn-primary" disabled="@saving">
|
||||||
|
@if (saving)
|
||||||
|
{
|
||||||
|
<span class="spinner-border spinner-border-sm me-1"></span>
|
||||||
|
}
|
||||||
|
@(IsNew ? "Create Stock Item" : "Save Changes")
|
||||||
|
</button>
|
||||||
|
<a href="stock" class="btn btn-outline-secondary">@(IsNew ? "Cancel" : "Back to List")</a>
|
||||||
|
</div>
|
||||||
|
</EditForm>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (!IsNew)
|
||||||
|
{
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0">Supplier Offerings</h5>
|
||||||
|
<button class="btn btn-sm btn-primary" @onclick="ShowAddOfferingForm">Add Offering</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
@if (showOfferingForm)
|
||||||
|
{
|
||||||
|
<div class="border rounded p-3 mb-3 bg-light">
|
||||||
|
<h6>@(editingOffering == null ? "Add Offering" : "Edit Offering")</h6>
|
||||||
|
<div class="row g-2">
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label">Supplier</label>
|
||||||
|
<select class="form-select" @bind="newOffering.SupplierId">
|
||||||
|
<option value="0">-- Select Supplier --</option>
|
||||||
|
@foreach (var supplier in suppliers)
|
||||||
|
{
|
||||||
|
<option value="@supplier.Id">@supplier.Name</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Part Number</label>
|
||||||
|
<InputText class="form-control" @bind-Value="newOffering.PartNumber" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Price</label>
|
||||||
|
<InputNumber class="form-control" @bind-Value="newOffering.Price" placeholder="0.00" />
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label">Supplier Description</label>
|
||||||
|
<InputText class="form-control" @bind-Value="newOffering.SupplierDescription" />
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label">Notes</label>
|
||||||
|
<InputText class="form-control" @bind-Value="newOffering.Notes" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (!string.IsNullOrEmpty(offeringErrorMessage))
|
||||||
|
{
|
||||||
|
<div class="alert alert-danger mt-2 mb-0">@offeringErrorMessage</div>
|
||||||
|
}
|
||||||
|
<div class="mt-3 d-flex gap-2">
|
||||||
|
<button class="btn btn-primary btn-sm" @onclick="SaveOfferingAsync" disabled="@savingOffering">
|
||||||
|
@if (savingOffering)
|
||||||
|
{
|
||||||
|
<span class="spinner-border spinner-border-sm me-1"></span>
|
||||||
|
}
|
||||||
|
@(editingOffering == null ? "Add" : "Save")
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-secondary btn-sm" @onclick="CancelOfferingForm">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (offerings.Count == 0)
|
||||||
|
{
|
||||||
|
<p class="text-muted">No supplier offerings configured yet.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<table class="table table-sm">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Supplier</th>
|
||||||
|
<th>Part #</th>
|
||||||
|
<th>Price</th>
|
||||||
|
<th style="width: 100px;">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var offering in offerings)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td>@offering.Supplier.Name</td>
|
||||||
|
<td>@(offering.PartNumber ?? "-")</td>
|
||||||
|
<td>@(offering.Price.HasValue ? offering.Price.Value.ToString("C") : "-")</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-sm btn-outline-primary" @onclick="() => EditOffering(offering)">Edit</button>
|
||||||
|
<button class="btn btn-sm btn-outline-danger" @onclick="() => ConfirmDeleteOffering(offering)">Delete</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<ConfirmDialog @ref="deleteOfferingDialog"
|
||||||
|
Title="Delete Offering"
|
||||||
|
Message="@deleteOfferingMessage"
|
||||||
|
ConfirmText="Delete"
|
||||||
|
OnConfirm="DeleteOfferingConfirmed" />
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public int? Id { get; set; }
|
||||||
|
|
||||||
|
private StockItem stockItem = new();
|
||||||
|
private List<Material> materials = new();
|
||||||
|
private List<Supplier> suppliers = new();
|
||||||
|
private List<SupplierOffering> offerings = new();
|
||||||
|
private bool loading = true;
|
||||||
|
private bool saving;
|
||||||
|
private bool savingOffering;
|
||||||
|
private string? errorMessage;
|
||||||
|
private string? offeringErrorMessage;
|
||||||
|
|
||||||
|
private bool showOfferingForm;
|
||||||
|
private SupplierOffering newOffering = new();
|
||||||
|
private SupplierOffering? editingOffering;
|
||||||
|
|
||||||
|
private ConfirmDialog deleteOfferingDialog = null!;
|
||||||
|
private SupplierOffering? offeringToDelete;
|
||||||
|
private string deleteOfferingMessage = "";
|
||||||
|
|
||||||
|
private bool IsNew => !Id.HasValue;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
materials = await MaterialService.GetAllAsync();
|
||||||
|
suppliers = await SupplierService.GetAllAsync();
|
||||||
|
|
||||||
|
if (Id.HasValue)
|
||||||
|
{
|
||||||
|
var existing = await StockItemService.GetByIdAsync(Id.Value);
|
||||||
|
if (existing == null)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo("stock");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
stockItem = existing;
|
||||||
|
offerings = existing.SupplierOfferings.Where(o => o.IsActive).ToList();
|
||||||
|
}
|
||||||
|
loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveStockItemAsync()
|
||||||
|
{
|
||||||
|
errorMessage = null;
|
||||||
|
saving = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (stockItem.MaterialId == 0)
|
||||||
|
{
|
||||||
|
errorMessage = "Please select a material";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stockItem.LengthInches <= 0)
|
||||||
|
{
|
||||||
|
errorMessage = "Length must be greater than zero";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var exists = await StockItemService.ExistsAsync(
|
||||||
|
stockItem.MaterialId,
|
||||||
|
stockItem.LengthInches,
|
||||||
|
IsNew ? null : stockItem.Id);
|
||||||
|
|
||||||
|
if (exists)
|
||||||
|
{
|
||||||
|
errorMessage = "A stock item with this material and length already exists";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsNew)
|
||||||
|
{
|
||||||
|
var created = await StockItemService.CreateAsync(stockItem);
|
||||||
|
Navigation.NavigateTo($"stock/{created.Id}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await StockItemService.UpdateAsync(stockItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
saving = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowAddOfferingForm()
|
||||||
|
{
|
||||||
|
editingOffering = null;
|
||||||
|
newOffering = new SupplierOffering { StockItemId = Id!.Value };
|
||||||
|
showOfferingForm = true;
|
||||||
|
offeringErrorMessage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EditOffering(SupplierOffering offering)
|
||||||
|
{
|
||||||
|
editingOffering = offering;
|
||||||
|
newOffering = new SupplierOffering
|
||||||
|
{
|
||||||
|
Id = offering.Id,
|
||||||
|
StockItemId = offering.StockItemId,
|
||||||
|
SupplierId = offering.SupplierId,
|
||||||
|
PartNumber = offering.PartNumber,
|
||||||
|
SupplierDescription = offering.SupplierDescription,
|
||||||
|
Price = offering.Price,
|
||||||
|
Notes = offering.Notes
|
||||||
|
};
|
||||||
|
showOfferingForm = true;
|
||||||
|
offeringErrorMessage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CancelOfferingForm()
|
||||||
|
{
|
||||||
|
showOfferingForm = false;
|
||||||
|
editingOffering = null;
|
||||||
|
offeringErrorMessage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveOfferingAsync()
|
||||||
|
{
|
||||||
|
offeringErrorMessage = null;
|
||||||
|
savingOffering = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (newOffering.SupplierId == 0)
|
||||||
|
{
|
||||||
|
offeringErrorMessage = "Please select a supplier";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var exists = await SupplierService.OfferingExistsAsync(
|
||||||
|
newOffering.SupplierId,
|
||||||
|
newOffering.StockItemId,
|
||||||
|
editingOffering?.Id);
|
||||||
|
|
||||||
|
if (exists)
|
||||||
|
{
|
||||||
|
offeringErrorMessage = "This supplier already has an offering for this stock item";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editingOffering == null)
|
||||||
|
{
|
||||||
|
await SupplierService.AddOfferingAsync(newOffering);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await SupplierService.UpdateOfferingAsync(newOffering);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh the stock item to get updated offerings
|
||||||
|
var updated = await StockItemService.GetByIdAsync(Id!.Value);
|
||||||
|
if (updated != null)
|
||||||
|
{
|
||||||
|
stockItem = updated;
|
||||||
|
offerings = updated.SupplierOfferings.Where(o => o.IsActive).ToList();
|
||||||
|
}
|
||||||
|
showOfferingForm = false;
|
||||||
|
editingOffering = null;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
savingOffering = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfirmDeleteOffering(SupplierOffering offering)
|
||||||
|
{
|
||||||
|
offeringToDelete = offering;
|
||||||
|
deleteOfferingMessage = $"Are you sure you want to delete the offering from {offering.Supplier.Name}?";
|
||||||
|
deleteOfferingDialog.Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DeleteOfferingConfirmed()
|
||||||
|
{
|
||||||
|
if (offeringToDelete != null)
|
||||||
|
{
|
||||||
|
await SupplierService.DeleteOfferingAsync(offeringToDelete.Id);
|
||||||
|
|
||||||
|
// Refresh the stock item to get updated offerings
|
||||||
|
var updated = await StockItemService.GetByIdAsync(Id!.Value);
|
||||||
|
if (updated != null)
|
||||||
|
{
|
||||||
|
stockItem = updated;
|
||||||
|
offerings = updated.SupplierOfferings.Where(o => o.IsActive).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
85
CutList.Web/Components/Pages/Stock/Index.razor
Normal file
85
CutList.Web/Components/Pages/Stock/Index.razor
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
@page "/stock"
|
||||||
|
@inject StockItemService StockItemService
|
||||||
|
@inject NavigationManager Navigation
|
||||||
|
@using CutList.Core.Formatting
|
||||||
|
|
||||||
|
<PageTitle>Stock Items</PageTitle>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<h1>Stock Items</h1>
|
||||||
|
<a href="stock/new" class="btn btn-primary">Add Stock Item</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (loading)
|
||||||
|
{
|
||||||
|
<p><em>Loading...</em></p>
|
||||||
|
}
|
||||||
|
else if (stockItems.Count == 0)
|
||||||
|
{
|
||||||
|
<div class="alert alert-info">
|
||||||
|
No stock items found. <a href="stock/new">Add your first stock item</a>.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Material</th>
|
||||||
|
<th>Length</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th style="width: 120px;">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var item in stockItems)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td>@item.Material.DisplayName</td>
|
||||||
|
<td>@ArchUnits.FormatFromInches((double)item.LengthInches)</td>
|
||||||
|
<td>@(item.Name ?? "-")</td>
|
||||||
|
<td>
|
||||||
|
<a href="stock/@item.Id" class="btn btn-sm btn-outline-primary">Edit</a>
|
||||||
|
<button class="btn btn-sm btn-outline-danger" @onclick="() => ConfirmDelete(item)">Delete</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
|
||||||
|
<ConfirmDialog @ref="deleteDialog"
|
||||||
|
Title="Delete Stock Item"
|
||||||
|
Message="@deleteMessage"
|
||||||
|
ConfirmText="Delete"
|
||||||
|
OnConfirm="DeleteConfirmed" />
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private List<StockItem> stockItems = new();
|
||||||
|
private bool loading = true;
|
||||||
|
private ConfirmDialog deleteDialog = null!;
|
||||||
|
private StockItem? itemToDelete;
|
||||||
|
private string deleteMessage = "";
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
stockItems = await StockItemService.GetAllAsync();
|
||||||
|
loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfirmDelete(StockItem item)
|
||||||
|
{
|
||||||
|
itemToDelete = item;
|
||||||
|
deleteMessage = $"Are you sure you want to delete \"{item.Material.DisplayName} - {ArchUnits.FormatFromInches((double)item.LengthInches)}\"?";
|
||||||
|
deleteDialog.Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DeleteConfirmed()
|
||||||
|
{
|
||||||
|
if (itemToDelete != null)
|
||||||
|
{
|
||||||
|
await StockItemService.DeleteAsync(itemToDelete.Id);
|
||||||
|
stockItems = await StockItemService.GetAllAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
@page "/suppliers/new"
|
@page "/suppliers/new"
|
||||||
@page "/suppliers/{Id:int}"
|
@page "/suppliers/{Id:int}"
|
||||||
@inject SupplierService SupplierService
|
@inject SupplierService SupplierService
|
||||||
@inject MaterialService MaterialService
|
|
||||||
@inject NavigationManager Navigation
|
@inject NavigationManager Navigation
|
||||||
|
@inject CutList.Web.Data.ApplicationDbContext DbContext
|
||||||
@using CutList.Core.Formatting
|
@using CutList.Core.Formatting
|
||||||
|
@using Microsoft.EntityFrameworkCore
|
||||||
|
|
||||||
<PageTitle>@(IsNew ? "Add Supplier" : "Edit Supplier")</PageTitle>
|
<PageTitle>@(IsNew ? "Add Supplier" : "Edit Supplier")</PageTitle>
|
||||||
|
|
||||||
@@ -66,80 +67,84 @@ else
|
|||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<h5 class="mb-0">Stock Lengths</h5>
|
<h5 class="mb-0">Supplier Offerings</h5>
|
||||||
<button class="btn btn-sm btn-primary" @onclick="ShowAddStockForm">Add Stock</button>
|
<button class="btn btn-sm btn-primary" @onclick="ShowAddOfferingForm">Add Offering</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@if (showStockForm)
|
@if (showOfferingForm)
|
||||||
{
|
{
|
||||||
<div class="border rounded p-3 mb-3 bg-light">
|
<div class="border rounded p-3 mb-3 bg-light">
|
||||||
<h6>@(editingStock == null ? "Add Stock Length" : "Edit Stock Length")</h6>
|
<h6>@(editingOffering == null ? "Add Offering" : "Edit Offering")</h6>
|
||||||
<div class="row g-2">
|
<div class="row g-2">
|
||||||
<div class="col-md-6">
|
<div class="col-12">
|
||||||
<label class="form-label">Material</label>
|
<label class="form-label">Stock Item</label>
|
||||||
<select class="form-select" @bind="newStock.MaterialId">
|
<select class="form-select" @bind="newOffering.StockItemId">
|
||||||
<option value="0">-- Select Material --</option>
|
<option value="0">-- Select Stock Item --</option>
|
||||||
@foreach (var material in materials)
|
@foreach (var stockItem in stockItems)
|
||||||
{
|
{
|
||||||
<option value="@material.Id">@material.DisplayName</option>
|
<option value="@stockItem.Id">@stockItem.Material.DisplayName - @ArchUnits.FormatFromInches((double)stockItem.LengthInches)</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label">Length</label>
|
<label class="form-label">Part Number</label>
|
||||||
<LengthInput @bind-Value="newStock.LengthInches" />
|
<InputText class="form-control" @bind-Value="newOffering.PartNumber" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label">Price (optional)</label>
|
<label class="form-label">Price</label>
|
||||||
<InputNumber class="form-control" @bind-Value="newStock.Price" placeholder="0.00" />
|
<InputNumber class="form-control" @bind-Value="newOffering.Price" placeholder="0.00" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-12">
|
||||||
|
<label class="form-label">Supplier Description</label>
|
||||||
|
<InputText class="form-control" @bind-Value="newOffering.SupplierDescription" />
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
<label class="form-label">Notes</label>
|
<label class="form-label">Notes</label>
|
||||||
<InputText class="form-control" @bind-Value="newStock.Notes" />
|
<InputText class="form-control" @bind-Value="newOffering.Notes" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (!string.IsNullOrEmpty(stockErrorMessage))
|
@if (!string.IsNullOrEmpty(offeringErrorMessage))
|
||||||
{
|
{
|
||||||
<div class="alert alert-danger mt-2 mb-0">@stockErrorMessage</div>
|
<div class="alert alert-danger mt-2 mb-0">@offeringErrorMessage</div>
|
||||||
}
|
}
|
||||||
<div class="mt-3 d-flex gap-2">
|
<div class="mt-3 d-flex gap-2">
|
||||||
<button class="btn btn-primary btn-sm" @onclick="SaveStockAsync" disabled="@savingStock">
|
<button class="btn btn-primary btn-sm" @onclick="SaveOfferingAsync" disabled="@savingOffering">
|
||||||
@if (savingStock)
|
@if (savingOffering)
|
||||||
{
|
{
|
||||||
<span class="spinner-border spinner-border-sm me-1"></span>
|
<span class="spinner-border spinner-border-sm me-1"></span>
|
||||||
}
|
}
|
||||||
@(editingStock == null ? "Add" : "Save")
|
@(editingOffering == null ? "Add" : "Save")
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-outline-secondary btn-sm" @onclick="CancelStockForm">Cancel</button>
|
<button class="btn btn-outline-secondary btn-sm" @onclick="CancelOfferingForm">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (stocks.Count == 0)
|
@if (offerings.Count == 0)
|
||||||
{
|
{
|
||||||
<p class="text-muted">No stock lengths configured yet.</p>
|
<p class="text-muted">No offerings configured yet.</p>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<table class="table table-sm">
|
<table class="table table-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Material</th>
|
<th>Stock Item</th>
|
||||||
<th>Length</th>
|
<th>Part #</th>
|
||||||
<th>Price</th>
|
<th>Price</th>
|
||||||
<th style="width: 100px;">Actions</th>
|
<th style="width: 100px;">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@foreach (var stock in stocks)
|
@foreach (var offering in offerings)
|
||||||
{
|
{
|
||||||
<tr>
|
<tr>
|
||||||
<td>@stock.Material.DisplayName</td>
|
<td>@offering.StockItem.Material.DisplayName - @ArchUnits.FormatFromInches((double)offering.StockItem.LengthInches)</td>
|
||||||
<td>@ArchUnits.FormatFromInches((double)stock.LengthInches)</td>
|
<td>@(offering.PartNumber ?? "-")</td>
|
||||||
<td>@(stock.Price.HasValue ? stock.Price.Value.ToString("C") : "-")</td>
|
<td>@(offering.Price.HasValue ? offering.Price.Value.ToString("C") : "-")</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn btn-sm btn-outline-primary" @onclick="() => EditStock(stock)">Edit</button>
|
<button class="btn btn-sm btn-outline-primary" @onclick="() => EditOffering(offering)">Edit</button>
|
||||||
<button class="btn btn-sm btn-outline-danger" @onclick="() => ConfirmDeleteStock(stock)">Delete</button>
|
<button class="btn btn-sm btn-outline-danger" @onclick="() => ConfirmDeleteOffering(offering)">Delete</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
@@ -153,38 +158,44 @@ else
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<ConfirmDialog @ref="deleteStockDialog"
|
<ConfirmDialog @ref="deleteOfferingDialog"
|
||||||
Title="Delete Stock Length"
|
Title="Delete Offering"
|
||||||
Message="@deleteStockMessage"
|
Message="@deleteOfferingMessage"
|
||||||
ConfirmText="Delete"
|
ConfirmText="Delete"
|
||||||
OnConfirm="DeleteStockConfirmed" />
|
OnConfirm="DeleteOfferingConfirmed" />
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public int? Id { get; set; }
|
public int? Id { get; set; }
|
||||||
|
|
||||||
private Supplier supplier = new();
|
private Supplier supplier = new();
|
||||||
private List<SupplierStock> stocks = new();
|
private List<SupplierOffering> offerings = new();
|
||||||
private List<Material> materials = new();
|
private List<StockItem> stockItems = new();
|
||||||
private bool loading = true;
|
private bool loading = true;
|
||||||
private bool savingSupplier;
|
private bool savingSupplier;
|
||||||
private bool savingStock;
|
private bool savingOffering;
|
||||||
private string? supplierErrorMessage;
|
private string? supplierErrorMessage;
|
||||||
private string? stockErrorMessage;
|
private string? offeringErrorMessage;
|
||||||
|
|
||||||
private bool showStockForm;
|
private bool showOfferingForm;
|
||||||
private SupplierStock newStock = new();
|
private SupplierOffering newOffering = new();
|
||||||
private SupplierStock? editingStock;
|
private SupplierOffering? editingOffering;
|
||||||
|
|
||||||
private ConfirmDialog deleteStockDialog = null!;
|
private ConfirmDialog deleteOfferingDialog = null!;
|
||||||
private SupplierStock? stockToDelete;
|
private SupplierOffering? offeringToDelete;
|
||||||
private string deleteStockMessage = "";
|
private string deleteOfferingMessage = "";
|
||||||
|
|
||||||
private bool IsNew => !Id.HasValue;
|
private bool IsNew => !Id.HasValue;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
materials = await MaterialService.GetAllAsync();
|
stockItems = await DbContext.StockItems
|
||||||
|
.Include(si => si.Material)
|
||||||
|
.Where(si => si.IsActive)
|
||||||
|
.OrderBy(si => si.Material.Shape)
|
||||||
|
.ThenBy(si => si.Material.Size)
|
||||||
|
.ThenBy(si => si.LengthInches)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
if (Id.HasValue)
|
if (Id.HasValue)
|
||||||
{
|
{
|
||||||
@@ -195,7 +206,7 @@ else
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
supplier = existing;
|
supplier = existing;
|
||||||
stocks = await SupplierService.GetStocksForSupplierAsync(Id.Value);
|
offerings = await SupplierService.GetOfferingsForSupplierAsync(Id.Value);
|
||||||
}
|
}
|
||||||
loading = false;
|
loading = false;
|
||||||
}
|
}
|
||||||
@@ -229,100 +240,94 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShowAddStockForm()
|
private void ShowAddOfferingForm()
|
||||||
{
|
{
|
||||||
editingStock = null;
|
editingOffering = null;
|
||||||
newStock = new SupplierStock { SupplierId = Id!.Value };
|
newOffering = new SupplierOffering { SupplierId = Id!.Value };
|
||||||
showStockForm = true;
|
showOfferingForm = true;
|
||||||
stockErrorMessage = null;
|
offeringErrorMessage = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EditStock(SupplierStock stock)
|
private void EditOffering(SupplierOffering offering)
|
||||||
{
|
{
|
||||||
editingStock = stock;
|
editingOffering = offering;
|
||||||
newStock = new SupplierStock
|
newOffering = new SupplierOffering
|
||||||
{
|
{
|
||||||
Id = stock.Id,
|
Id = offering.Id,
|
||||||
SupplierId = stock.SupplierId,
|
SupplierId = offering.SupplierId,
|
||||||
MaterialId = stock.MaterialId,
|
StockItemId = offering.StockItemId,
|
||||||
LengthInches = stock.LengthInches,
|
PartNumber = offering.PartNumber,
|
||||||
Price = stock.Price,
|
SupplierDescription = offering.SupplierDescription,
|
||||||
Notes = stock.Notes
|
Price = offering.Price,
|
||||||
|
Notes = offering.Notes
|
||||||
};
|
};
|
||||||
showStockForm = true;
|
showOfferingForm = true;
|
||||||
stockErrorMessage = null;
|
offeringErrorMessage = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CancelStockForm()
|
private void CancelOfferingForm()
|
||||||
{
|
{
|
||||||
showStockForm = false;
|
showOfferingForm = false;
|
||||||
editingStock = null;
|
editingOffering = null;
|
||||||
stockErrorMessage = null;
|
offeringErrorMessage = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveStockAsync()
|
private async Task SaveOfferingAsync()
|
||||||
{
|
{
|
||||||
stockErrorMessage = null;
|
offeringErrorMessage = null;
|
||||||
savingStock = true;
|
savingOffering = true;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (newStock.MaterialId == 0)
|
if (newOffering.StockItemId == 0)
|
||||||
{
|
{
|
||||||
stockErrorMessage = "Please select a material";
|
offeringErrorMessage = "Please select a stock item";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newStock.LengthInches <= 0)
|
var exists = await SupplierService.OfferingExistsAsync(
|
||||||
{
|
newOffering.SupplierId,
|
||||||
stockErrorMessage = "Length must be greater than zero";
|
newOffering.StockItemId,
|
||||||
return;
|
editingOffering?.Id);
|
||||||
}
|
|
||||||
|
|
||||||
var exists = await SupplierService.StockExistsAsync(
|
|
||||||
newStock.SupplierId,
|
|
||||||
newStock.MaterialId,
|
|
||||||
newStock.LengthInches,
|
|
||||||
editingStock?.Id);
|
|
||||||
|
|
||||||
if (exists)
|
if (exists)
|
||||||
{
|
{
|
||||||
stockErrorMessage = "This stock length already exists for this material";
|
offeringErrorMessage = "This supplier already has an offering for this stock item";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (editingStock == null)
|
if (editingOffering == null)
|
||||||
{
|
{
|
||||||
await SupplierService.AddStockAsync(newStock);
|
await SupplierService.AddOfferingAsync(newOffering);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await SupplierService.UpdateStockAsync(newStock);
|
await SupplierService.UpdateOfferingAsync(newOffering);
|
||||||
}
|
}
|
||||||
|
|
||||||
stocks = await SupplierService.GetStocksForSupplierAsync(Id!.Value);
|
offerings = await SupplierService.GetOfferingsForSupplierAsync(Id!.Value);
|
||||||
showStockForm = false;
|
showOfferingForm = false;
|
||||||
editingStock = null;
|
editingOffering = null;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
savingStock = false;
|
savingOffering = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ConfirmDeleteStock(SupplierStock stock)
|
private void ConfirmDeleteOffering(SupplierOffering offering)
|
||||||
{
|
{
|
||||||
stockToDelete = stock;
|
offeringToDelete = offering;
|
||||||
deleteStockMessage = $"Are you sure you want to delete the {ArchUnits.FormatFromInches((double)stock.LengthInches)} stock length?";
|
deleteOfferingMessage = $"Are you sure you want to delete the offering for {offering.StockItem.Material.DisplayName} - {ArchUnits.FormatFromInches((double)offering.StockItem.LengthInches)}?";
|
||||||
deleteStockDialog.Show();
|
deleteOfferingDialog.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DeleteStockConfirmed()
|
private async Task DeleteOfferingConfirmed()
|
||||||
{
|
{
|
||||||
if (stockToDelete != null)
|
if (offeringToDelete != null)
|
||||||
{
|
{
|
||||||
await SupplierService.DeleteStockAsync(stockToDelete.Id);
|
await SupplierService.DeleteOfferingAsync(offeringToDelete.Id);
|
||||||
stocks = await SupplierService.GetStocksForSupplierAsync(Id!.Value);
|
offerings = await SupplierService.GetOfferingsForSupplierAsync(Id!.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ public class ApplicationDbContext : DbContext
|
|||||||
public DbSet<Material> Materials => Set<Material>();
|
public DbSet<Material> Materials => Set<Material>();
|
||||||
public DbSet<MaterialStockLength> MaterialStockLengths => Set<MaterialStockLength>();
|
public DbSet<MaterialStockLength> MaterialStockLengths => Set<MaterialStockLength>();
|
||||||
public DbSet<Supplier> Suppliers => Set<Supplier>();
|
public DbSet<Supplier> Suppliers => Set<Supplier>();
|
||||||
public DbSet<SupplierStock> SupplierStocks => Set<SupplierStock>();
|
public DbSet<StockItem> StockItems => Set<StockItem>();
|
||||||
|
public DbSet<SupplierOffering> SupplierOfferings => Set<SupplierOffering>();
|
||||||
public DbSet<CuttingTool> CuttingTools => Set<CuttingTool>();
|
public DbSet<CuttingTool> CuttingTools => Set<CuttingTool>();
|
||||||
public DbSet<Project> Projects => Set<Project>();
|
public DbSet<Project> Projects => Set<Project>();
|
||||||
public DbSet<ProjectPart> ProjectParts => Set<ProjectPart>();
|
public DbSet<ProjectPart> ProjectParts => Set<ProjectPart>();
|
||||||
@@ -56,25 +57,42 @@ public class ApplicationDbContext : DbContext
|
|||||||
entity.Property(e => e.CreatedAt).HasDefaultValueSql("GETUTCDATE()");
|
entity.Property(e => e.CreatedAt).HasDefaultValueSql("GETUTCDATE()");
|
||||||
});
|
});
|
||||||
|
|
||||||
// SupplierStock
|
// StockItem
|
||||||
modelBuilder.Entity<SupplierStock>(entity =>
|
modelBuilder.Entity<StockItem>(entity =>
|
||||||
{
|
{
|
||||||
entity.HasKey(e => e.Id);
|
entity.HasKey(e => e.Id);
|
||||||
entity.Property(e => e.LengthInches).HasPrecision(10, 4);
|
entity.Property(e => e.LengthInches).HasPrecision(10, 4);
|
||||||
entity.Property(e => e.Price).HasPrecision(10, 2);
|
entity.Property(e => e.Name).HasMaxLength(100);
|
||||||
entity.Property(e => e.Notes).HasMaxLength(255);
|
entity.Property(e => e.CreatedAt).HasDefaultValueSql("GETUTCDATE()");
|
||||||
|
|
||||||
entity.HasOne(e => e.Supplier)
|
|
||||||
.WithMany(s => s.Stocks)
|
|
||||||
.HasForeignKey(e => e.SupplierId)
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
|
|
||||||
entity.HasOne(e => e.Material)
|
entity.HasOne(e => e.Material)
|
||||||
.WithMany(m => m.SupplierStocks)
|
.WithMany(m => m.StockItems)
|
||||||
.HasForeignKey(e => e.MaterialId)
|
.HasForeignKey(e => e.MaterialId)
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
entity.HasIndex(e => new { e.SupplierId, e.MaterialId, e.LengthInches }).IsUnique();
|
entity.HasIndex(e => new { e.MaterialId, e.LengthInches }).IsUnique();
|
||||||
|
});
|
||||||
|
|
||||||
|
// SupplierOffering
|
||||||
|
modelBuilder.Entity<SupplierOffering>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(e => e.Id);
|
||||||
|
entity.Property(e => e.PartNumber).HasMaxLength(100);
|
||||||
|
entity.Property(e => e.SupplierDescription).HasMaxLength(255);
|
||||||
|
entity.Property(e => e.Price).HasPrecision(10, 2);
|
||||||
|
entity.Property(e => e.Notes).HasMaxLength(255);
|
||||||
|
|
||||||
|
entity.HasOne(e => e.StockItem)
|
||||||
|
.WithMany(s => s.SupplierOfferings)
|
||||||
|
.HasForeignKey(e => e.StockItemId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
entity.HasOne(e => e.Supplier)
|
||||||
|
.WithMany(s => s.Offerings)
|
||||||
|
.HasForeignKey(e => e.SupplierId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
entity.HasIndex(e => new { e.SupplierId, e.StockItemId }).IsUnique();
|
||||||
});
|
});
|
||||||
|
|
||||||
// CuttingTool
|
// CuttingTool
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ public class Material
|
|||||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
public DateTime? UpdatedAt { get; set; }
|
public DateTime? UpdatedAt { get; set; }
|
||||||
|
|
||||||
public ICollection<SupplierStock> SupplierStocks { get; set; } = new List<SupplierStock>();
|
public ICollection<StockItem> StockItems { get; set; } = new List<StockItem>();
|
||||||
public ICollection<MaterialStockLength> StockLengths { get; set; } = new List<MaterialStockLength>();
|
public ICollection<MaterialStockLength> StockLengths { get; set; } = new List<MaterialStockLength>();
|
||||||
public ICollection<ProjectPart> ProjectParts { get; set; } = new List<ProjectPart>();
|
public ICollection<ProjectPart> ProjectParts { get; set; } = new List<ProjectPart>();
|
||||||
|
|
||||||
|
|||||||
15
CutList.Web/Data/Entities/StockItem.cs
Normal file
15
CutList.Web/Data/Entities/StockItem.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
namespace CutList.Web.Data.Entities;
|
||||||
|
|
||||||
|
public class StockItem
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int MaterialId { get; set; }
|
||||||
|
public decimal LengthInches { get; set; }
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public bool IsActive { get; set; } = true;
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
public DateTime? UpdatedAt { get; set; }
|
||||||
|
|
||||||
|
public Material Material { get; set; } = null!;
|
||||||
|
public ICollection<SupplierOffering> SupplierOfferings { get; set; } = new List<SupplierOffering>();
|
||||||
|
}
|
||||||
@@ -9,5 +9,5 @@ public class Supplier
|
|||||||
public bool IsActive { get; set; } = true;
|
public bool IsActive { get; set; } = true;
|
||||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
public ICollection<SupplierStock> Stocks { get; set; } = new List<SupplierStock>();
|
public ICollection<SupplierOffering> Offerings { get; set; } = new List<SupplierOffering>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
namespace CutList.Web.Data.Entities;
|
namespace CutList.Web.Data.Entities;
|
||||||
|
|
||||||
public class SupplierStock
|
public class SupplierOffering
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
public int StockItemId { get; set; }
|
||||||
public int SupplierId { get; set; }
|
public int SupplierId { get; set; }
|
||||||
public int MaterialId { get; set; }
|
public string? PartNumber { get; set; }
|
||||||
public decimal LengthInches { get; set; }
|
public string? SupplierDescription { get; set; }
|
||||||
public decimal? Price { get; set; }
|
public decimal? Price { get; set; }
|
||||||
public string? Notes { get; set; }
|
public string? Notes { get; set; }
|
||||||
public bool IsActive { get; set; } = true;
|
public bool IsActive { get; set; } = true;
|
||||||
|
|
||||||
|
public StockItem StockItem { get; set; } = null!;
|
||||||
public Supplier Supplier { get; set; } = null!;
|
public Supplier Supplier { get; set; } = null!;
|
||||||
public Material Material { get; set; } = null!;
|
|
||||||
}
|
}
|
||||||
452
CutList.Web/Migrations/20260203032915_AddStockItemAndSupplierOffering.Designer.cs
generated
Normal file
452
CutList.Web/Migrations/20260203032915_AddStockItemAndSupplierOffering.Designer.cs
generated
Normal file
@@ -0,0 +1,452 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using CutList.Web.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace CutList.Web.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(ApplicationDbContext))]
|
||||||
|
[Migration("20260203032915_AddStockItemAndSupplierOffering")]
|
||||||
|
partial class AddStockItemAndSupplierOffering
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "8.0.11")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||||
|
|
||||||
|
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("CutList.Web.Data.Entities.CuttingTool", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDefault")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<decimal>("KerfInches")
|
||||||
|
.HasPrecision(6, 4)
|
||||||
|
.HasColumnType("decimal(6,4)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("CuttingTools");
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
IsActive = true,
|
||||||
|
IsDefault = true,
|
||||||
|
KerfInches = 0.0625m,
|
||||||
|
Name = "Bandsaw"
|
||||||
|
},
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
IsActive = true,
|
||||||
|
IsDefault = false,
|
||||||
|
KerfInches = 0.125m,
|
||||||
|
Name = "Chop Saw"
|
||||||
|
},
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 3,
|
||||||
|
IsActive = true,
|
||||||
|
IsDefault = false,
|
||||||
|
KerfInches = 0.0625m,
|
||||||
|
Name = "Cold Cut Saw"
|
||||||
|
},
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 4,
|
||||||
|
IsActive = true,
|
||||||
|
IsDefault = false,
|
||||||
|
KerfInches = 0.0625m,
|
||||||
|
Name = "Hacksaw"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CutList.Web.Data.Entities.Material", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasDefaultValueSql("GETUTCDATE()");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("nvarchar(255)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<string>("Shape")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<string>("Size")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Materials");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CutList.Web.Data.Entities.MaterialStockLength", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<decimal>("LengthInches")
|
||||||
|
.HasPrecision(10, 4)
|
||||||
|
.HasColumnType("decimal(10,4)");
|
||||||
|
|
||||||
|
b.Property<int>("MaterialId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Notes")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("nvarchar(255)");
|
||||||
|
|
||||||
|
b.Property<int>("Quantity")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("MaterialId", "LengthInches")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("MaterialStockLengths");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CutList.Web.Data.Entities.Project", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasDefaultValueSql("GETUTCDATE()");
|
||||||
|
|
||||||
|
b.Property<string>("Customer")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<int?>("CuttingToolId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<string>("Notes")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CuttingToolId");
|
||||||
|
|
||||||
|
b.ToTable("Projects");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CutList.Web.Data.Entities.ProjectPart", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("LengthInches")
|
||||||
|
.HasPrecision(10, 4)
|
||||||
|
.HasColumnType("decimal(10,4)");
|
||||||
|
|
||||||
|
b.Property<int>("MaterialId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<int>("ProjectId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("Quantity")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("SortOrder")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("MaterialId");
|
||||||
|
|
||||||
|
b.HasIndex("ProjectId");
|
||||||
|
|
||||||
|
b.ToTable("ProjectParts");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CutList.Web.Data.Entities.StockItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasDefaultValueSql("GETUTCDATE()");
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<decimal>("LengthInches")
|
||||||
|
.HasPrecision(10, 4)
|
||||||
|
.HasColumnType("decimal(10,4)");
|
||||||
|
|
||||||
|
b.Property<int>("MaterialId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("MaterialId", "LengthInches")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("StockItems");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CutList.Web.Data.Entities.Supplier", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("ContactInfo")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("nvarchar(500)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasDefaultValueSql("GETUTCDATE()");
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<string>("Notes")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Suppliers");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CutList.Web.Data.Entities.SupplierOffering", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<string>("Notes")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("nvarchar(255)");
|
||||||
|
|
||||||
|
b.Property<string>("PartNumber")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<decimal?>("Price")
|
||||||
|
.HasPrecision(10, 2)
|
||||||
|
.HasColumnType("decimal(10,2)");
|
||||||
|
|
||||||
|
b.Property<int>("StockItemId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("SupplierDescription")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("nvarchar(255)");
|
||||||
|
|
||||||
|
b.Property<int>("SupplierId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("StockItemId");
|
||||||
|
|
||||||
|
b.HasIndex("SupplierId", "StockItemId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("SupplierOfferings");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CutList.Web.Data.Entities.MaterialStockLength", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("CutList.Web.Data.Entities.Material", "Material")
|
||||||
|
.WithMany("StockLengths")
|
||||||
|
.HasForeignKey("MaterialId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Material");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CutList.Web.Data.Entities.Project", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("CutList.Web.Data.Entities.CuttingTool", "CuttingTool")
|
||||||
|
.WithMany("Projects")
|
||||||
|
.HasForeignKey("CuttingToolId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.Navigation("CuttingTool");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CutList.Web.Data.Entities.ProjectPart", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("CutList.Web.Data.Entities.Material", "Material")
|
||||||
|
.WithMany("ProjectParts")
|
||||||
|
.HasForeignKey("MaterialId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("CutList.Web.Data.Entities.Project", "Project")
|
||||||
|
.WithMany("Parts")
|
||||||
|
.HasForeignKey("ProjectId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Material");
|
||||||
|
|
||||||
|
b.Navigation("Project");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CutList.Web.Data.Entities.StockItem", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("CutList.Web.Data.Entities.Material", "Material")
|
||||||
|
.WithMany("StockItems")
|
||||||
|
.HasForeignKey("MaterialId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Material");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CutList.Web.Data.Entities.SupplierOffering", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("CutList.Web.Data.Entities.StockItem", "StockItem")
|
||||||
|
.WithMany("SupplierOfferings")
|
||||||
|
.HasForeignKey("StockItemId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("CutList.Web.Data.Entities.Supplier", "Supplier")
|
||||||
|
.WithMany("Offerings")
|
||||||
|
.HasForeignKey("SupplierId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("StockItem");
|
||||||
|
|
||||||
|
b.Navigation("Supplier");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CutList.Web.Data.Entities.CuttingTool", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Projects");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CutList.Web.Data.Entities.Material", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("ProjectParts");
|
||||||
|
|
||||||
|
b.Navigation("StockItems");
|
||||||
|
|
||||||
|
b.Navigation("StockLengths");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CutList.Web.Data.Entities.Project", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Parts");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CutList.Web.Data.Entities.StockItem", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("SupplierOfferings");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CutList.Web.Data.Entities.Supplier", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Offerings");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace CutList.Web.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddStockItemAndSupplierOffering : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "SupplierStocks");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "StockItems",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("SqlServer:Identity", "1, 1"),
|
||||||
|
MaterialId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
LengthInches = table.Column<decimal>(type: "decimal(10,4)", precision: 10, scale: 4, nullable: false),
|
||||||
|
Name = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||||
|
IsActive = table.Column<bool>(type: "bit", nullable: false),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false, defaultValueSql: "GETUTCDATE()"),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_StockItems", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_StockItems_Materials_MaterialId",
|
||||||
|
column: x => x.MaterialId,
|
||||||
|
principalTable: "Materials",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "SupplierOfferings",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("SqlServer:Identity", "1, 1"),
|
||||||
|
StockItemId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
SupplierId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
PartNumber = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||||
|
SupplierDescription = table.Column<string>(type: "nvarchar(255)", maxLength: 255, nullable: true),
|
||||||
|
Price = table.Column<decimal>(type: "decimal(10,2)", precision: 10, scale: 2, nullable: true),
|
||||||
|
Notes = table.Column<string>(type: "nvarchar(255)", maxLength: 255, nullable: true),
|
||||||
|
IsActive = table.Column<bool>(type: "bit", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_SupplierOfferings", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_SupplierOfferings_StockItems_StockItemId",
|
||||||
|
column: x => x.StockItemId,
|
||||||
|
principalTable: "StockItems",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_SupplierOfferings_Suppliers_SupplierId",
|
||||||
|
column: x => x.SupplierId,
|
||||||
|
principalTable: "Suppliers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_StockItems_MaterialId_LengthInches",
|
||||||
|
table: "StockItems",
|
||||||
|
columns: new[] { "MaterialId", "LengthInches" },
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_SupplierOfferings_StockItemId",
|
||||||
|
table: "SupplierOfferings",
|
||||||
|
column: "StockItemId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_SupplierOfferings_SupplierId_StockItemId",
|
||||||
|
table: "SupplierOfferings",
|
||||||
|
columns: new[] { "SupplierId", "StockItemId" },
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "SupplierOfferings");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "StockItems");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "SupplierStocks",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("SqlServer:Identity", "1, 1"),
|
||||||
|
MaterialId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
SupplierId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
IsActive = table.Column<bool>(type: "bit", nullable: false),
|
||||||
|
LengthInches = table.Column<decimal>(type: "decimal(10,4)", precision: 10, scale: 4, nullable: false),
|
||||||
|
Notes = table.Column<string>(type: "nvarchar(255)", maxLength: 255, nullable: true),
|
||||||
|
Price = table.Column<decimal>(type: "decimal(10,2)", precision: 10, scale: 2, nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_SupplierStocks", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_SupplierStocks_Materials_MaterialId",
|
||||||
|
column: x => x.MaterialId,
|
||||||
|
principalTable: "Materials",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_SupplierStocks_Suppliers_SupplierId",
|
||||||
|
column: x => x.SupplierId,
|
||||||
|
principalTable: "Suppliers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_SupplierStocks_MaterialId",
|
||||||
|
table: "SupplierStocks",
|
||||||
|
column: "MaterialId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_SupplierStocks_SupplierId_MaterialId_LengthInches",
|
||||||
|
table: "SupplierStocks",
|
||||||
|
columns: new[] { "SupplierId", "MaterialId", "LengthInches" },
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -231,6 +231,44 @@ namespace CutList.Web.Migrations
|
|||||||
b.ToTable("ProjectParts");
|
b.ToTable("ProjectParts");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CutList.Web.Data.Entities.StockItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasDefaultValueSql("GETUTCDATE()");
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<decimal>("LengthInches")
|
||||||
|
.HasPrecision(10, 4)
|
||||||
|
.HasColumnType("decimal(10,4)");
|
||||||
|
|
||||||
|
b.Property<int>("MaterialId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("MaterialId", "LengthInches")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("StockItems");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("CutList.Web.Data.Entities.Supplier", b =>
|
modelBuilder.Entity("CutList.Web.Data.Entities.Supplier", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -264,7 +302,7 @@ namespace CutList.Web.Migrations
|
|||||||
b.ToTable("Suppliers");
|
b.ToTable("Suppliers");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("CutList.Web.Data.Entities.SupplierStock", b =>
|
modelBuilder.Entity("CutList.Web.Data.Entities.SupplierOffering", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -275,32 +313,36 @@ namespace CutList.Web.Migrations
|
|||||||
b.Property<bool>("IsActive")
|
b.Property<bool>("IsActive")
|
||||||
.HasColumnType("bit");
|
.HasColumnType("bit");
|
||||||
|
|
||||||
b.Property<decimal>("LengthInches")
|
|
||||||
.HasPrecision(10, 4)
|
|
||||||
.HasColumnType("decimal(10,4)");
|
|
||||||
|
|
||||||
b.Property<int>("MaterialId")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<string>("Notes")
|
b.Property<string>("Notes")
|
||||||
.HasMaxLength(255)
|
.HasMaxLength(255)
|
||||||
.HasColumnType("nvarchar(255)");
|
.HasColumnType("nvarchar(255)");
|
||||||
|
|
||||||
|
b.Property<string>("PartNumber")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
b.Property<decimal?>("Price")
|
b.Property<decimal?>("Price")
|
||||||
.HasPrecision(10, 2)
|
.HasPrecision(10, 2)
|
||||||
.HasColumnType("decimal(10,2)");
|
.HasColumnType("decimal(10,2)");
|
||||||
|
|
||||||
|
b.Property<int>("StockItemId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("SupplierDescription")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("nvarchar(255)");
|
||||||
|
|
||||||
b.Property<int>("SupplierId")
|
b.Property<int>("SupplierId")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("MaterialId");
|
b.HasIndex("StockItemId");
|
||||||
|
|
||||||
b.HasIndex("SupplierId", "MaterialId", "LengthInches")
|
b.HasIndex("SupplierId", "StockItemId")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
b.ToTable("SupplierStocks");
|
b.ToTable("SupplierOfferings");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("CutList.Web.Data.Entities.MaterialStockLength", b =>
|
modelBuilder.Entity("CutList.Web.Data.Entities.MaterialStockLength", b =>
|
||||||
@@ -343,21 +385,32 @@ namespace CutList.Web.Migrations
|
|||||||
b.Navigation("Project");
|
b.Navigation("Project");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("CutList.Web.Data.Entities.SupplierStock", b =>
|
modelBuilder.Entity("CutList.Web.Data.Entities.StockItem", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("CutList.Web.Data.Entities.Material", "Material")
|
b.HasOne("CutList.Web.Data.Entities.Material", "Material")
|
||||||
.WithMany("SupplierStocks")
|
.WithMany("StockItems")
|
||||||
.HasForeignKey("MaterialId")
|
.HasForeignKey("MaterialId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Material");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CutList.Web.Data.Entities.SupplierOffering", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("CutList.Web.Data.Entities.StockItem", "StockItem")
|
||||||
|
.WithMany("SupplierOfferings")
|
||||||
|
.HasForeignKey("StockItemId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("CutList.Web.Data.Entities.Supplier", "Supplier")
|
b.HasOne("CutList.Web.Data.Entities.Supplier", "Supplier")
|
||||||
.WithMany("Stocks")
|
.WithMany("Offerings")
|
||||||
.HasForeignKey("SupplierId")
|
.HasForeignKey("SupplierId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.Navigation("Material");
|
b.Navigation("StockItem");
|
||||||
|
|
||||||
b.Navigation("Supplier");
|
b.Navigation("Supplier");
|
||||||
});
|
});
|
||||||
@@ -371,9 +424,9 @@ namespace CutList.Web.Migrations
|
|||||||
{
|
{
|
||||||
b.Navigation("ProjectParts");
|
b.Navigation("ProjectParts");
|
||||||
|
|
||||||
b.Navigation("StockLengths");
|
b.Navigation("StockItems");
|
||||||
|
|
||||||
b.Navigation("SupplierStocks");
|
b.Navigation("StockLengths");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("CutList.Web.Data.Entities.Project", b =>
|
modelBuilder.Entity("CutList.Web.Data.Entities.Project", b =>
|
||||||
@@ -381,9 +434,14 @@ namespace CutList.Web.Migrations
|
|||||||
b.Navigation("Parts");
|
b.Navigation("Parts");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CutList.Web.Data.Entities.StockItem", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("SupplierOfferings");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("CutList.Web.Data.Entities.Supplier", b =>
|
modelBuilder.Entity("CutList.Web.Data.Entities.Supplier", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Stocks");
|
b.Navigation("Offerings");
|
||||||
});
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ builder.Services.AddDbContext<ApplicationDbContext>(options =>
|
|||||||
// Add application services
|
// Add application services
|
||||||
builder.Services.AddScoped<MaterialService>();
|
builder.Services.AddScoped<MaterialService>();
|
||||||
builder.Services.AddScoped<SupplierService>();
|
builder.Services.AddScoped<SupplierService>();
|
||||||
|
builder.Services.AddScoped<StockItemService>();
|
||||||
builder.Services.AddScoped<ProjectService>();
|
builder.Services.AddScoped<ProjectService>();
|
||||||
builder.Services.AddScoped<CutListPackingService>();
|
builder.Services.AddScoped<CutListPackingService>();
|
||||||
builder.Services.AddScoped<ReportService>();
|
builder.Services.AddScoped<ReportService>();
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ public class CutListPackingService
|
|||||||
.Where(s => s.MaterialId == materialId && s.IsActive && s.Quantity > 0)
|
.Where(s => s.MaterialId == materialId && s.IsActive && s.Quantity > 0)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
// Get supplier stock lengths for this material (for purchase)
|
// Get stock item lengths for this material (for purchase)
|
||||||
var supplierLengths = await _context.SupplierStocks
|
var stockItemLengths = await _context.StockItems
|
||||||
.Where(s => s.MaterialId == materialId && s.IsActive)
|
.Where(s => s.MaterialId == materialId && s.IsActive)
|
||||||
.Select(s => s.LengthInches)
|
.Select(s => s.LengthInches)
|
||||||
.Distinct()
|
.Distinct()
|
||||||
@@ -60,8 +60,8 @@ public class CutListPackingService
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Supplier stock bins with unlimited quantity
|
// Stock item bins with unlimited quantity
|
||||||
foreach (var length in supplierLengths)
|
foreach (var length in stockItemLengths)
|
||||||
{
|
{
|
||||||
// Only add if not already covered by in-stock
|
// Only add if not already covered by in-stock
|
||||||
if (!stockBins.Any(b => b.LengthInches == length && b.IsInStock))
|
if (!stockBins.Any(b => b.LengthInches == length && b.IsInStock))
|
||||||
|
|||||||
99
CutList.Web/Services/StockItemService.cs
Normal file
99
CutList.Web/Services/StockItemService.cs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
using CutList.Web.Data;
|
||||||
|
using CutList.Web.Data.Entities;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace CutList.Web.Services;
|
||||||
|
|
||||||
|
public class StockItemService
|
||||||
|
{
|
||||||
|
private readonly ApplicationDbContext _context;
|
||||||
|
|
||||||
|
public StockItemService(ApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<StockItem>> GetAllAsync(bool includeInactive = false)
|
||||||
|
{
|
||||||
|
var query = _context.StockItems
|
||||||
|
.Include(s => s.Material)
|
||||||
|
.AsQueryable();
|
||||||
|
|
||||||
|
if (!includeInactive)
|
||||||
|
{
|
||||||
|
query = query.Where(s => s.IsActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await query
|
||||||
|
.OrderBy(s => s.Material.Shape)
|
||||||
|
.ThenBy(s => s.Material.Size)
|
||||||
|
.ThenBy(s => s.LengthInches)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<StockItem>> GetByMaterialAsync(int materialId, bool includeInactive = false)
|
||||||
|
{
|
||||||
|
var query = _context.StockItems
|
||||||
|
.Include(s => s.Material)
|
||||||
|
.Where(s => s.MaterialId == materialId);
|
||||||
|
|
||||||
|
if (!includeInactive)
|
||||||
|
{
|
||||||
|
query = query.Where(s => s.IsActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await query
|
||||||
|
.OrderBy(s => s.LengthInches)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<StockItem?> GetByIdAsync(int id)
|
||||||
|
{
|
||||||
|
return await _context.StockItems
|
||||||
|
.Include(s => s.Material)
|
||||||
|
.Include(s => s.SupplierOfferings)
|
||||||
|
.ThenInclude(o => o.Supplier)
|
||||||
|
.FirstOrDefaultAsync(s => s.Id == id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<StockItem> CreateAsync(StockItem stockItem)
|
||||||
|
{
|
||||||
|
stockItem.CreatedAt = DateTime.UtcNow;
|
||||||
|
_context.StockItems.Add(stockItem);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
return stockItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateAsync(StockItem stockItem)
|
||||||
|
{
|
||||||
|
stockItem.UpdatedAt = DateTime.UtcNow;
|
||||||
|
_context.StockItems.Update(stockItem);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteAsync(int id)
|
||||||
|
{
|
||||||
|
var stockItem = await _context.StockItems.FindAsync(id);
|
||||||
|
if (stockItem != null)
|
||||||
|
{
|
||||||
|
stockItem.IsActive = false;
|
||||||
|
stockItem.UpdatedAt = DateTime.UtcNow;
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ExistsAsync(int materialId, decimal lengthInches, int? excludeId = null)
|
||||||
|
{
|
||||||
|
var query = _context.StockItems.Where(s =>
|
||||||
|
s.MaterialId == materialId &&
|
||||||
|
s.LengthInches == lengthInches &&
|
||||||
|
s.IsActive);
|
||||||
|
|
||||||
|
if (excludeId.HasValue)
|
||||||
|
{
|
||||||
|
query = query.Where(s => s.Id != excludeId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await query.AnyAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,8 +26,9 @@ public class SupplierService
|
|||||||
public async Task<Supplier?> GetByIdAsync(int id)
|
public async Task<Supplier?> GetByIdAsync(int id)
|
||||||
{
|
{
|
||||||
return await _context.Suppliers
|
return await _context.Suppliers
|
||||||
.Include(s => s.Stocks)
|
.Include(s => s.Offerings)
|
||||||
.ThenInclude(st => st.Material)
|
.ThenInclude(o => o.StockItem)
|
||||||
|
.ThenInclude(si => si.Material)
|
||||||
.FirstOrDefaultAsync(s => s.Id == id);
|
.FirstOrDefaultAsync(s => s.Id == id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,70 +56,70 @@ public class SupplierService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stock management
|
// Offering management
|
||||||
public async Task<List<SupplierStock>> GetStocksForSupplierAsync(int supplierId)
|
public async Task<List<SupplierOffering>> GetOfferingsForSupplierAsync(int supplierId)
|
||||||
{
|
{
|
||||||
return await _context.SupplierStocks
|
return await _context.SupplierOfferings
|
||||||
.Include(s => s.Material)
|
.Include(o => o.StockItem)
|
||||||
.Where(s => s.SupplierId == supplierId && s.IsActive)
|
.ThenInclude(si => si.Material)
|
||||||
.OrderBy(s => s.Material.Shape)
|
.Where(o => o.SupplierId == supplierId && o.IsActive)
|
||||||
.ThenBy(s => s.Material.Size)
|
.OrderBy(o => o.StockItem.Material.Shape)
|
||||||
.ThenBy(s => s.LengthInches)
|
.ThenBy(o => o.StockItem.Material.Size)
|
||||||
|
.ThenBy(o => o.StockItem.LengthInches)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<SupplierStock>> GetStocksForMaterialAsync(int materialId)
|
public async Task<List<SupplierOffering>> GetOfferingsForStockItemAsync(int stockItemId)
|
||||||
{
|
{
|
||||||
return await _context.SupplierStocks
|
return await _context.SupplierOfferings
|
||||||
.Include(s => s.Supplier)
|
.Include(o => o.Supplier)
|
||||||
.Where(s => s.MaterialId == materialId && s.IsActive && s.Supplier.IsActive)
|
.Where(o => o.StockItemId == stockItemId && o.IsActive && o.Supplier.IsActive)
|
||||||
.OrderBy(s => s.Supplier.Name)
|
.OrderBy(o => o.Supplier.Name)
|
||||||
.ThenBy(s => s.LengthInches)
|
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SupplierStock?> GetStockByIdAsync(int id)
|
public async Task<SupplierOffering?> GetOfferingByIdAsync(int id)
|
||||||
{
|
{
|
||||||
return await _context.SupplierStocks
|
return await _context.SupplierOfferings
|
||||||
.Include(s => s.Material)
|
.Include(o => o.StockItem)
|
||||||
.Include(s => s.Supplier)
|
.ThenInclude(si => si.Material)
|
||||||
.FirstOrDefaultAsync(s => s.Id == id);
|
.Include(o => o.Supplier)
|
||||||
|
.FirstOrDefaultAsync(o => o.Id == id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SupplierStock> AddStockAsync(SupplierStock stock)
|
public async Task<SupplierOffering> AddOfferingAsync(SupplierOffering offering)
|
||||||
{
|
{
|
||||||
_context.SupplierStocks.Add(stock);
|
_context.SupplierOfferings.Add(offering);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
return stock;
|
return offering;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateStockAsync(SupplierStock stock)
|
public async Task UpdateOfferingAsync(SupplierOffering offering)
|
||||||
{
|
{
|
||||||
_context.SupplierStocks.Update(stock);
|
_context.SupplierOfferings.Update(offering);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteStockAsync(int id)
|
public async Task DeleteOfferingAsync(int id)
|
||||||
{
|
{
|
||||||
var stock = await _context.SupplierStocks.FindAsync(id);
|
var offering = await _context.SupplierOfferings.FindAsync(id);
|
||||||
if (stock != null)
|
if (offering != null)
|
||||||
{
|
{
|
||||||
stock.IsActive = false;
|
offering.IsActive = false;
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> StockExistsAsync(int supplierId, int materialId, decimal lengthInches, int? excludeId = null)
|
public async Task<bool> OfferingExistsAsync(int supplierId, int stockItemId, int? excludeId = null)
|
||||||
{
|
{
|
||||||
var query = _context.SupplierStocks.Where(s =>
|
var query = _context.SupplierOfferings.Where(o =>
|
||||||
s.SupplierId == supplierId &&
|
o.SupplierId == supplierId &&
|
||||||
s.MaterialId == materialId &&
|
o.StockItemId == stockItemId &&
|
||||||
s.LengthInches == lengthInches &&
|
o.IsActive);
|
||||||
s.IsActive);
|
|
||||||
|
|
||||||
if (excludeId.HasValue)
|
if (excludeId.HasValue)
|
||||||
{
|
{
|
||||||
query = query.Where(s => s.Id != excludeId.Value);
|
query = query.Where(o => o.Id != excludeId.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await query.AnyAsync();
|
return await query.AnyAsync();
|
||||||
|
|||||||
Reference in New Issue
Block a user