Files
PowderCoatingLogix/CLAUDE.md
T
spouliot 8f11e00a0a Merge duplicate powder lines on dashboard order queue
When multiple jobs need the same powder, the 'Powder in Queue to be
Ordered' panel now collapses them into a single line (summed lbs) rather
than showing one row per coat. 'Mark as Ordered' marks all contributing
coats at once and injects each into the 'Awaiting Receipt' panel
individually so per-coat receiving still works unchanged.

- Add PowderOrderJobRefDto; PowderOrderLineDto gains CoatIds + Jobs lists
  (scalar CoatId/JobId/etc. become computed accessors for backward compat)
- MapPowderOrderGroupsMerged: secondary GroupBy on (ColorName, ColorCode,
  Finish, SKU) within vendor group for the 'needed' panel
- MapPowderOrderGroups kept per-coat for the 'awaiting receipt' panel
- MarkPowderOrdered accepts comma-separated coatIds, returns coats array
- Dashboard view: Customer column loops job refs for merged rows; JS posts
  coatIds and iterates data.coats to populate awaiting-receipt panel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 12:59:10 -04:00

215 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md
Guidance for Claude Code when working in this repository.
## Project Overview
ASP.NET Core 8.0 MVC application for powder coating business operations. Clean Architecture:
**Core** (entities/interfaces) → **Application** (DTOs/profiles) → **Infrastructure** (EF/repos/services) + **Web** (Razor MVC) + **Api** (REST/JWT).
## Essential Commands
```bash
# Build
dotnet build
# Web MVC — https://localhost:58461
cd src/PowderCoating.Web && dotnet run
# API — Swagger at root URL
cd src/PowderCoating.Api && dotnet run
# Tests
dotnet test
dotnet test tests/PowderCoating.UnitTests
dotnet test tests/PowderCoating.IntegrationTests
```
### Database (EF Core)
Run from `src/PowderCoating.Web`. **Always include `--context ApplicationDbContext`** — multiple DbContexts exist; omitting it throws.
```bash
cd src/PowderCoating.Web
dotnet ef migrations add <Name> --project ../PowderCoating.Infrastructure --context ApplicationDbContext
dotnet ef database update --project ../PowderCoating.Infrastructure --context ApplicationDbContext
dotnet ef migrations remove --project ../PowderCoating.Infrastructure --context ApplicationDbContext
dotnet ef migrations list --project ../PowderCoating.Infrastructure --context ApplicationDbContext
dotnet ef database drop --project ../PowderCoating.Infrastructure --context ApplicationDbContext
```
### Default Credentials
```
SuperAdmin (break glass): artemis@powdercoatinglogix.com / SuperAdmin123!
SuperAdmin (seed): superadmin@powdercoatinglogix.com / SuperAdmin123!
SuperAdmin (seed): spouliot@powdercoatinglogix.com / SuperAdmin123!
Company Admin (seed): demo@powdercoatinglogix.com / CompanyAdmin123!
```
## Architecture
### Layers
- **Core** — Entities, enums, repository + service interfaces. `BaseEntity` provides `Id`, `CompanyId`, `CreatedAt`, `UpdatedAt`, `IsDeleted`, audit fields on every entity.
- **Application** — DTOs, AutoMapper profiles (auto-discovered via `cfg.AddMaps()`; `PricingTierProfile` is an exception — registered manually in `Program.cs`), service interfaces. No UI/infra deps.
- **Infrastructure** — `ApplicationDbContext`, `Repository<T>`, `UnitOfWork`. Seed data is **manual only** via Platform Management → Seed Data.
- **Web** — Razor MVC + Bootstrap 5. **Api** — JWT Bearer, Swagger.
### Global Query Filters (always active)
- Soft deletes: `IsDeleted == false`
- Multi-tenancy: non-SuperAdmin sees only their `CompanyId`
- Bypass: `ignoreQueryFilters: true` on repository methods
**Critical:** global filters are not sufficient on their own. Every `FindAsync`/`GetAllAsync` in a controller must also include an explicit `CompanyId == currentCompanyId` predicate — defense in depth.
## Data Access Rules (ENFORCE THESE)
> **`ApplicationDbContext` is NEVER injected into a controller.**
> All data access goes through `IUnitOfWork`. Enforced at startup by `EnforceDataAccessArchitecture()` in `Program.cs`.
> Full rationale + permanent exceptions: `docs/DATA_ACCESS_ARCHITECTURE.md`
### Three tiers — use the right one:
**Tier 1 — Simple CRUD**`IUnitOfWork.EntityName` (generic `IRepository<T>`)
```csharp
var items = await _unitOfWork.CatalogItems.GetAllAsync();
await _unitOfWork.Announcements.AddAsync(entity);
await _unitOfWork.CompleteAsync();
```
**Tier 2 — Complex domain queries** → typed repositories on `IUnitOfWork`
```csharp
var job = await _unitOfWork.Jobs.LoadForDetailsAsync(id);
var invoice = await _unitOfWork.Invoices.LoadForViewAsync(id);
var quote = await _unitOfWork.Quotes.GetByApprovalTokenAsync(token);
```
Typed repos: `IJobRepository`, `IInvoiceRepository`, `IQuoteRepository`, `ICustomerRepository`, `IBillRepository`, `IPurchaseOrderRepository`
— defined in `Core/Interfaces/Repositories/`, implemented in `Infrastructure/Repositories/`.
**Tier 3 — Aggregate/reporting** → injected read services
```csharp
var aging = await _financialReports.GetArAgingAsync(companyId);
```
Services: `IFinancialReportService`, `IOperationalReportService`.
### Permanent exceptions (ApplicationDbContext allowed — intentional):
`StripeWebhookController`, `WebhooksController`, `PaymentController`, `RegistrationController`,
`DataExportController`, `AccountDataExportController`, `DataPurgeController`,
`SystemInfoController`, `SystemLogsController`, `CompanyHealthController`
---
## Domain Concepts
### Job Lifecycle
16 statuses in `JobStatusLookup` **table — NOT an enum**: Pending → Quoted → Approved → InPreparation → Sandblasting → MaskingTaping → Cleaning → InOven → Coating → Curing → QualityCheck → Completed → ReadyForPickup → Delivered | OnHold | Cancelled.
Use `.Include(j => j.JobStatus)` and filter on `!j.JobStatus.IsTerminalStatus`.
**Priorities**: Low, Normal, High, Urgent, Rush (color-coded in UI).
### Customers
- **Commercial**: B2B, pricing tiers, credit limits
- **Non-Commercial**: individual/residential
### Inventory
Transactions tracked in `InventoryTransaction` (Purchase, Sale, Adjustment, Transfer, Return, Waste, Initial). Reorder points trigger alerts.
### Equipment & Maintenance
Equipment: Operational, NeedsMaintenance, UnderMaintenance, OutOfService, Retired.
Maintenance priority: Low/Normal/High/Critical. Status: Scheduled/InProgress/Completed/Cancelled/Overdue.
---
## Pricing
### Key Rules
- Custom powder (no inventory item + `PowderToOrder > 0`): charge for the **full ordered quantity**
- In-stock powder: charge for calculated usage only (surface area × lbs/sqft × unit cost)
- Tax-exempt customers (`Customer.IsTaxExempt`): `TaxPercent` defaults to 0 on quote/invoice create; marked ★ in dropdowns
### Pricing Routing Flags — Must Stay In Sync Across All Three Layers
`PricingCalculationService.CalculateQuoteItemPriceAsync` routes via boolean flags. **Must exist identically on `QuoteItem`, `JobItem`, and `CreateQuoteItemDto`, mapped in all three `JobItemAssemblyService.CreateJobItem` overloads.**
| Flag | Effect if missing on JobItem |
|------|------------------------------|
| `IsAiItem` | Job repriced as calculated item; oven cost double-charged on every save |
| `IsGenericItem` | ManualUnitPrice ignored; price recalculated from surface area |
| `IsLaborItem` | Item repriced at surface-area rate instead of hours × labor rate |
| `IsSalesItem` | ManualUnitPrice ignored; item repriced using coat/surface math |
**Checklist when adding a new flag:**
1. Add to `QuoteItem` (Core/Entities)
2. Add to `JobItem` (Core/Entities)
3. Add to `CreateQuoteItemDto` (Application/DTOs)
4. Add to `JobItemSeed` (private class in `JobItemAssemblyService`)
5. Map in all three `JobItemAssemblyService.CreateJobItem` overloads
6. Include in every `existingItemsData` JSON block in `Edit.cshtml`, `EditItems.cshtml`, and all controller actions that build `CreateQuoteItemDto` from a `JobItem`
7. Add migration if field is new on a persisted entity
8. Structural test `PricingRoutingFlags_ExistOnBothQuoteItemAndJobItem` fails until steps 13 are done — intentional
---
## Configuration
### Key Settings (`src/PowderCoating.Web/appsettings.json`)
- DB: `ConnectionStrings:DefaultConnection` (SQL Server Express)
- AI: `AI:Anthropic:ApiKey`**Anthropic Claude `claude-sonnet-4-6`, NOT OpenAI**
- Ports: HTTPS 58461 / HTTP 58462
### Auth & Roles
- Web: cookie-based ASP.NET Identity. API: JWT Bearer.
- System roles: SuperAdmin, Administrator, Manager, Employee, ShopFloor, ReadOnly
- Policies in `AppConstants.cs`: `RequireAdministratorRole`, `CanManageJobs`, `CanManageInventory`, `CanManageUsers`, `CanViewData`, `CompanyAdminOnly`
- **PricingTiers use `CompanyAdminOnly` — NOT `RequireAdministratorRole`** (that policy is unregistered and will throw)
### File Uploads
Limits in `AppConstants.cs`: 10 MB max, allowed: jpg/jpeg/png/gif/pdf/doc/docx/xls/xlsx.
---
## UI Rules
- **HTML entities in `.cshtml`** — `&mdash;` not `—`, `&times;` not `×`, `&hellip;` not `…`. Literal Unicode gets corrupted by AI tools + Windows file encoding.
- **External JS files only** — put scripts in `wwwroot/js/*.js`, reference via `src=`. Inline `@section Scripts` blocks can silently fail with SyntaxErrors from layout HTML context.
- **`alert-permanent` CSS class** — `_Layout` auto-dismisses `.alert:not(.alert-permanent)` after ~5s. Any non-toast alert that must persist needs this class.
- **SignalR hubs already in place**: `NotificationHub``/hubs/notifications` (company-scoped), `ShopHub``/hubs/shop` (shop floor).
---
## Gotchas
- **Two data export controllers**: `DataExportController` (SuperAdmin) and `AccountDataExportController` (company self-service). When changing CSV columns, fix **both**.
- **Help docs**: when a feature changes, update both `HelpKnowledgeBase.cs` (AI assistant knowledge) and the matching article in `Views/Help/` (human-readable help center).
- **Demo reset**: `DemoController.ResetDemoData` is gated on `company.CompanyCode == "DEMO"` — only the demo tenant can trigger a reset. ForceRemoveAll wipes all company data before reseeding.
- **artemis@ account**: the "break glass" root SuperAdmin — guards in `PlatformUsersController` protecting it are intentional, never remove them.
---
## Implemented Modules
All fully implemented with controllers, views, and migrations applied.
**Operations**: Jobs (16 statuses, worker assignment, time entries, rework, shop codes, templates) · Quotes (AI Photo Quoting via Anthropic, quote→job conversion, customer approval portal) · Invoices (1:1 Job→Invoice unique index; partial payments, void, PDF, email) · Deposits (auto-applied on invoice create; QuestPDF receipt) · Customers (commercial/non-commercial, pricing tiers, tax-exempt + cert upload) · Oven Scheduler (named ovens, capacity, suggested batches)
**Inventory & Purchasing**: Inventory (transactions, reorder alerts, powder coverage/efficiency) · Vendors · Purchase Orders (create/submit/receive, convert to bills) · Accounts Payable (bills, AP ledger, payment tracking)
**Shop Management**: Shop Workers (roles, job/maintenance assignment) · Equipment & Maintenance · Catalog Items · Pricing Tiers
**Billing**: Stripe (subscriptions, checkout sessions, webhooks `/stripe/webhook`) · Stripe Connect (embedded payments, OAuth) · Twilio SMS (`ISmsService`; webhook `POST /Webhooks/TwilioSms`)
**Platform (SuperAdmin)**: Platform Users · Companies · Seed Data (manual only) · Subscription Plans (`SubscriptionPlanConfig`)
**Other**: Help Center (14 articles at `Views/Help/`) · Setup Wizard (10-step, `SetupWizardController`) · Reports (24 actions: P&L, AR Aging, Powder Usage, Cycle Time, PDF exports) · Gift Certificates · Announcements · In-App Notification Bell · Passkey/Biometric Login (WebAuthn, Fido2NetLib) · Customer Intake Kiosk (iPad, SignalR push, `KioskSession`) · AI Accounting Features (receipt scan, AR follow-up, smart categorization, cash flow forecast, anomaly detection)
---
## Branding
- App name: **Powder Coating Logix**
- PCL logo: `wwwroot/images/pcl-logo.png` — sidebar header (when no tenant logo), login/register, sidebar footer (always)
- Sidebar footer always links to `http://www.powdercoatinglogix.com`
- Tenant logos: Azure Blob `companylogos` container; replaces PCL logo in sidebar header only
## Active Design Work
A visual redesign is in progress. For UI changes, dashboard/jobs/board styling, or design tokens: read `design_handoff_pcl_redesign/README.md` and follow `design_handoff_pcl_redesign/CLAUDE.md`.