refactor: migrate to IDbContextFactory and Windows Service hosting

Replace AddDbContext with AddDbContextFactory for Blazor Server circuit
safety — each service method now creates a short-lived DbContext via the
factory. Configure Windows Service hosting with ContentRootPath and
remove HSTS/HTTPS redirect for reverse-proxy deployment.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
aj
2026-03-12 13:48:30 -04:00
co-authored by Claude Opus 4.6
parent f66fd3eaa7
commit b5bef23c1d
8 changed files with 352 additions and 226 deletions
+9 -5
View File
@@ -3,7 +3,13 @@ using CutList.Web.Data;
using CutList.Web.Services;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
ContentRootPath = AppContext.BaseDirectory
});
builder.Host.UseWindowsService();
// Add services to the container.
builder.Services.AddControllers();
@@ -13,8 +19,8 @@ builder.Services.AddRazorComponents()
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Add Entity Framework
builder.Services.AddDbContext<ApplicationDbContext>(options =>
// Add Entity Framework (factory pattern for Blazor Server circuit safety)
builder.Services.AddDbContextFactory<ApplicationDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// Add application services
@@ -38,10 +44,8 @@ if (app.Environment.IsDevelopment())
else
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAntiforgery();
+43 -40
View File
@@ -7,30 +7,32 @@ namespace CutList.Web.Services;
public class CatalogService
{
private readonly ApplicationDbContext _context;
private readonly IDbContextFactory<ApplicationDbContext> _factory;
private readonly MaterialService _materialService;
public CatalogService(ApplicationDbContext context, MaterialService materialService)
public CatalogService(IDbContextFactory<ApplicationDbContext> factory, MaterialService materialService)
{
_context = context;
_factory = factory;
_materialService = materialService;
}
public async Task<CatalogData> ExportAsync()
{
var suppliers = await _context.Suppliers
await using var context = _factory.CreateDbContext();
var suppliers = await context.Suppliers
.Where(s => s.IsActive)
.OrderBy(s => s.Name)
.AsNoTracking()
.ToListAsync();
var cuttingTools = await _context.CuttingTools
var cuttingTools = await context.CuttingTools
.Where(t => t.IsActive)
.OrderBy(t => t.Name)
.AsNoTracking()
.ToListAsync();
var materials = await _context.Materials
var materials = await context.Materials
.Include(m => m.Dimensions)
.Include(m => m.StockItems.Where(s => s.IsActive))
.ThenInclude(s => s.SupplierOfferings.Where(o => o.IsActive))
@@ -157,18 +159,19 @@ public class CatalogService
{
var result = new ImportResultDto();
await using var transaction = await _context.Database.BeginTransactionAsync();
await using var context = _factory.CreateDbContext();
await using var transaction = await context.Database.BeginTransactionAsync();
try
{
// 1. Suppliers - upsert by name
var supplierMap = await ImportSuppliersAsync(data.Suppliers, result);
var supplierMap = await ImportSuppliersAsync(context, data.Suppliers, result);
// 2. Cutting tools - upsert by name
await ImportCuttingToolsAsync(data.CuttingTools, result);
await ImportCuttingToolsAsync(context, data.CuttingTools, result);
// 3. Materials + stock items + offerings
await ImportAllMaterialsAsync(data.Materials, supplierMap, result);
await ImportAllMaterialsAsync(context, data.Materials, supplierMap, result);
await transaction.CommitAsync();
}
@@ -182,11 +185,11 @@ public class CatalogService
}
private async Task<Dictionary<string, int>> ImportSuppliersAsync(
List<CatalogSupplierDto> suppliers, ImportResultDto result)
ApplicationDbContext context, List<CatalogSupplierDto> suppliers, ImportResultDto result)
{
var map = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
var existingSuppliers = await _context.Suppliers.ToListAsync();
var existingSuppliers = await context.Suppliers.ToListAsync();
foreach (var dto in suppliers)
{
@@ -212,8 +215,8 @@ public class CatalogService
Notes = dto.Notes,
CreatedAt = DateTime.UtcNow
};
_context.Suppliers.Add(supplier);
await _context.SaveChangesAsync();
context.Suppliers.Add(supplier);
await context.SaveChangesAsync();
existingSuppliers.Add(supplier);
map[dto.Name] = supplier.Id;
result.SuppliersCreated++;
@@ -225,14 +228,14 @@ public class CatalogService
}
}
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
return map;
}
private async Task ImportCuttingToolsAsync(
List<CatalogCuttingToolDto> tools, ImportResultDto result)
ApplicationDbContext context, List<CatalogCuttingToolDto> tools, ImportResultDto result)
{
var existingTools = await _context.CuttingTools.ToListAsync();
var existingTools = await context.CuttingTools.ToListAsync();
foreach (var dto in tools)
{
@@ -255,7 +258,7 @@ public class CatalogService
KerfInches = dto.KerfInches,
IsDefault = false
};
_context.CuttingTools.Add(tool);
context.CuttingTools.Add(tool);
existingTools.Add(tool);
result.CuttingToolsCreated++;
}
@@ -266,71 +269,71 @@ public class CatalogService
}
}
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
}
private async Task ImportAllMaterialsAsync(
CatalogMaterialsDto materials, Dictionary<string, int> supplierMap, ImportResultDto result)
ApplicationDbContext context, CatalogMaterialsDto materials, Dictionary<string, int> supplierMap, ImportResultDto result)
{
var existingMaterials = await _context.Materials
var existingMaterials = await context.Materials
.Include(m => m.Dimensions)
.Include(m => m.StockItems)
.ThenInclude(s => s.SupplierOfferings)
.ToListAsync();
foreach (var dto in materials.Angles)
await ImportMaterialAsync(dto, MaterialShape.Angle, existingMaterials, supplierMap, result,
await ImportMaterialAsync(context, dto, MaterialShape.Angle, existingMaterials, supplierMap, result,
() => new AngleDimensions { Leg1 = dto.Leg1, Leg2 = dto.Leg2, Thickness = dto.Thickness },
dim => { var d = (AngleDimensions)dim; d.Leg1 = dto.Leg1; d.Leg2 = dto.Leg2; d.Thickness = dto.Thickness; });
foreach (var dto in materials.Channels)
await ImportMaterialAsync(dto, MaterialShape.Channel, existingMaterials, supplierMap, result,
await ImportMaterialAsync(context, dto, MaterialShape.Channel, existingMaterials, supplierMap, result,
() => new ChannelDimensions { Height = dto.Height, Flange = dto.Flange, Web = dto.Web },
dim => { var d = (ChannelDimensions)dim; d.Height = dto.Height; d.Flange = dto.Flange; d.Web = dto.Web; });
foreach (var dto in materials.FlatBars)
await ImportMaterialAsync(dto, MaterialShape.FlatBar, existingMaterials, supplierMap, result,
await ImportMaterialAsync(context, dto, MaterialShape.FlatBar, existingMaterials, supplierMap, result,
() => new FlatBarDimensions { Width = dto.Width, Thickness = dto.Thickness },
dim => { var d = (FlatBarDimensions)dim; d.Width = dto.Width; d.Thickness = dto.Thickness; });
foreach (var dto in materials.IBeams)
await ImportMaterialAsync(dto, MaterialShape.IBeam, existingMaterials, supplierMap, result,
await ImportMaterialAsync(context, dto, MaterialShape.IBeam, existingMaterials, supplierMap, result,
() => new IBeamDimensions { Height = dto.Height, WeightPerFoot = dto.WeightPerFoot },
dim => { var d = (IBeamDimensions)dim; d.Height = dto.Height; d.WeightPerFoot = dto.WeightPerFoot; });
foreach (var dto in materials.Pipes)
await ImportMaterialAsync(dto, MaterialShape.Pipe, existingMaterials, supplierMap, result,
await ImportMaterialAsync(context, dto, MaterialShape.Pipe, existingMaterials, supplierMap, result,
() => new PipeDimensions { NominalSize = dto.NominalSize, Wall = dto.Wall, Schedule = dto.Schedule },
dim => { var d = (PipeDimensions)dim; d.NominalSize = dto.NominalSize; d.Wall = (decimal?)dto.Wall; d.Schedule = dto.Schedule; });
foreach (var dto in materials.RectangularTubes)
await ImportMaterialAsync(dto, MaterialShape.RectangularTube, existingMaterials, supplierMap, result,
await ImportMaterialAsync(context, dto, MaterialShape.RectangularTube, existingMaterials, supplierMap, result,
() => new RectangularTubeDimensions { Width = dto.Width, Height = dto.Height, Wall = dto.Wall },
dim => { var d = (RectangularTubeDimensions)dim; d.Width = dto.Width; d.Height = dto.Height; d.Wall = dto.Wall; });
foreach (var dto in materials.RoundBars)
await ImportMaterialAsync(dto, MaterialShape.RoundBar, existingMaterials, supplierMap, result,
await ImportMaterialAsync(context, dto, MaterialShape.RoundBar, existingMaterials, supplierMap, result,
() => new RoundBarDimensions { Diameter = dto.Diameter },
dim => { var d = (RoundBarDimensions)dim; d.Diameter = dto.Diameter; });
foreach (var dto in materials.RoundTubes)
await ImportMaterialAsync(dto, MaterialShape.RoundTube, existingMaterials, supplierMap, result,
await ImportMaterialAsync(context, dto, MaterialShape.RoundTube, existingMaterials, supplierMap, result,
() => new RoundTubeDimensions { OuterDiameter = dto.OuterDiameter, Wall = dto.Wall },
dim => { var d = (RoundTubeDimensions)dim; d.OuterDiameter = dto.OuterDiameter; d.Wall = dto.Wall; });
foreach (var dto in materials.SquareBars)
await ImportMaterialAsync(dto, MaterialShape.SquareBar, existingMaterials, supplierMap, result,
await ImportMaterialAsync(context, dto, MaterialShape.SquareBar, existingMaterials, supplierMap, result,
() => new SquareBarDimensions { Size = dto.SideLength },
dim => { var d = (SquareBarDimensions)dim; d.Size = dto.SideLength; });
foreach (var dto in materials.SquareTubes)
await ImportMaterialAsync(dto, MaterialShape.SquareTube, existingMaterials, supplierMap, result,
await ImportMaterialAsync(context, dto, MaterialShape.SquareTube, existingMaterials, supplierMap, result,
() => new SquareTubeDimensions { Size = dto.SideLength, Wall = dto.Wall },
dim => { var d = (SquareTubeDimensions)dim; d.Size = dto.SideLength; d.Wall = dto.Wall; });
}
private async Task ImportMaterialAsync(
CatalogMaterialBaseDto dto, MaterialShape shape,
ApplicationDbContext context, CatalogMaterialBaseDto dto, MaterialShape shape,
List<Material> existingMaterials, Dictionary<string, int> supplierMap,
ImportResultDto result,
Func<MaterialDimensions> createDimensions,
@@ -384,9 +387,9 @@ public class CatalogService
result.MaterialsCreated++;
}
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
await ImportStockItemsAsync(material, dto.StockItems, supplierMap, result);
await ImportStockItemsAsync(context, material, dto.StockItems, supplierMap, result);
}
catch (Exception ex)
{
@@ -395,10 +398,10 @@ public class CatalogService
}
private async Task ImportStockItemsAsync(
Material material, List<CatalogStockItemDto> stockItems,
ApplicationDbContext context, Material material, List<CatalogStockItemDto> stockItems,
Dictionary<string, int> supplierMap, ImportResultDto result)
{
var existingStockItems = await _context.StockItems
var existingStockItems = await context.StockItems
.Include(s => s.SupplierOfferings)
.Where(s => s.MaterialId == material.Id)
.ToListAsync();
@@ -432,8 +435,8 @@ public class CatalogService
Notes = dto.Notes,
CreatedAt = DateTime.UtcNow
};
_context.StockItems.Add(stockItem);
await _context.SaveChangesAsync();
context.StockItems.Add(stockItem);
await context.SaveChangesAsync();
existingStockItems.Add(stockItem);
result.StockItemsCreated++;
}
@@ -473,7 +476,7 @@ public class CatalogService
Price = offeringDto.Price,
Notes = offeringDto.Notes
};
_context.SupplierOfferings.Add(offering);
context.SupplierOfferings.Add(offering);
stockItem.SupplierOfferings.Add(offering);
result.OfferingsCreated++;
}
@@ -486,7 +489,7 @@ public class CatalogService
}
}
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
}
catch (Exception ex)
{
+12 -9
View File
@@ -8,11 +8,11 @@ namespace CutList.Web.Services;
public class CutListPackingService
{
private readonly ApplicationDbContext _context;
private readonly IDbContextFactory<ApplicationDbContext> _factory;
public CutListPackingService(ApplicationDbContext context)
public CutListPackingService(IDbContextFactory<ApplicationDbContext> factory)
{
_context = context;
_factory = factory;
}
public async Task<MultiMaterialPackResult> PackAsync(IEnumerable<JobPart> parts, decimal kerfInches)
@@ -22,6 +22,7 @@ public class CutListPackingService
public async Task<MultiMaterialPackResult> PackAsync(IEnumerable<JobPart> parts, decimal kerfInches, IEnumerable<JobStock>? jobStock)
{
await using var context = _factory.CreateDbContext();
var result = new MultiMaterialPackResult();
// Group parts by material
@@ -37,7 +38,7 @@ public class CutListPackingService
var materialParts = group.ToList();
// Get the material
var material = await _context.Materials
var material = await context.Materials
.FirstOrDefaultAsync(m => m.Id == materialId);
if (material == null) continue;
@@ -63,7 +64,7 @@ public class CutListPackingService
else
{
// No job-specific stock - use all available stock items for this material
var stockItems = await _context.StockItems
var stockItems = await context.StockItems
.Where(s => s.MaterialId == materialId && s.IsActive)
.ToListAsync();
@@ -172,10 +173,12 @@ public class CutListPackingService
return result;
}
public MultiMaterialPackResult? LoadSavedResult(string json)
public async Task<MultiMaterialPackResult?> LoadSavedResultAsync(string json)
{
var saved = System.Text.Json.JsonSerializer.Deserialize<SavedOptimizationResult>(json);
return saved?.ToPackResult(_context);
if (saved == null) return null;
await using var context = _factory.CreateDbContext();
return await saved.ToPackResultAsync(context);
}
public string SerializeResult(MultiMaterialPackResult result)
@@ -344,13 +347,13 @@ public class SavedOptimizationResult
return saved;
}
public MultiMaterialPackResult ToPackResult(ApplicationDbContext context)
public async Task<MultiMaterialPackResult> ToPackResultAsync(ApplicationDbContext context)
{
var result = new MultiMaterialPackResult();
foreach (var savedMr in MaterialResults)
{
var material = context.Materials.Find(savedMr.MaterialId);
var material = await context.Materials.FindAsync(savedMr.MaterialId);
if (material == null) continue;
var packResult = new PackResult();
+108 -69
View File
@@ -6,16 +6,17 @@ namespace CutList.Web.Services;
public class JobService
{
private readonly ApplicationDbContext _context;
private readonly IDbContextFactory<ApplicationDbContext> _factory;
public JobService(ApplicationDbContext context)
public JobService(IDbContextFactory<ApplicationDbContext> factory)
{
_context = context;
_factory = factory;
}
public async Task<List<Job>> GetAllAsync()
{
return await _context.Jobs
await using var context = _factory.CreateDbContext();
return await context.Jobs
.Include(p => p.CuttingTool)
.Include(p => p.Parts)
.ThenInclude(pt => pt.Material)
@@ -25,7 +26,8 @@ public class JobService
public async Task<Job?> GetByIdAsync(int id)
{
return await _context.Jobs
await using var context = _factory.CreateDbContext();
return await context.Jobs
.Include(p => p.CuttingTool)
.Include(p => p.Parts.OrderBy(pt => pt.SortOrder))
.ThenInclude(pt => pt.Material)
@@ -38,17 +40,24 @@ public class JobService
public async Task<Job> CreateAsync(Job? job = null)
{
await using var context = _factory.CreateDbContext();
job ??= new Job();
job.JobNumber = await GenerateJobNumberAsync();
job.JobNumber = await GenerateJobNumberAsync(context);
job.CreatedAt = DateTime.UtcNow;
_context.Jobs.Add(job);
await _context.SaveChangesAsync();
context.Jobs.Add(job);
await context.SaveChangesAsync();
return job;
}
public async Task<string> GenerateJobNumberAsync()
{
var maxNumber = await _context.Jobs
await using var context = _factory.CreateDbContext();
return await GenerateJobNumberAsync(context);
}
private static async Task<string> GenerateJobNumberAsync(ApplicationDbContext context)
{
var maxNumber = await context.Jobs
.Where(j => j.JobNumber.StartsWith("JOB-"))
.Select(j => j.JobNumber)
.MaxAsync() as string;
@@ -71,46 +80,61 @@ public class JobService
public async Task UpdateAsync(Job job)
{
await using var context = _factory.CreateDbContext();
job.UpdatedAt = DateTime.UtcNow;
job.OptimizationResultJson = null;
job.OptimizedAt = null;
_context.Jobs.Update(job);
await _context.SaveChangesAsync();
context.Jobs.Update(job);
await context.SaveChangesAsync();
}
public async Task LockAsync(int id)
{
var job = await _context.Jobs.FindAsync(id);
await using var context = _factory.CreateDbContext();
var job = await context.Jobs.FindAsync(id);
if (job != null)
{
job.LockedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
}
}
public async Task UnlockAsync(int id)
{
var job = await _context.Jobs.FindAsync(id);
await using var context = _factory.CreateDbContext();
var job = await context.Jobs.FindAsync(id);
if (job != null)
{
job.LockedAt = null;
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
}
}
public async Task DeleteAsync(int id)
{
var job = await _context.Jobs.FindAsync(id);
await using var context = _factory.CreateDbContext();
var job = await context.Jobs.FindAsync(id);
if (job != null)
{
_context.Jobs.Remove(job);
await _context.SaveChangesAsync();
context.Jobs.Remove(job);
await context.SaveChangesAsync();
}
}
public async Task<Job> DuplicateAsync(int id)
{
var original = await GetByIdAsync(id);
await using var context = _factory.CreateDbContext();
var original = await context.Jobs
.Include(p => p.CuttingTool)
.Include(p => p.Parts.OrderBy(pt => pt.SortOrder))
.ThenInclude(pt => pt.Material)
.Include(p => p.Stock.OrderBy(s => s.SortOrder))
.ThenInclude(s => s.Material)
.Include(p => p.Stock)
.ThenInclude(s => s.StockItem)
.FirstOrDefaultAsync(p => p.Id == id);
if (original == null)
{
throw new ArgumentException("Job not found", nameof(id));
@@ -118,7 +142,7 @@ public class JobService
var duplicate = new Job
{
JobNumber = await GenerateJobNumberAsync(),
JobNumber = await GenerateJobNumberAsync(context),
Name = string.IsNullOrWhiteSpace(original.Name) ? null : $"{original.Name} (Copy)",
Customer = original.Customer,
CuttingToolId = original.CuttingToolId,
@@ -126,13 +150,13 @@ public class JobService
CreatedAt = DateTime.UtcNow
};
_context.Jobs.Add(duplicate);
await _context.SaveChangesAsync();
context.Jobs.Add(duplicate);
await context.SaveChangesAsync();
// Copy parts
foreach (var part in original.Parts)
{
_context.JobParts.Add(new JobPart
context.JobParts.Add(new JobPart
{
JobId = duplicate.Id,
MaterialId = part.MaterialId,
@@ -146,7 +170,7 @@ public class JobService
// Copy stock selections
foreach (var stock in original.Stock)
{
_context.JobStocks.Add(new JobStock
context.JobStocks.Add(new JobStock
{
JobId = duplicate.Id,
MaterialId = stock.MaterialId,
@@ -159,52 +183,55 @@ public class JobService
});
}
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
return duplicate;
}
// Optimization result persistence
public async Task SaveOptimizationResultAsync(int jobId, string resultJson, DateTime optimizedAt)
{
var job = await _context.Jobs.FindAsync(jobId);
await using var context = _factory.CreateDbContext();
var job = await context.Jobs.FindAsync(jobId);
if (job != null)
{
job.OptimizationResultJson = resultJson;
job.OptimizedAt = optimizedAt;
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
}
}
public async Task ClearOptimizationResultAsync(int jobId)
{
var job = await _context.Jobs.FindAsync(jobId);
await using var context = _factory.CreateDbContext();
var job = await context.Jobs.FindAsync(jobId);
if (job != null && job.OptimizationResultJson != null)
{
job.OptimizationResultJson = null;
job.OptimizedAt = null;
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
}
}
// Parts management
public async Task<JobPart> AddPartAsync(JobPart part)
{
var maxOrder = await _context.JobParts
await using var context = _factory.CreateDbContext();
var maxOrder = await context.JobParts
.Where(p => p.JobId == part.JobId)
.MaxAsync(p => (int?)p.SortOrder) ?? -1;
part.SortOrder = maxOrder + 1;
_context.JobParts.Add(part);
await _context.SaveChangesAsync();
context.JobParts.Add(part);
await context.SaveChangesAsync();
// Update job timestamp and clear stale results
var job = await _context.Jobs.FindAsync(part.JobId);
var job = await context.Jobs.FindAsync(part.JobId);
if (job != null)
{
job.UpdatedAt = DateTime.UtcNow;
job.OptimizationResultJson = null;
job.OptimizedAt = null;
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
}
return part;
@@ -212,35 +239,37 @@ public class JobService
public async Task UpdatePartAsync(JobPart part)
{
_context.JobParts.Update(part);
await _context.SaveChangesAsync();
await using var context = _factory.CreateDbContext();
context.JobParts.Update(part);
await context.SaveChangesAsync();
var job = await _context.Jobs.FindAsync(part.JobId);
var job = await context.Jobs.FindAsync(part.JobId);
if (job != null)
{
job.UpdatedAt = DateTime.UtcNow;
job.OptimizationResultJson = null;
job.OptimizedAt = null;
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
}
}
public async Task DeletePartAsync(int id)
{
var part = await _context.JobParts.FindAsync(id);
await using var context = _factory.CreateDbContext();
var part = await context.JobParts.FindAsync(id);
if (part != null)
{
var jobId = part.JobId;
_context.JobParts.Remove(part);
await _context.SaveChangesAsync();
context.JobParts.Remove(part);
await context.SaveChangesAsync();
var job = await _context.Jobs.FindAsync(jobId);
var job = await context.Jobs.FindAsync(jobId);
if (job != null)
{
job.UpdatedAt = DateTime.UtcNow;
job.OptimizationResultJson = null;
job.OptimizedAt = null;
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
}
}
}
@@ -248,21 +277,22 @@ public class JobService
// Stock management
public async Task<JobStock> AddStockAsync(JobStock stock)
{
var maxOrder = await _context.JobStocks
await using var context = _factory.CreateDbContext();
var maxOrder = await context.JobStocks
.Where(s => s.JobId == stock.JobId)
.MaxAsync(s => (int?)s.SortOrder) ?? -1;
stock.SortOrder = maxOrder + 1;
_context.JobStocks.Add(stock);
await _context.SaveChangesAsync();
context.JobStocks.Add(stock);
await context.SaveChangesAsync();
var job = await _context.Jobs.FindAsync(stock.JobId);
var job = await context.Jobs.FindAsync(stock.JobId);
if (job != null)
{
job.UpdatedAt = DateTime.UtcNow;
job.OptimizationResultJson = null;
job.OptimizedAt = null;
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
}
return stock;
@@ -270,42 +300,45 @@ public class JobService
public async Task UpdateStockAsync(JobStock stock)
{
_context.JobStocks.Update(stock);
await _context.SaveChangesAsync();
await using var context = _factory.CreateDbContext();
context.JobStocks.Update(stock);
await context.SaveChangesAsync();
var job = await _context.Jobs.FindAsync(stock.JobId);
var job = await context.Jobs.FindAsync(stock.JobId);
if (job != null)
{
job.UpdatedAt = DateTime.UtcNow;
job.OptimizationResultJson = null;
job.OptimizedAt = null;
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
}
}
public async Task DeleteStockAsync(int id)
{
var stock = await _context.JobStocks.FindAsync(id);
await using var context = _factory.CreateDbContext();
var stock = await context.JobStocks.FindAsync(id);
if (stock != null)
{
var jobId = stock.JobId;
_context.JobStocks.Remove(stock);
await _context.SaveChangesAsync();
context.JobStocks.Remove(stock);
await context.SaveChangesAsync();
var job = await _context.Jobs.FindAsync(jobId);
var job = await context.Jobs.FindAsync(jobId);
if (job != null)
{
job.UpdatedAt = DateTime.UtcNow;
job.OptimizationResultJson = null;
job.OptimizedAt = null;
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
}
}
}
public async Task<List<StockItem>> GetAvailableStockForMaterialAsync(int materialId)
{
return await _context.StockItems
await using var context = _factory.CreateDbContext();
return await context.StockItems
.Include(s => s.Material)
.Where(s => s.MaterialId == materialId && s.IsActive)
.OrderBy(s => s.LengthInches)
@@ -315,7 +348,8 @@ public class JobService
// Cutting tools
public async Task<List<CuttingTool>> GetCuttingToolsAsync(bool includeInactive = false)
{
var query = _context.CuttingTools.AsQueryable();
await using var context = _factory.CreateDbContext();
var query = context.CuttingTools.AsQueryable();
if (!includeInactive)
{
query = query.Where(t => t.IsActive);
@@ -325,53 +359,58 @@ public class JobService
public async Task<CuttingTool?> GetCuttingToolByIdAsync(int id)
{
return await _context.CuttingTools.FindAsync(id);
await using var context = _factory.CreateDbContext();
return await context.CuttingTools.FindAsync(id);
}
public async Task<CuttingTool?> GetDefaultCuttingToolAsync()
{
return await _context.CuttingTools.FirstOrDefaultAsync(t => t.IsDefault && t.IsActive);
await using var context = _factory.CreateDbContext();
return await context.CuttingTools.FirstOrDefaultAsync(t => t.IsDefault && t.IsActive);
}
public async Task<CuttingTool> CreateCuttingToolAsync(CuttingTool tool)
{
await using var context = _factory.CreateDbContext();
if (tool.IsDefault)
{
// Clear other defaults
var others = await _context.CuttingTools.Where(t => t.IsDefault).ToListAsync();
var others = await context.CuttingTools.Where(t => t.IsDefault).ToListAsync();
foreach (var other in others)
{
other.IsDefault = false;
}
}
_context.CuttingTools.Add(tool);
await _context.SaveChangesAsync();
context.CuttingTools.Add(tool);
await context.SaveChangesAsync();
return tool;
}
public async Task UpdateCuttingToolAsync(CuttingTool tool)
{
await using var context = _factory.CreateDbContext();
if (tool.IsDefault)
{
var others = await _context.CuttingTools.Where(t => t.IsDefault && t.Id != tool.Id).ToListAsync();
var others = await context.CuttingTools.Where(t => t.IsDefault && t.Id != tool.Id).ToListAsync();
foreach (var other in others)
{
other.IsDefault = false;
}
}
_context.CuttingTools.Update(tool);
await _context.SaveChangesAsync();
context.CuttingTools.Update(tool);
await context.SaveChangesAsync();
}
public async Task DeleteCuttingToolAsync(int id)
{
var tool = await _context.CuttingTools.FindAsync(id);
await using var context = _factory.CreateDbContext();
var tool = await context.CuttingTools.FindAsync(id);
if (tool != null)
{
tool.IsActive = false;
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
}
}
}
+52 -33
View File
@@ -6,16 +6,17 @@ namespace CutList.Web.Services;
public class MaterialService
{
private readonly ApplicationDbContext _context;
private readonly IDbContextFactory<ApplicationDbContext> _factory;
public MaterialService(ApplicationDbContext context)
public MaterialService(IDbContextFactory<ApplicationDbContext> factory)
{
_context = context;
_factory = factory;
}
public async Task<List<Material>> GetAllAsync(bool includeInactive = false)
{
var query = _context.Materials
await using var context = _factory.CreateDbContext();
var query = context.Materials
.Include(m => m.Dimensions)
.AsQueryable();
if (!includeInactive)
@@ -27,16 +28,18 @@ public class MaterialService
public async Task<Material?> GetByIdAsync(int id)
{
return await _context.Materials
await using var context = _factory.CreateDbContext();
return await context.Materials
.Include(m => m.Dimensions)
.FirstOrDefaultAsync(m => m.Id == id);
}
public async Task<Material> CreateAsync(Material material)
{
await using var context = _factory.CreateDbContext();
material.CreatedAt = DateTime.UtcNow;
_context.Materials.Add(material);
await _context.SaveChangesAsync();
context.Materials.Add(material);
await context.SaveChangesAsync();
return material;
}
@@ -45,6 +48,7 @@ public class MaterialService
/// </summary>
public async Task<Material> CreateWithDimensionsAsync(Material material, MaterialDimensions dimensions)
{
await using var context = _factory.CreateDbContext();
material.CreatedAt = DateTime.UtcNow;
// Auto-generate Size string from dimensions if not provided
@@ -56,13 +60,13 @@ public class MaterialService
// Set sort order from primary dimension
material.SortOrder = dimensions.GetSortOrder();
_context.Materials.Add(material);
await _context.SaveChangesAsync();
context.Materials.Add(material);
await context.SaveChangesAsync();
// Link dimensions to the created material
dimensions.MaterialId = material.Id;
_context.MaterialDimensions.Add(dimensions);
await _context.SaveChangesAsync();
context.MaterialDimensions.Add(dimensions);
await context.SaveChangesAsync();
material.Dimensions = dimensions;
return material;
@@ -70,9 +74,10 @@ public class MaterialService
public async Task UpdateAsync(Material material)
{
await using var context = _factory.CreateDbContext();
material.UpdatedAt = DateTime.UtcNow;
_context.Materials.Update(material);
await _context.SaveChangesAsync();
context.Materials.Update(material);
await context.SaveChangesAsync();
}
/// <summary>
@@ -80,6 +85,7 @@ public class MaterialService
/// </summary>
public async Task UpdateWithDimensionsAsync(Material material, MaterialDimensions dimensions, bool regenerateSize = false)
{
await using var context = _factory.CreateDbContext();
material.UpdatedAt = DateTime.UtcNow;
if (regenerateSize)
@@ -98,14 +104,14 @@ public class MaterialService
if (dimensions.Id > 0)
{
// Already tracked, just save
_context.Entry(material).State = EntityState.Modified;
context.Entry(material).State = EntityState.Modified;
}
else
{
_context.Materials.Update(material);
context.Materials.Update(material);
// Check if dimensions already exist for this material
var existingDimensions = await _context.MaterialDimensions
var existingDimensions = await context.MaterialDimensions
.AsNoTracking()
.FirstOrDefaultAsync(d => d.MaterialId == material.Id);
@@ -113,30 +119,32 @@ public class MaterialService
{
// Copy the existing Id to update in place
dimensions.Id = existingDimensions.Id;
_context.MaterialDimensions.Update(dimensions);
context.MaterialDimensions.Update(dimensions);
}
else
{
_context.MaterialDimensions.Add(dimensions);
context.MaterialDimensions.Add(dimensions);
}
}
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
}
public async Task DeleteAsync(int id)
{
var material = await _context.Materials.FindAsync(id);
await using var context = _factory.CreateDbContext();
var material = await context.Materials.FindAsync(id);
if (material != null)
{
material.IsActive = false;
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
}
}
public async Task<bool> ExistsAsync(MaterialShape shape, string size, int? excludeId = null)
{
var query = _context.Materials.Where(m => m.Shape == shape && m.Size == size && m.IsActive);
await using var context = _factory.CreateDbContext();
var query = context.Materials.Where(m => m.Shape == shape && m.Size == size && m.IsActive);
if (excludeId.HasValue)
{
query = query.Where(m => m.Id != excludeId.Value);
@@ -149,10 +157,11 @@ public class MaterialService
/// </summary>
public async Task<List<Material>> SearchRoundBarByDiameterAsync(decimal targetDiameter, decimal tolerance)
{
await using var context = _factory.CreateDbContext();
var minValue = targetDiameter - tolerance;
var maxValue = targetDiameter + tolerance;
return await _context.Set<RoundBarDimensions>()
return await context.Set<RoundBarDimensions>()
.Include(d => d.Material)
.Where(d => d.Material.IsActive)
.Where(d => d.Diameter >= minValue && d.Diameter <= maxValue)
@@ -166,10 +175,11 @@ public class MaterialService
/// </summary>
public async Task<List<Material>> SearchRoundTubeByODAsync(decimal targetOD, decimal tolerance)
{
await using var context = _factory.CreateDbContext();
var minValue = targetOD - tolerance;
var maxValue = targetOD + tolerance;
return await _context.Set<RoundTubeDimensions>()
return await context.Set<RoundTubeDimensions>()
.Include(d => d.Material)
.Where(d => d.Material.IsActive)
.Where(d => d.OuterDiameter >= minValue && d.OuterDiameter <= maxValue)
@@ -183,10 +193,11 @@ public class MaterialService
/// </summary>
public async Task<List<Material>> SearchFlatBarByWidthAsync(decimal targetWidth, decimal tolerance)
{
await using var context = _factory.CreateDbContext();
var minValue = targetWidth - tolerance;
var maxValue = targetWidth + tolerance;
return await _context.Set<FlatBarDimensions>()
return await context.Set<FlatBarDimensions>()
.Include(d => d.Material)
.Where(d => d.Material.IsActive)
.Where(d => d.Width >= minValue && d.Width <= maxValue)
@@ -200,10 +211,11 @@ public class MaterialService
/// </summary>
public async Task<List<Material>> SearchSquareBarBySizeAsync(decimal targetSize, decimal tolerance)
{
await using var context = _factory.CreateDbContext();
var minValue = targetSize - tolerance;
var maxValue = targetSize + tolerance;
return await _context.Set<SquareBarDimensions>()
return await context.Set<SquareBarDimensions>()
.Include(d => d.Material)
.Where(d => d.Material.IsActive)
.Where(d => d.Size >= minValue && d.Size <= maxValue)
@@ -217,10 +229,11 @@ public class MaterialService
/// </summary>
public async Task<List<Material>> SearchSquareTubeBySizeAsync(decimal targetSize, decimal tolerance)
{
await using var context = _factory.CreateDbContext();
var minValue = targetSize - tolerance;
var maxValue = targetSize + tolerance;
return await _context.Set<SquareTubeDimensions>()
return await context.Set<SquareTubeDimensions>()
.Include(d => d.Material)
.Where(d => d.Material.IsActive)
.Where(d => d.Size >= minValue && d.Size <= maxValue)
@@ -234,10 +247,11 @@ public class MaterialService
/// </summary>
public async Task<List<Material>> SearchRectangularTubeByWidthAsync(decimal targetWidth, decimal tolerance)
{
await using var context = _factory.CreateDbContext();
var minValue = targetWidth - tolerance;
var maxValue = targetWidth + tolerance;
return await _context.Set<RectangularTubeDimensions>()
return await context.Set<RectangularTubeDimensions>()
.Include(d => d.Material)
.Where(d => d.Material.IsActive)
.Where(d => d.Width >= minValue && d.Width <= maxValue)
@@ -251,10 +265,11 @@ public class MaterialService
/// </summary>
public async Task<List<Material>> SearchAngleByLegAsync(decimal targetLeg, decimal tolerance)
{
await using var context = _factory.CreateDbContext();
var minValue = targetLeg - tolerance;
var maxValue = targetLeg + tolerance;
return await _context.Set<AngleDimensions>()
return await context.Set<AngleDimensions>()
.Include(d => d.Material)
.Where(d => d.Material.IsActive)
.Where(d => d.Leg1 >= minValue && d.Leg1 <= maxValue)
@@ -268,10 +283,11 @@ public class MaterialService
/// </summary>
public async Task<List<Material>> SearchChannelByHeightAsync(decimal targetHeight, decimal tolerance)
{
await using var context = _factory.CreateDbContext();
var minValue = targetHeight - tolerance;
var maxValue = targetHeight + tolerance;
return await _context.Set<ChannelDimensions>()
return await context.Set<ChannelDimensions>()
.Include(d => d.Material)
.Where(d => d.Material.IsActive)
.Where(d => d.Height >= minValue && d.Height <= maxValue)
@@ -285,10 +301,11 @@ public class MaterialService
/// </summary>
public async Task<List<Material>> SearchIBeamByHeightAsync(decimal targetHeight, decimal tolerance)
{
await using var context = _factory.CreateDbContext();
var minValue = targetHeight - tolerance;
var maxValue = targetHeight + tolerance;
return await _context.Set<IBeamDimensions>()
return await context.Set<IBeamDimensions>()
.Include(d => d.Material)
.Where(d => d.Material.IsActive)
.Where(d => d.Height >= minValue && d.Height <= maxValue)
@@ -302,10 +319,11 @@ public class MaterialService
/// </summary>
public async Task<List<Material>> SearchPipeByNominalSizeAsync(decimal targetNPS, decimal tolerance)
{
await using var context = _factory.CreateDbContext();
var minValue = targetNPS - tolerance;
var maxValue = targetNPS + tolerance;
return await _context.Set<PipeDimensions>()
return await context.Set<PipeDimensions>()
.Include(d => d.Material)
.Where(d => d.Material.IsActive)
.Where(d => d.NominalSize >= minValue && d.NominalSize <= maxValue)
@@ -319,7 +337,8 @@ public class MaterialService
/// </summary>
public async Task<List<Material>> GetByShapeAsync(MaterialShape shape, bool includeInactive = false)
{
var query = _context.Materials
await using var context = _factory.CreateDbContext();
var query = context.Materials
.Include(m => m.Dimensions)
.Where(m => m.Shape == shape);
+34 -18
View File
@@ -6,16 +6,18 @@ namespace CutList.Web.Services;
public class PurchaseItemService
{
private readonly ApplicationDbContext _context;
private readonly IDbContextFactory<ApplicationDbContext> _factory;
public PurchaseItemService(ApplicationDbContext context)
public PurchaseItemService(IDbContextFactory<ApplicationDbContext> factory)
{
_context = context;
_factory = factory;
}
public async Task<List<PurchaseItem>> GetAllAsync(PurchaseItemStatus? status = null)
{
var query = _context.PurchaseItems
await using var context = _factory.CreateDbContext();
var query = context.PurchaseItems
.Include(p => p.StockItem)
.ThenInclude(s => s.Material)
.Include(p => p.Supplier)
@@ -35,7 +37,9 @@ public class PurchaseItemService
public async Task<PurchaseItem?> GetByIdAsync(int id)
{
return await _context.PurchaseItems
await using var context = _factory.CreateDbContext();
return await context.PurchaseItems
.Include(p => p.StockItem)
.ThenInclude(s => s.Material)
.Include(p => p.Supplier)
@@ -45,59 +49,71 @@ public class PurchaseItemService
public async Task<PurchaseItem> CreateAsync(PurchaseItem item)
{
await using var context = _factory.CreateDbContext();
item.CreatedAt = DateTime.UtcNow;
_context.PurchaseItems.Add(item);
await _context.SaveChangesAsync();
context.PurchaseItems.Add(item);
await context.SaveChangesAsync();
return item;
}
public async Task CreateBulkAsync(List<PurchaseItem> items)
{
await using var context = _factory.CreateDbContext();
var now = DateTime.UtcNow;
foreach (var item in items)
{
item.CreatedAt = now;
}
_context.PurchaseItems.AddRange(items);
await _context.SaveChangesAsync();
context.PurchaseItems.AddRange(items);
await context.SaveChangesAsync();
}
public async Task UpdateAsync(PurchaseItem item)
{
await using var context = _factory.CreateDbContext();
item.UpdatedAt = DateTime.UtcNow;
_context.PurchaseItems.Update(item);
await _context.SaveChangesAsync();
context.PurchaseItems.Update(item);
await context.SaveChangesAsync();
}
public async Task UpdateStatusAsync(int id, PurchaseItemStatus status)
{
var item = await _context.PurchaseItems.FindAsync(id);
await using var context = _factory.CreateDbContext();
var item = await context.PurchaseItems.FindAsync(id);
if (item != null)
{
item.Status = status;
item.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
}
}
public async Task UpdateSupplierAsync(int id, int? supplierId)
{
var item = await _context.PurchaseItems.FindAsync(id);
await using var context = _factory.CreateDbContext();
var item = await context.PurchaseItems.FindAsync(id);
if (item != null)
{
item.SupplierId = supplierId;
item.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
}
}
public async Task DeleteAsync(int id)
{
var item = await _context.PurchaseItems.FindAsync(id);
await using var context = _factory.CreateDbContext();
var item = await context.PurchaseItems.FindAsync(id);
if (item != null)
{
_context.PurchaseItems.Remove(item);
await _context.SaveChangesAsync();
context.PurchaseItems.Remove(item);
await context.SaveChangesAsync();
}
}
}
+61 -31
View File
@@ -6,16 +6,18 @@ namespace CutList.Web.Services;
public class StockItemService
{
private readonly ApplicationDbContext _context;
private readonly IDbContextFactory<ApplicationDbContext> _factory;
public StockItemService(ApplicationDbContext context)
public StockItemService(IDbContextFactory<ApplicationDbContext> factory)
{
_context = context;
_factory = factory;
}
public async Task<List<StockItem>> GetAllAsync(bool includeInactive = false)
{
var query = _context.StockItems
await using var context = _factory.CreateDbContext();
var query = context.StockItems
.Include(s => s.Material)
.AsQueryable();
@@ -33,7 +35,9 @@ public class StockItemService
public async Task<List<StockItem>> GetByMaterialAsync(int materialId, bool includeInactive = false)
{
var query = _context.StockItems
await using var context = _factory.CreateDbContext();
var query = context.StockItems
.Include(s => s.Material)
.Where(s => s.MaterialId == materialId);
@@ -49,7 +53,9 @@ public class StockItemService
public async Task<StockItem?> GetByIdAsync(int id)
{
return await _context.StockItems
await using var context = _factory.CreateDbContext();
return await context.StockItems
.Include(s => s.Material)
.Include(s => s.SupplierOfferings)
.ThenInclude(o => o.Supplier)
@@ -58,33 +64,41 @@ public class StockItemService
public async Task<StockItem> CreateAsync(StockItem stockItem)
{
await using var context = _factory.CreateDbContext();
stockItem.CreatedAt = DateTime.UtcNow;
_context.StockItems.Add(stockItem);
await _context.SaveChangesAsync();
context.StockItems.Add(stockItem);
await context.SaveChangesAsync();
return stockItem;
}
public async Task UpdateAsync(StockItem stockItem)
{
await using var context = _factory.CreateDbContext();
stockItem.UpdatedAt = DateTime.UtcNow;
_context.StockItems.Update(stockItem);
await _context.SaveChangesAsync();
context.StockItems.Update(stockItem);
await context.SaveChangesAsync();
}
public async Task DeleteAsync(int id)
{
var stockItem = await _context.StockItems.FindAsync(id);
await using var context = _factory.CreateDbContext();
var stockItem = await context.StockItems.FindAsync(id);
if (stockItem != null)
{
stockItem.IsActive = false;
stockItem.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
}
}
public async Task<bool> ExistsAsync(int materialId, decimal lengthInches, int? excludeId = null)
{
var query = _context.StockItems.Where(s =>
await using var context = _factory.CreateDbContext();
var query = context.StockItems.Where(s =>
s.MaterialId == materialId &&
s.LengthInches == lengthInches &&
s.IsActive);
@@ -100,7 +114,9 @@ public class StockItemService
// Stock transaction methods
public async Task<StockTransaction> AddStockAsync(int stockItemId, int quantity, int? supplierId = null, decimal? unitPrice = null, string? notes = null)
{
var stockItem = await _context.StockItems.FindAsync(stockItemId)
await using var context = _factory.CreateDbContext();
var stockItem = await context.StockItems.FindAsync(stockItemId)
?? throw new InvalidOperationException($"Stock item {stockItemId} not found");
var transaction = new StockTransaction
@@ -117,15 +133,17 @@ public class StockItemService
stockItem.QuantityOnHand += quantity;
stockItem.UpdatedAt = DateTime.UtcNow;
_context.StockTransactions.Add(transaction);
await _context.SaveChangesAsync();
context.StockTransactions.Add(transaction);
await context.SaveChangesAsync();
return transaction;
}
public async Task<decimal?> GetAverageCostAsync(int stockItemId)
{
var transactions = await _context.StockTransactions
await using var context = _factory.CreateDbContext();
var transactions = await context.StockTransactions
.Where(t => t.StockItemId == stockItemId && t.Type == StockTransactionType.Received && t.UnitPrice.HasValue)
.ToListAsync();
@@ -140,7 +158,9 @@ public class StockItemService
public async Task<decimal?> GetLastPurchasePriceAsync(int stockItemId)
{
return await _context.StockTransactions
await using var context = _factory.CreateDbContext();
return await context.StockTransactions
.Where(t => t.StockItemId == stockItemId && t.Type == StockTransactionType.Received && t.UnitPrice.HasValue)
.OrderByDescending(t => t.CreatedAt)
.Select(t => t.UnitPrice)
@@ -149,7 +169,9 @@ public class StockItemService
public async Task<StockTransaction> UseStockAsync(int stockItemId, int quantity, int? jobId = null, string? notes = null)
{
var stockItem = await _context.StockItems.FindAsync(stockItemId)
await using var context = _factory.CreateDbContext();
var stockItem = await context.StockItems.FindAsync(stockItemId)
?? throw new InvalidOperationException($"Stock item {stockItemId} not found");
var transaction = new StockTransaction
@@ -165,15 +187,17 @@ public class StockItemService
stockItem.QuantityOnHand -= quantity;
stockItem.UpdatedAt = DateTime.UtcNow;
_context.StockTransactions.Add(transaction);
await _context.SaveChangesAsync();
context.StockTransactions.Add(transaction);
await context.SaveChangesAsync();
return transaction;
}
public async Task<StockTransaction> AdjustStockAsync(int stockItemId, int newQuantity, string? notes = null)
{
var stockItem = await _context.StockItems.FindAsync(stockItemId)
await using var context = _factory.CreateDbContext();
var stockItem = await context.StockItems.FindAsync(stockItemId)
?? throw new InvalidOperationException($"Stock item {stockItemId} not found");
var difference = newQuantity - stockItem.QuantityOnHand;
@@ -190,15 +214,17 @@ public class StockItemService
stockItem.QuantityOnHand = newQuantity;
stockItem.UpdatedAt = DateTime.UtcNow;
_context.StockTransactions.Add(transaction);
await _context.SaveChangesAsync();
context.StockTransactions.Add(transaction);
await context.SaveChangesAsync();
return transaction;
}
public async Task<StockTransaction> ScrapStockAsync(int stockItemId, int quantity, string? notes = null)
{
var stockItem = await _context.StockItems.FindAsync(stockItemId)
await using var context = _factory.CreateDbContext();
var stockItem = await context.StockItems.FindAsync(stockItemId)
?? throw new InvalidOperationException($"Stock item {stockItemId} not found");
var transaction = new StockTransaction
@@ -213,15 +239,17 @@ public class StockItemService
stockItem.QuantityOnHand -= quantity;
stockItem.UpdatedAt = DateTime.UtcNow;
_context.StockTransactions.Add(transaction);
await _context.SaveChangesAsync();
context.StockTransactions.Add(transaction);
await context.SaveChangesAsync();
return transaction;
}
public async Task<List<StockTransaction>> GetTransactionHistoryAsync(int stockItemId, int? limit = null)
{
var query = _context.StockTransactions
await using var context = _factory.CreateDbContext();
var query = context.StockTransactions
.Include(t => t.Job)
.Include(t => t.Supplier)
.Where(t => t.StockItemId == stockItemId)
@@ -238,10 +266,12 @@ public class StockItemService
public async Task<int> RecalculateQuantityAsync(int stockItemId)
{
var stockItem = await _context.StockItems.FindAsync(stockItemId)
await using var context = _factory.CreateDbContext();
var stockItem = await context.StockItems.FindAsync(stockItemId)
?? throw new InvalidOperationException($"Stock item {stockItemId} not found");
var calculatedQuantity = await _context.StockTransactions
var calculatedQuantity = await context.StockTransactions
.Where(t => t.StockItemId == stockItemId)
.SumAsync(t => t.Quantity);
@@ -249,7 +279,7 @@ public class StockItemService
{
stockItem.QuantityOnHand = calculatedQuantity;
stockItem.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
}
return calculatedQuantity;
+33 -21
View File
@@ -6,16 +6,17 @@ namespace CutList.Web.Services;
public class SupplierService
{
private readonly ApplicationDbContext _context;
private readonly IDbContextFactory<ApplicationDbContext> _factory;
public SupplierService(ApplicationDbContext context)
public SupplierService(IDbContextFactory<ApplicationDbContext> factory)
{
_context = context;
_factory = factory;
}
public async Task<List<Supplier>> GetAllAsync(bool includeInactive = false)
{
var query = _context.Suppliers.AsQueryable();
await using var context = _factory.CreateDbContext();
var query = context.Suppliers.AsQueryable();
if (!includeInactive)
{
query = query.Where(s => s.IsActive);
@@ -25,7 +26,8 @@ public class SupplierService
public async Task<Supplier?> GetByIdAsync(int id)
{
return await _context.Suppliers
await using var context = _factory.CreateDbContext();
return await context.Suppliers
.Include(s => s.Offerings)
.ThenInclude(o => o.StockItem)
.ThenInclude(si => si.Material)
@@ -34,32 +36,36 @@ public class SupplierService
public async Task<Supplier> CreateAsync(Supplier supplier)
{
await using var context = _factory.CreateDbContext();
supplier.CreatedAt = DateTime.UtcNow;
_context.Suppliers.Add(supplier);
await _context.SaveChangesAsync();
context.Suppliers.Add(supplier);
await context.SaveChangesAsync();
return supplier;
}
public async Task UpdateAsync(Supplier supplier)
{
_context.Suppliers.Update(supplier);
await _context.SaveChangesAsync();
await using var context = _factory.CreateDbContext();
context.Suppliers.Update(supplier);
await context.SaveChangesAsync();
}
public async Task DeleteAsync(int id)
{
var supplier = await _context.Suppliers.FindAsync(id);
await using var context = _factory.CreateDbContext();
var supplier = await context.Suppliers.FindAsync(id);
if (supplier != null)
{
supplier.IsActive = false;
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
}
}
// Offering management
public async Task<List<SupplierOffering>> GetOfferingsForSupplierAsync(int supplierId)
{
return await _context.SupplierOfferings
await using var context = _factory.CreateDbContext();
return await context.SupplierOfferings
.Include(o => o.StockItem)
.ThenInclude(si => si.Material)
.Where(o => o.SupplierId == supplierId && o.IsActive)
@@ -71,7 +77,8 @@ public class SupplierService
public async Task<List<SupplierOffering>> GetOfferingsForStockItemAsync(int stockItemId)
{
return await _context.SupplierOfferings
await using var context = _factory.CreateDbContext();
return await context.SupplierOfferings
.Include(o => o.Supplier)
.Where(o => o.StockItemId == stockItemId && o.IsActive && o.Supplier.IsActive)
.OrderBy(o => o.Supplier.Name)
@@ -80,7 +87,8 @@ public class SupplierService
public async Task<SupplierOffering?> GetOfferingByIdAsync(int id)
{
return await _context.SupplierOfferings
await using var context = _factory.CreateDbContext();
return await context.SupplierOfferings
.Include(o => o.StockItem)
.ThenInclude(si => si.Material)
.Include(o => o.Supplier)
@@ -89,30 +97,34 @@ public class SupplierService
public async Task<SupplierOffering> AddOfferingAsync(SupplierOffering offering)
{
_context.SupplierOfferings.Add(offering);
await _context.SaveChangesAsync();
await using var context = _factory.CreateDbContext();
context.SupplierOfferings.Add(offering);
await context.SaveChangesAsync();
return offering;
}
public async Task UpdateOfferingAsync(SupplierOffering offering)
{
_context.SupplierOfferings.Update(offering);
await _context.SaveChangesAsync();
await using var context = _factory.CreateDbContext();
context.SupplierOfferings.Update(offering);
await context.SaveChangesAsync();
}
public async Task DeleteOfferingAsync(int id)
{
var offering = await _context.SupplierOfferings.FindAsync(id);
await using var context = _factory.CreateDbContext();
var offering = await context.SupplierOfferings.FindAsync(id);
if (offering != null)
{
offering.IsActive = false;
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
}
}
public async Task<bool> OfferingExistsAsync(int supplierId, int stockItemId, int? excludeId = null)
{
var query = _context.SupplierOfferings.Where(o =>
await using var context = _factory.CreateDbContext();
var query = context.SupplierOfferings.Where(o =>
o.SupplierId == supplierId &&
o.StockItemId == stockItemId &&
o.IsActive);