From dbd39a9fe54f1053d367b5ad1a619595fded989a Mon Sep 17 00:00:00 2001 From: Scott Pouliot Date: Wed, 10 Jun 2026 22:29:31 -0400 Subject: [PATCH] Phase 3: AR/AP aging buckets, PO seeder, Bills vendor fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bills.cs: replace aceHardware/fastenal lookups with grainger/harbor/localSupply to match Phase 1 vendor renames; update all vendor invoice number prefixes - Bills.cs: add 3 AP aging-bucket bills (30-60, 61-90, 90+ days overdue) so all four AP aging buckets are populated for report demos - Invoices.cs: add 3 more overdue invoices (31-60, 61-90, 90+ day AR buckets) alongside the existing 21-day overdue; total now 29 invoices - New SeedDataService.PurchaseOrders.cs: 7 POs — 3 Received (historical), 2 Submitted (in-flight), 2 Draft (pending approval); links to inventory items where available - SeedDataService.cs: wire SeedPurchaseOrdersAsync after Vendors seeder - Remove.cs: add PO + POItem cleanup inside Bills removal block (two-step ID fetch to avoid nested LINQ translation issues) Co-Authored-By: Claude Sonnet 4.6 --- .../Services/SeedDataService.Bills.cs | 117 +++++++-- .../Services/SeedDataService.Invoices.cs | 10 +- .../SeedDataService.PurchaseOrders.cs | 228 ++++++++++++++++++ .../Services/SeedDataService.Remove.cs | 28 +++ .../Services/SeedDataService.cs | 1 + 5 files changed, 359 insertions(+), 25 deletions(-) create mode 100644 src/PowderCoating.Infrastructure/Services/SeedDataService.PurchaseOrders.cs diff --git a/src/PowderCoating.Infrastructure/Services/SeedDataService.Bills.cs b/src/PowderCoating.Infrastructure/Services/SeedDataService.Bills.cs index 6ec658c..ecd85bb 100644 --- a/src/PowderCoating.Infrastructure/Services/SeedDataService.Bills.cs +++ b/src/PowderCoating.Infrastructure/Services/SeedDataService.Bills.cs @@ -84,10 +84,11 @@ public partial class SeedDataService .Where(v => v.CompanyId == company.Id && !v.IsDeleted) .ToListAsync(); - var prismatic = vendors.FirstOrDefault(v => v.CompanyName.Contains("Prismatic")) ?? vendors.FirstOrDefault(); + var prismatic = vendors.FirstOrDefault(v => v.CompanyName.Contains("Prismatic")) ?? vendors.FirstOrDefault(); var columbia = vendors.FirstOrDefault(v => v.CompanyName.Contains("Columbia")) ?? vendors.FirstOrDefault(); - var aceHardware = vendors.FirstOrDefault(v => v.CompanyName.Contains("Ace")) ?? vendors.FirstOrDefault(); - var fastenal = vendors.FirstOrDefault(v => v.CompanyName.Contains("Fastenal")) ?? vendors.FirstOrDefault(); + var grainger = vendors.FirstOrDefault(v => v.CompanyName.Contains("Grainger")) ?? vendors.FirstOrDefault(); + var harbor = vendors.FirstOrDefault(v => v.CompanyName.Contains("Harbor")) ?? vendors.FirstOrDefault(); + var localSupply = vendors.FirstOrDefault(v => v.CompanyName.Contains("Local")) ?? vendors.FirstOrDefault(); var fallback = vendors.FirstOrDefault(); if (fallback == null) @@ -270,11 +271,11 @@ public partial class SeedDataService // ── CONSUMABLES / HARDWARE ───────────────────────────────────────────── - // Month -3: Fastenal hardware — Paid + // Month -3: Harbor Freight consumables — Paid await AddBill(new Bill { - VendorInvoiceNumber = "FST-18822", - VendorId = (fastenal ?? fallback).Id, + VendorInvoiceNumber = "HBF-18822", + VendorId = (harbor ?? fallback).Id, APAccountId = apAccount.Id, BillDate = now.AddDays(-85), DueDate = now.AddDays(-55), @@ -293,20 +294,20 @@ public partial class SeedDataService } }, new BillPayment { - VendorId = (fastenal ?? fallback).Id, + VendorId = (harbor ?? fallback).Id, BankAccountId = checkingAccount.Id, PaymentDate = now.AddDays(-55), Amount = 412.50m, PaymentMethod = PaymentMethod.Check, CheckNumber = "1082", - Memo = "FST-18822 — paid in full" + Memo = "HBF-18822 — paid in full" }); - // Month -1: Fastenal — Paid + // Month -1: Harbor Freight — Paid await AddBill(new Bill { - VendorInvoiceNumber = "FST-20041", - VendorId = (fastenal ?? fallback).Id, + VendorInvoiceNumber = "HBF-20041", + VendorId = (harbor ?? fallback).Id, APAccountId = apAccount.Id, BillDate = now.AddDays(-40), DueDate = now.AddDays(-10), @@ -325,20 +326,20 @@ public partial class SeedDataService } }, new BillPayment { - VendorId = (fastenal ?? fallback).Id, + VendorId = (harbor ?? fallback).Id, BankAccountId = checkingAccount.Id, PaymentDate = now.AddDays(-10), Amount = 298.00m, PaymentMethod = PaymentMethod.Check, CheckNumber = "1086", - Memo = "FST-20041 — paid in full" + Memo = "HBF-20041 — paid in full" }); - // Current: Fastenal — Open + // Current: Harbor Freight — Open await AddBill(new Bill { - VendorInvoiceNumber = "FST-20441", - VendorId = (fastenal ?? fallback).Id, + VendorInvoiceNumber = "HBF-20441", + VendorId = (harbor ?? fallback).Id, APAccountId = apAccount.Id, BillDate = now.AddDays(-7), DueDate = now.AddDays(23), @@ -361,8 +362,8 @@ public partial class SeedDataService // Month -3: Sandblaster service — Paid await AddBill(new Bill { - VendorInvoiceNumber = "ACE-6901", - VendorId = (aceHardware ?? fallback).Id, + VendorInvoiceNumber = "GRG-6901", + VendorId = (grainger ?? fallback).Id, APAccountId = apAccount.Id, BillDate = now.AddDays(-80), DueDate = now.AddDays(-50), @@ -380,20 +381,20 @@ public partial class SeedDataService } }, new BillPayment { - VendorId = (aceHardware ?? fallback).Id, + VendorId = (grainger ?? fallback).Id, BankAccountId = checkingAccount.Id, PaymentDate = now.AddDays(-50), Amount = 310.00m, PaymentMethod = PaymentMethod.Check, CheckNumber = "1079", - Memo = "ACE-6901 — sandblaster parts" + Memo = "GRG-6901 — sandblaster parts" }); // Month -1: Oven repair — Partially paid await AddBill(new Bill { - VendorInvoiceNumber = "ACE-7714", - VendorId = (aceHardware ?? fallback).Id, + VendorInvoiceNumber = "GRG-7714", + VendorId = (grainger ?? fallback).Id, APAccountId = apAccount.Id, BillDate = now.AddDays(-20), DueDate = now.AddDays(10), @@ -411,13 +412,13 @@ public partial class SeedDataService } }, new BillPayment { - VendorId = (aceHardware ?? fallback).Id, + VendorId = (grainger ?? fallback).Id, BankAccountId = checkingAccount.Id, PaymentDate = now.AddDays(-10), Amount = 200.00m, PaymentMethod = PaymentMethod.Check, CheckNumber = "1087", - Memo = "ACE-7714 — partial payment" + Memo = "GRG-7714 — partial payment" }); // ── UTILITIES (3 months each) ───────────────────────────────────────── @@ -509,6 +510,74 @@ public partial class SeedDataService Memo = "Electric bill — auto pay" }); + // ── AP AGING DEMO BILLS ─────────────────────────────────────────────── + // These open/unpaid bills populate all four AP aging buckets for report demos. + + // 30-60 day bucket: consumables that slipped through AP (~40 days past due) + await AddBill(new Bill + { + VendorInvoiceNumber = "HBF-19900", + VendorId = (harbor ?? fallback).Id, + APAccountId = apAccount.Id, + BillDate = now.AddDays(-70), + DueDate = now.AddDays(-40), + Status = BillStatus.Open, + Terms = "Net 30", + Memo = "Shop consumables — unpaid (aging demo)", + SubTotal = 228.50m, + Total = 228.50m, + AmountPaid = 0m, + CreatedAt = now.AddDays(-70), + LineItems = + { + new BillLineItem { AccountId = consumablesAccount?.Id, Description = "Silicone Plugs Assortment", Quantity = 2, UnitPrice = 64.25m, Amount = 128.50m, DisplayOrder = 1 }, + new BillLineItem { AccountId = consumablesAccount?.Id, Description = "Ground Strap Replacements", Quantity = 1, UnitPrice = 100.00m, Amount = 100.00m, DisplayOrder = 2 } + } + }); + + // 61-90 day bucket: blast media from Local Industrial Supply (~72 days past due) + await AddBill(new Bill + { + VendorInvoiceNumber = "LIS-3301", + VendorId = (localSupply ?? fallback).Id, + APAccountId = apAccount.Id, + BillDate = now.AddDays(-102), + DueDate = now.AddDays(-72), + Status = BillStatus.Open, + Terms = "Net 30", + Memo = "Aluminum oxide blast media — unpaid (aging demo)", + SubTotal = 385.00m, + Total = 385.00m, + AmountPaid = 0m, + CreatedAt = now.AddDays(-102), + LineItems = + { + new BillLineItem { AccountId = consumablesAccount?.Id, Description = "Aluminum Oxide #80 Grit — 100 lb bag", Quantity = 5, UnitPrice = 77.00m, Amount = 385.00m, DisplayOrder = 1 } + } + }); + + // 90+ day bucket: old Grainger equipment parts order (~98 days past due) + await AddBill(new Bill + { + VendorInvoiceNumber = "GRG-5001", + VendorId = (grainger ?? fallback).Id, + APAccountId = apAccount.Id, + BillDate = now.AddDays(-128), + DueDate = now.AddDays(-98), + Status = BillStatus.Open, + Terms = "Net 30", + Memo = "Oven conveyor motor parts — unpaid (aging demo)", + SubTotal = 492.00m, + Total = 492.00m, + AmountPaid = 0m, + CreatedAt = now.AddDays(-128), + LineItems = + { + new BillLineItem { AccountId = equipRepairsAccount?.Id, Description = "Conveyor Drive Motor 1/2 HP", Quantity = 1, UnitPrice = 312.00m, Amount = 312.00m, DisplayOrder = 1 }, + new BillLineItem { AccountId = equipRepairsAccount?.Id, Description = "Drive Chain Sprocket (2-pack)", Quantity = 1, UnitPrice = 180.00m, Amount = 180.00m, DisplayOrder = 2 } + } + }); + // Electric — current month (open) await AddBill(new Bill { diff --git a/src/PowderCoating.Infrastructure/Services/SeedDataService.Invoices.cs b/src/PowderCoating.Infrastructure/Services/SeedDataService.Invoices.cs index 52176c2..46e2edc 100644 --- a/src/PowderCoating.Infrastructure/Services/SeedDataService.Invoices.cs +++ b/src/PowderCoating.Infrastructure/Services/SeedDataService.Invoices.cs @@ -213,12 +213,20 @@ public partial class SeedDataService await Inv(InvoiceStatus.Sent, 6, 30, 0m, "Net 30", "Payment due within 30 days."); // ── Current month (1 overdue + 2 sent + 1 draft) ───────────────────── - // Overdue: created 35 days ago on Net 14 terms → 21 days past due + // Overdue: created 35 days ago on Net 14 terms → 21 days past due (1–30 bucket) await Inv(InvoiceStatus.Sent, 35, 14, 7.5m, "Net 14", "PAST DUE — please remit payment immediately."); await Inv(InvoiceStatus.Sent, 4, 30, 0m, "Net 30", "Payment due within 30 days."); await Inv(InvoiceStatus.Sent, 2, 30, 7.5m, "Net 30", "Payment due within 30 days."); await Inv(InvoiceStatus.Draft, 1, 30, 0m, "Net 30", null); + // ── AR Aging demo invoices — populate all four overdue buckets ──────── + // 31–60 day bucket: issued 55 days ago, Net 10 → 45 days past due + await Inv(InvoiceStatus.Sent, 55, 10, 0m, "Net 10", "PAST DUE 45 days — second notice sent."); + // 61–90 day bucket: issued 80 days ago, Net 10 → 70 days past due + await Inv(InvoiceStatus.Sent, 80, 10, 7.5m, "Net 10", "PAST DUE 70 days — final notice. Collections pending."); + // 90+ day bucket: issued 120 days ago, Net 14 → 106 days past due + await Inv(InvoiceStatus.PartiallyPaid, 120, 14, 0m, "Net 14", "PAST DUE 106 days — partial payment received, balance outstanding."); + return seeded; } } diff --git a/src/PowderCoating.Infrastructure/Services/SeedDataService.PurchaseOrders.cs b/src/PowderCoating.Infrastructure/Services/SeedDataService.PurchaseOrders.cs new file mode 100644 index 0000000..21553bc --- /dev/null +++ b/src/PowderCoating.Infrastructure/Services/SeedDataService.PurchaseOrders.cs @@ -0,0 +1,228 @@ +using Microsoft.EntityFrameworkCore; +using PowderCoating.Core.Entities; +using PowderCoating.Core.Enums; + +namespace PowderCoating.Infrastructure.Services; + +public partial class SeedDataService +{ + /// + /// Seeds 7 purchase orders across three vendors covering a 3-month window: + /// 3 Received (historical), 2 Submitted (in-flight), and 2 Draft (pending approval). + /// This gives every PO status a visible example for demo walkthroughs. + /// + /// + /// Vendors are resolved by partial name match against the company's vendor list — the + /// same approach used by . PO numbers follow the convention + /// PO-YYMM-#### stamped at seed time. + /// + /// Received POs link back to the that was created after receipt when + /// one with a matching vendor invoice number exists; otherwise BillId is left null + /// so the PO still seeds cleanly even if bills were skipped. + /// + /// Idempotency: returns 0 immediately if any purchase orders already exist for the company. + /// + /// The tenant company to seed purchase orders for. + /// Number of PO records inserted, or 0 if already seeded. + private async Task SeedPurchaseOrdersAsync(Company company) + { + var existingCount = await _context.Set() + .IgnoreQueryFilters() + .CountAsync(p => p.CompanyId == company.Id && !p.IsDeleted); + + if (existingCount > 0) + return 0; + + var vendors = await _context.Set() + .IgnoreQueryFilters() + .Where(v => v.CompanyId == company.Id && !v.IsDeleted) + .ToListAsync(); + + if (vendors.Count == 0) + return 0; + + var prismatic = vendors.FirstOrDefault(v => v.CompanyName.Contains("Prismatic")) ?? vendors.First(); + var columbia = vendors.FirstOrDefault(v => v.CompanyName.Contains("Columbia")) ?? vendors.First(); + var harbor = vendors.FirstOrDefault(v => v.CompanyName.Contains("Harbor")) ?? vendors.First(); + var grainger = vendors.FirstOrDefault(v => v.CompanyName.Contains("Grainger")) ?? vendors.First(); + var localSupply = vendors.FirstOrDefault(v => v.CompanyName.Contains("Local")) ?? vendors.First(); + + // Resolve inventory item IDs for PO line items (optional — may be null if inventory + // wasn't seeded yet; the PO seeds cleanly either way using the Description field). + var glossBlack = await _context.Set().IgnoreQueryFilters() + .FirstOrDefaultAsync(i => i.CompanyId == company.Id && i.SKU.EndsWith("-PWD-GBK-001") && !i.IsDeleted); + var matteBlack = await _context.Set().IgnoreQueryFilters() + .FirstOrDefaultAsync(i => i.CompanyId == company.Id && i.SKU.EndsWith("-PWD-MBK-001") && !i.IsDeleted); + var superChrome = await _context.Set().IgnoreQueryFilters() + .FirstOrDefaultAsync(i => i.CompanyId == company.Id && i.SKU.EndsWith("-PWD-CHR-001") && !i.IsDeleted); + var candyRed = await _context.Set().IgnoreQueryFilters() + .FirstOrDefaultAsync(i => i.CompanyId == company.Id && i.SKU.EndsWith("-PWD-CRD-001") && !i.IsDeleted); + var blastMedia = await _context.Set().IgnoreQueryFilters() + .FirstOrDefaultAsync(i => i.CompanyId == company.Id && i.SKU.EndsWith("-BLM-001") && !i.IsDeleted); + + var now = DateTime.UtcNow; + var pfx = $"PO-{now:yy}{now.Month:D2}-"; + var seq = 1; + var seeded = 0; + + async Task AddPO(PurchaseOrder po) + { + po.PoNumber = $"{pfx}{seq++:D4}"; + po.CompanyId = company.Id; + foreach (var item in po.Items) + { + item.CompanyId = company.Id; + item.CreatedAt = po.OrderDate; + } + await _context.Set().AddAsync(po); + await _context.SaveChangesAsync(); + seeded++; + return po; + } + + // ── RECEIVED (historical — tied to bills already in the system) ─────── + + // PO-1: Prismatic Powders — powder restock 3 months ago (Received, matches bill PP-77211) + await AddPO(new PurchaseOrder + { + VendorId = prismatic.Id, + Status = PurchaseOrderStatus.Received, + OrderDate = now.AddDays(-95), + ExpectedDeliveryDate = now.AddDays(-80), + ReceivedDate = now.AddDays(-82), + SubTotal = 1_145.00m, + TotalAmount = 1_145.00m, + Notes = "Quarterly powder restock — Q1", + CreatedAt = now.AddDays(-95), + Items = + { + new PurchaseOrderItem { InventoryItemId = matteBlack?.Id, Description = "Matte Black Powder — 50 lb bags", UnitOfMeasure = "bag", QuantityOrdered = 2, QuantityReceived = 2, UnitCost = 178.00m, LineTotal = 356.00m }, + new PurchaseOrderItem { InventoryItemId = glossBlack?.Id, Description = "Gloss Black Powder — 50 lb bags", UnitOfMeasure = "bag", QuantityOrdered = 2, QuantityReceived = 2, UnitCost = 165.00m, LineTotal = 330.00m }, + new PurchaseOrderItem { Description = "Satin Silver Powder — 25 lb bags", UnitOfMeasure = "bag", QuantityOrdered = 2, QuantityReceived = 2, UnitCost = 144.50m, LineTotal = 289.00m }, + new PurchaseOrderItem { Description = "Masking Tape & Plugs Kit", UnitOfMeasure = "kit", QuantityOrdered = 1, QuantityReceived = 1, UnitCost = 170.00m, LineTotal = 170.00m } + } + }); + + // PO-2: Columbia Coatings — specialty colors 2 months ago (Received, matches bill CC-4401) + await AddPO(new PurchaseOrder + { + VendorId = columbia.Id, + Status = PurchaseOrderStatus.Received, + OrderDate = now.AddDays(-70), + ExpectedDeliveryDate = now.AddDays(-58), + ReceivedDate = now.AddDays(-60), + SubTotal = 986.00m, + TotalAmount = 986.00m, + Notes = "Specialty metallic & candy colors order", + CreatedAt = now.AddDays(-70), + Items = + { + new PurchaseOrderItem { InventoryItemId = candyRed?.Id, Description = "Candy Red Metallic — 10 lb bags", UnitOfMeasure = "bag", QuantityOrdered = 3, QuantityReceived = 3, UnitCost = 145.00m, LineTotal = 435.00m }, + new PurchaseOrderItem { InventoryItemId = superChrome?.Id, Description = "Chrome Effect Powder — 10 lb bags", UnitOfMeasure = "bag", QuantityOrdered = 2, QuantityReceived = 2, UnitCost = 168.00m, LineTotal = 336.00m }, + new PurchaseOrderItem { Description = "Hammertone Bronze — 10 lb bags", UnitOfMeasure = "bag", QuantityOrdered = 1, QuantityReceived = 1, UnitCost = 150.50m, LineTotal = 150.50m }, + new PurchaseOrderItem { Description = "Ground Straps & Hooks", UnitOfMeasure = "lot", QuantityOrdered = 1, QuantityReceived = 1, UnitCost = 64.50m, LineTotal = 64.50m } + } + }); + + // PO-3: Harbor Freight Tools — consumables 6 weeks ago (Received, matches bill HBF-18822 timing) + await AddPO(new PurchaseOrder + { + VendorId = harbor.Id, + Status = PurchaseOrderStatus.Received, + OrderDate = now.AddDays(-48), + ExpectedDeliveryDate = now.AddDays(-40), + ReceivedDate = now.AddDays(-42), + SubTotal = 412.50m, + TotalAmount = 412.50m, + Notes = "Shop consumables & hardware restock", + CreatedAt = now.AddDays(-48), + Items = + { + new PurchaseOrderItem { Description = "J-Hook Hangers Assortment", UnitOfMeasure = "pkg", QuantityOrdered = 2, QuantityReceived = 2, UnitCost = 89.75m, LineTotal = 179.50m }, + new PurchaseOrderItem { Description = "Masking Caps — Mixed (100-pack)", UnitOfMeasure = "box", QuantityOrdered = 2, QuantityReceived = 2, UnitCost = 60.00m, LineTotal = 120.00m }, + new PurchaseOrderItem { Description = "Wire Brushes & Abrasives", UnitOfMeasure = "lot", QuantityOrdered = 1, QuantityReceived = 1, UnitCost = 113.00m, LineTotal = 113.00m } + } + }); + + // ── SUBMITTED (in-flight — awaiting delivery) ───────────────────────── + + // PO-4: Grainger Industrial Supply — safety equipment & filter replacement (matches open GRG-7714 partial bill) + await AddPO(new PurchaseOrder + { + VendorId = grainger.Id, + Status = PurchaseOrderStatus.Submitted, + OrderDate = now.AddDays(-14), + ExpectedDeliveryDate = now.AddDays(3), + SubTotal = 648.00m, + TotalAmount = 648.00m, + Notes = "Blast room filter replacement + safety restocking", + CreatedAt = now.AddDays(-14), + Items = + { + new PurchaseOrderItem { Description = "HEPA Filter Cartridges — 12-pack", UnitOfMeasure = "box", QuantityOrdered = 2, QuantityReceived = 0, UnitCost = 189.00m, LineTotal = 378.00m }, + new PurchaseOrderItem { Description = "Blast Nozzle Tungsten — 3/8\"", UnitOfMeasure = "ea", QuantityOrdered = 2, QuantityReceived = 0, UnitCost = 85.00m, LineTotal = 170.00m }, + new PurchaseOrderItem { Description = "Safety Respirators (10-pack)", UnitOfMeasure = "box", QuantityOrdered = 1, QuantityReceived = 0, UnitCost = 100.00m, LineTotal = 100.00m } + } + }); + + // PO-5: Prismatic Powders — current month powder order (matches open bill PP-88530) + await AddPO(new PurchaseOrder + { + VendorId = prismatic.Id, + Status = PurchaseOrderStatus.Submitted, + OrderDate = now.AddDays(-8), + ExpectedDeliveryDate = now.AddDays(7), + SubTotal = 1_050.00m, + TotalAmount = 1_050.00m, + Notes = "June powder restock — Matte Black + seasonal Gloss Red", + CreatedAt = now.AddDays(-8), + Items = + { + new PurchaseOrderItem { InventoryItemId = matteBlack?.Id, Description = "Matte Black Powder — 25 lb bags", UnitOfMeasure = "bag", QuantityOrdered = 6, QuantityReceived = 0, UnitCost = 89.00m, LineTotal = 534.00m }, + new PurchaseOrderItem { Description = "Gloss Red Powder — 10 lb bags", UnitOfMeasure = "bag", QuantityOrdered = 2, QuantityReceived = 0, UnitCost = 132.00m, LineTotal = 264.00m }, + new PurchaseOrderItem { Description = "Hanging Racks (10-pack)", UnitOfMeasure = "pkg", QuantityOrdered = 2, QuantityReceived = 0, UnitCost = 126.00m, LineTotal = 252.00m } + } + }); + + // ── DRAFT (pending review / approval) ───────────────────────────────── + + // PO-6: Harbor Freight Tools — shop tools pending manager approval + await AddPO(new PurchaseOrder + { + VendorId = harbor.Id, + Status = PurchaseOrderStatus.Draft, + OrderDate = now.AddDays(-3), + ExpectedDeliveryDate = now.AddDays(10), + SubTotal = 318.75m, + TotalAmount = 318.75m, + Notes = "Monthly hardware restock — needs approval before submit", + InternalNotes = "Manager review requested: higher than normal month due to extra hooks for upcoming Apex Motorsports batch.", + CreatedAt = now.AddDays(-3), + Items = + { + new PurchaseOrderItem { Description = "Hanging Racks & J-Hooks", UnitOfMeasure = "pkg", QuantityOrdered = 1, QuantityReceived = 0, UnitCost = 198.75m, LineTotal = 198.75m }, + new PurchaseOrderItem { Description = "Masking Caps — Mixed (100-pack)", UnitOfMeasure = "box", QuantityOrdered = 2, QuantityReceived = 0, UnitCost = 60.00m, LineTotal = 120.00m } + } + }); + + // PO-7: Local Industrial Supply — blast media restock (inventory currently at zero) + await AddPO(new PurchaseOrder + { + VendorId = localSupply.Id, + Status = PurchaseOrderStatus.Draft, + OrderDate = now.AddDays(-1), + ExpectedDeliveryDate = now.AddDays(5), + SubTotal = 385.00m, + TotalAmount = 385.00m, + Notes = "URGENT — blast media out of stock, production blocked on Pressure Pot Blaster", + InternalNotes = "Rush order requested; call LIS rep for same-week delivery.", + CreatedAt = now.AddDays(-1), + Items = + { + new PurchaseOrderItem { InventoryItemId = blastMedia?.Id, Description = "Aluminum Oxide #80 Grit — 100 lb bag", UnitOfMeasure = "bag", QuantityOrdered = 5, QuantityReceived = 0, UnitCost = 77.00m, LineTotal = 385.00m } + } + }); + + return seeded; + } +} diff --git a/src/PowderCoating.Infrastructure/Services/SeedDataService.Remove.cs b/src/PowderCoating.Infrastructure/Services/SeedDataService.Remove.cs index 4e14328..85fc858 100644 --- a/src/PowderCoating.Infrastructure/Services/SeedDataService.Remove.cs +++ b/src/PowderCoating.Infrastructure/Services/SeedDataService.Remove.cs @@ -342,6 +342,34 @@ public partial class SeedDataService } } + // --- Purchase Orders (removed alongside bills — tightly coupled in demo workflows) --- + if (options.Bills) + { + var poIds = await _context.Set() + .IgnoreQueryFilters() + .Where(p => p.CompanyId == companyId) + .Select(p => p.Id) + .ToListAsync(); + + if (poIds.Any()) + { + var poItems = await _context.Set() + .IgnoreQueryFilters() + .Where(i => poIds.Contains(i.PurchaseOrderId)) + .ToListAsync(); + if (poItems.Any()) _context.Set().RemoveRange(poItems); + + var pos = await _context.Set() + .IgnoreQueryFilters() + .Where(p => poIds.Contains(p.Id)) + .ToListAsync(); + _context.Set().RemoveRange(pos); + totalRemoved += pos.Count; + details.Add($"✓ Removed {pos.Count} purchase order(s)"); + await _context.SaveChangesAsync(); + } + } + // --- Vendor Bills (all bills for the company) --- if (options.Bills) { diff --git a/src/PowderCoating.Infrastructure/Services/SeedDataService.cs b/src/PowderCoating.Infrastructure/Services/SeedDataService.cs index e5217de..b70cd78 100644 --- a/src/PowderCoating.Infrastructure/Services/SeedDataService.cs +++ b/src/PowderCoating.Infrastructure/Services/SeedDataService.cs @@ -416,6 +416,7 @@ public partial class SeedDataService : ISeedDataService await RunSeeder("Equipment", details, errors, result, () => SeedEquipmentAsync(company)); await RunSeeder("Maintenance", details, errors, result, () => SeedMaintenanceRecordsAsync(company)); await RunSeeder("Vendors", details, errors, result, () => SeedVendorsAsync(company)); + await RunSeeder("Purchase orders", details, errors, result, () => SeedPurchaseOrdersAsync(company)); await RunSeeder("Named ovens", details, errors, result, () => SeedOvenCostsAsync(company)); await RunSeeder("Catalog", details, errors, result, () => SeedCatalogAsync(company)); await RunSeeder("Quotes", details, errors, result, () => SeedQuotesAsync(company));