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));