# 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 --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`, `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`) ```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 1–3 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`** — `—` not `—`, `×` not `×`, `…` 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`.