diff --git a/.claude/skills/radzen-blazor/SKILL.md b/.claude/skills/radzen-blazor/SKILL.md index a87ef23..ec6dc8a 100644 --- a/.claude/skills/radzen-blazor/SKILL.md +++ b/.claude/skills/radzen-blazor/SKILL.md @@ -353,6 +353,25 @@ Powerful grid with sorting, filtering, paging, editing, selection, grouping. ``` +**⚠️ TItem="object" Pitfall:** +When using `TItem="object"` for dynamic entity grids, `Property` binding does NOT work (causes blank cells). Use `Template` with reflection instead: +```razor + + + + + + + + +@code { + object? GetPropertyValue(object? obj, string propertyName) => + obj?.GetType().GetProperty(propertyName)?.GetValue(obj); +} +``` + +For dynamic entities with many columns (200+), use **SmartDataGridObject** component which handles reflection and auto-limits visible columns. + See [references/components-quick-ref.md](references/components-quick-ref.md) for complete DataGrid reference. ### RadzenDataList diff --git a/.claude/skills/radzen-blazor/references/best-practices.md b/.claude/skills/radzen-blazor/references/best-practices.md index cfaee2c..2689b9f 100644 --- a/.claude/skills/radzen-blazor/references/best-practices.md +++ b/.claude/skills/radzen-blazor/references/best-practices.md @@ -113,6 +113,76 @@ Template columns disable some optimizations. Use Property when possible: ``` +### Avoid TItem="object" with Property Binding +**Critical gotcha:** Radzen's `Property` attribute uses compiled expressions that require knowing the type at compile time. When `TItem="object"`, the Property binding fails silently (blank cells): + +```razor +@* BAD - Property binding doesn't work with TItem="object" *@ + + + + + + +@* GOOD - Use Template with reflection for object-typed data *@ + + + + + + + + +@code { + object? GetPropertyValue(object? obj, string propertyName) + { + if (obj == null) return null; + return obj.GetType() + .GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance) + ?.GetValue(obj); + } +} + +@* BEST - Use strongly-typed generic when possible *@ + + + + + +``` + +**When you must use `TItem="object"`** (e.g., dynamic entity grids): +- Use `Template` instead of `Property` for column content +- Access values via reflection in the template +- Note: Sorting/filtering may not work without Property binding + +### Limit Visible Columns for Wide Tables +Tables with many columns (50+) can overwhelm browsers and users. Use column limiting: + +```razor +@* SmartDataGridObject automatically limits to 15 columns by default *@ + @* Show 20 columns *@ + +@* Set to 0 to show all columns (not recommended for 100+ columns) *@ + +``` + +**Column prioritization (automatic):** +1. ID columns (e.g., `item_id`, `Id`) - shown first +2. Code/number columns (e.g., `item_code`, `product_code`) +3. Name/description columns (e.g., `item_name`, `description`) +4. Status/active columns (e.g., `is_active`, `is_enabled`) +5. All other columns alphabetically + +**When to override defaults:** +- Use `ColumnOverrides` parameter for full control over which columns appear +- Increase `MaxVisibleColumns` if users need more fields visible +- For reporting/export, consider a separate view with all columns + ### Reuse Grid References Store grid reference for programmatic control: @@ -757,6 +827,8 @@ Use Radzen components, avoid raw HTML injection: - [ ] Add `` in MainLayout - [ ] Use interactive render mode for components with events - [ ] Use server-side loading for large DataGrids +- [ ] **Avoid `TItem="object"` with Property binding** - use Template with reflection instead +- [ ] **Limit visible columns for wide tables** (50+ columns) - use MaxVisibleColumns - [ ] Validate forms before submission - [ ] Handle dialog results (check for null) - [ ] Use appropriate notification durations and severities diff --git a/.claude/skills/radzen-blazor/references/components-quick-ref.md b/.claude/skills/radzen-blazor/references/components-quick-ref.md index 692e467..e56385a 100644 --- a/.claude/skills/radzen-blazor/references/components-quick-ref.md +++ b/.claude/skills/radzen-blazor/references/components-quick-ref.md @@ -92,6 +92,58 @@ --- +## SmartDataGridObject (Project Component) + +Custom component for dynamic entity grids with `TItem="object"`. Handles reflection-based property access and auto-limits columns for wide tables (200+ columns). + +### Key Parameters +- `Data` - IEnumerable data source +- `MaxVisibleColumns` - Max columns to display (default: 15, set to 0 for unlimited) +- `ColumnOverrides` - Custom column configuration (IEnumerable) +- `AllowFiltering` - Enable filtering (default: true) +- `AllowSorting` - Enable sorting (default: true) +- `AllowPaging` - Enable pagination (default: true) +- `AllowInlineEdit` - Enable inline editing (default: false) +- `AllowEdit` - Show edit button in actions column (default: false) +- `AllowDelete` - Show delete button in actions column (default: false) +- `ShowActionColumn` - Show actions column (default: false) +- `OnRowUpdate`, `OnRowCreate`, `OnRowDelete`, `OnRowEdit` - Event callbacks + +### Usage +```razor +@* Basic usage - auto-discovers columns, limits to 15 *@ + + +@* Show more columns *@ + + +@* Show all columns (use with caution for wide tables) *@ + + +@* With editing *@ + +``` + +### Column Prioritization (Automatic) +When limiting columns, SmartDataGridObject prioritizes: +1. ID columns (`_id`, `Id`) - shown first +2. Code/number columns (`code`, `codenum`, `number`) +3. Name/description columns (`name`, `descrip`, `title`) +4. Status columns (`active`, `status`, `enabled`) +5. All other columns alphabetically + +### Footer Display +Shows row count and hidden column info: +``` +Showing 3 records (15 of 265 columns shown) +``` + +--- + ## RadzenStack ### Parameters diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ab69c70 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,26 @@ +# Developer-local config files (contain localhost connection strings - must not override Docker env vars) +appsettings.Local.json +appsettings.*.Local.json +.env.local +.envrc +.env + +# Secrets +secrets.json +*.pfx +*.key + +# Build artifacts (Docker build stage handles these itself) +**/bin/ +**/obj/ + +# Test projects (not needed in production image) +tests/ + +# Documentation +*.md +DOCKER_SETUP_CONTEXT.md + +# Git +.git/ +.gitignore diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..122b089 --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +# SQL Server password for Docker Compose +# Change this to a secure password! +SA_PASSWORD=YourStrongPassword123! + +# Optional: Override ASPNETCORE_ENVIRONMENT +# ASPNETCORE_ENVIRONMENT=Production + +# Optional: Override app ports (default: 7012 for HTTPS, 5210 for HTTP) +# Leave blank to use defaults diff --git a/.gitignore b/.gitignore index 6522e8f..a03abaa 100644 --- a/.gitignore +++ b/.gitignore @@ -463,8 +463,9 @@ Thumbs.db ehthumbs.db ehthumbs_vista.db -# Dump file +# Dump type files *.stackdump +*.patch # Folder config file [Dd]esktop.ini @@ -490,8 +491,13 @@ $RECYCLE.BIN/ # Project and App Specific .env.local +appsettings.Local.json tmp/ specs/ +dotnetwebapp.conf +dotnetwebapp.crt +dotnetwebapp.key +dev.log # Generated YAML files (regenerated from pipeline) app.yaml data.yaml diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 7d0170f..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,64 +0,0 @@ -# Repository Guidelines - -## Project Structure & Module Organization - -- `Components/`, `Pages/`, `Shared/`: Blazor UI components and layouts. -- `Controllers/`: Web API endpoints (generic and entity controllers). -- `Services/`: Business logic and DI services. -- `Data/`: `AppDbContext`, tenancy helpers, and EF configuration. -- `DdlParser/`: SQL DDL → `app.yaml` converter used in the pipeline. -- `DotNetWebApp.Models/`: Separate models assembly containing all data models, configuration classes, and YAML model classes. -- `DotNetWebApp.Models/Generated/`: Auto-generated entity types from `ModelGenerator`. -- `DotNetWebApp.Models/AppDictionary/`: YAML model classes for app.yaml structure. -- `ModelGenerator/`: Reads `app.yaml` and produces generated models. -- `Migrations/`: Generated EF Core migration files (current baseline checked in; pipeline regenerates). -- `wwwroot/`: Static assets (CSS, images, JS). - -## Build, Test, and Development Commands - -- `make check`: Runs `shellcheck` on `setup.sh`, `dotnet-build.sh`, and `Makefile`, then restores and builds. -- `make restore`: Restores app, generator, parser, and test projects. -- `make build`: Builds `DotNetWebApp.Models`, `DotNetWebApp`, `ModelGenerator`, and `DdlParser` (default `BUILD_CONFIGURATION=Debug`). -- `make build-all`: Builds the full solution, including tests; automatically runs `cleanup-nested-dirs` to prevent inotify exhaustion. -- `make build-release`: Release builds for main projects only. -- `make run-ddl-pipeline`: DDL → YAML → models → migration pipeline, then build. -- `make migrate`: Applies the current EF Core migration (SQL Server must be running). -- `make dev`: Runs with hot reload (`dotnet watch`). -- `make run`: Runs once without hot reload. -- `make test`: Builds and runs `dotnet test` for `tests/DotNetWebApp.Tests` and `tests/ModelGenerator.Tests` (uses `BUILD_CONFIGURATION`); automatically runs `cleanup-nested-dirs`. -- `make seed`: Runs the app in seed mode to apply `seed.sql` via EF (`-- --seed`). -- `make cleanup-nested-dirs`: Removes nested project directories created by MSBuild to prevent inotify watch exhaustion on Linux. -- Docker DB helpers: `make db-start`, `make db-stop`, `make db-logs`, `make db-drop`. -- Local SQL Server helpers: `make ms-status`, `make ms-start`, `make ms-logs`, `make ms-drop`. - -## Project Goal & Session Notes - -- **Primary Goal:** Abstract the application's data model, configuration, and branding into a single `app.yaml` file for dynamic customization. -- **Current State:** DDL → YAML → models → migration pipeline drives generated models and schema; run `make run-ddl-pipeline` before `make migrate`/`make seed` when the DDL changes. Seed data lives in `seed.sql` and is applied via `make seed`. -- Review `SESSION_SUMMARY.md` before starting work and update it when you make meaningful progress or decisions. - -## Coding Style & Naming Conventions - -- C#: 4-space indentation, PascalCase for types/props, camelCase for locals/params, `Async` suffix for async methods. -- Razor components: PascalCase filenames (e.g., `GenericEntityPage.razor`). -- Generated files in `DotNetWebApp.Models/Generated/` should not be edited directly; update `ModelGenerator/EntityTemplate.scriban` and regenerate instead. -- Model classes in `DotNetWebApp.Models/` are shared across the application; ensure changes don't break existing consumers. -- Keep Radzen UI wiring intact in `Shared/` and `_Layout.cshtml`. - -## Testing Guidelines - -- Tests live in `tests/` using a `ProjectName.Tests` project and `*Tests` class naming. -- Run tests via `make test` and include failing/passing notes in PRs. - -## Commit & Pull Request Guidelines - -- Commit messages are short and imperative (e.g., “Add docker database commands”, “Fix nav bar button”); keep them concise. -- PRs should include: a brief summary, commands run (`make check`, `make build`, etc.), screenshots for UI changes, and DDL pipeline notes if schema changed. - -## Configuration & Safety Notes - -- Secrets belong in user secrets or environment variables; see `SECRETS.md`. -- `app.yaml` drives model generation; branding/navigation labels still come from `appsettings.json` via `AppCustomizationOptions`. -- `dotnet-build.sh` sets `DOTNET_ROOT` for global tools; do not modify or reinstall the system .NET runtime. -- Tenant schema switching uses the `X-Customer-Schema` header (defaults to `dbo`). -- Models are in separate `DotNetWebApp.Models` project; YamlDotNet dependency lives there. diff --git a/ARCHITECTURE_SUMMARY.md b/ARCHITECTURE_SUMMARY.md index 08b8ee3..7a37c0d 100644 --- a/ARCHITECTURE_SUMMARY.md +++ b/ARCHITECTURE_SUMMARY.md @@ -1,7 +1,7 @@ # DotNetWebApp Architecture Summary -**Last Updated:** 2026-01-27 -**Status:** Architecture finalized; Phase 1 & Phase 2B complete +**Last Updated:** 2026-02-02 +**Status:** Architecture finalized; Phase 1, Phase 2B, Phase 3+4, and WAMS Phase 1 MVP complete --- @@ -176,6 +176,51 @@ **Note:** Phase 3 (read-only patterns) and Phase 4 (editable patterns) are combined into a single PR. +### ✅ WAMS Phase 1 COMPLETED - Web App Management System MVP (2026-02-02) + +**Goal:** Implement core scheduling system following proven patterns from InventoryPicking and Allocation + +**Status:** ✅ COMPLETED - Full MVP with 39-column grid, status workflow, AppID grouping, and soft delete + +**Key Features Delivered:** +- ✅ 39-column RadzenDataGrid with order type classification (SO, PO) +- ✅ Status workflow state machine (NA → CheckIn → Loading → Unloading → Shipped → Received) +- ✅ AppID batch operations (orders with same AppID move together) +- ✅ Soft delete pattern (type + 10, reversible with undelete) +- ✅ Personnel assignment (forklift operators filtered by warehouse) +- ✅ Color coding by order type and status +- ✅ Multi-warehouse support (Chicago=3, NYC=92) +- ✅ Comprehensive seed data (15 test orders covering all scenarios) + +**Architecture:** +- Single service pattern following InventoryPicking (WAMSService + validators) +- Hybrid EF Core (writes) + Dapper (reads) with SecondaryDbContext (WEBAPPMisc database) +- Keyed dependency injection: `[FromKeyedServices("Secondary")]` IDapperQueryService +- Static classifier (WAMSOrderTypeClassifier) + color service (WAMSColorCodingService) +- Two validators: StatusTransitionValidator (state machine) + DeleteValidator (security) + +**Files Created (13 new files):** +1. `Services/WAMS/Models/WAMSModels.cs` - All models, enums, requests, responses +2. `Services/WAMS/WAMSOrderTypeClassifier.cs` - Static classification helper +3. `Services/WAMS/Validators/StatusTransitionValidator.cs` - State machine validation +4. `Services/WAMS/Validators/DeleteValidator.cs` - Delete authorization +5. `Services/WAMS/WAMSColorCodingService.cs` - Type and status colors +6. `Services/WAMS/IWAMSService.cs` - Service interface (10 methods) +7. `Services/WAMS/WAMSService.cs` - Service implementation +8. `Controllers/WAMSController.cs` - REST API (10 endpoints) +9. `Components/Pages/WAMS/Index.razor` - Main UI with 39-column grid +10-13. Test files (4 files with 105 unit tests) + +**Testing:** +- ✅ **105 WAMS unit tests** (22 validator + 25 delete + 43 color + 15 service) +- ✅ **582 total tests passing** (up from 467 = +115 new tests) +- ✅ Clean build (0 warnings, 0 errors) +- ✅ API integration verified with curl tests +- ✅ UI displaying data correctly + +**Deferred to Production:** +- BOL generation, Excel export, email notifications, auto-refresh timers, daily reset + ### 🔄 Future: Validation Pipeline (1 day) - As Needed **Goal:** Robust tenant isolation for multiple schemas diff --git a/AppsYamlGenerator/Program.cs b/AppsYamlGenerator/Program.cs index d8bbc8d..e697a77 100644 --- a/AppsYamlGenerator/Program.cs +++ b/AppsYamlGenerator/Program.cs @@ -39,9 +39,18 @@ // Read appsettings.json and extract Applications section Console.WriteLine($"Reading appsettings.json: {appSettingsAbsPath}"); - var config = new ConfigurationBuilder() - .AddJsonFile(appSettingsAbsPath) - .Build(); + var configBuilder = new ConfigurationBuilder() + .AddJsonFile(appSettingsAbsPath); + + // Also read appsettings.Local.json if it exists (for local overrides) + var localSettingsPath = Path.Combine(Path.GetDirectoryName(appSettingsAbsPath)!, "appsettings.Local.json"); + if (File.Exists(localSettingsPath)) + { + Console.WriteLine($"Found appsettings.Local.json, merging Applications..."); + configBuilder.AddJsonFile(localSettingsPath); + } + + var config = configBuilder.Build(); var applicationsSection = config.GetSection("Applications"); var applications = new List(); @@ -49,11 +58,11 @@ if (applicationsSection.Exists()) { applicationsSection.Bind(applications); - Console.WriteLine($"Found {applications.Count} application(s) in appsettings.json"); + Console.WriteLine($"Found {applications.Count} application(s) in configuration"); } else { - Console.WriteLine("Warning: No Applications section found in appsettings.json"); + Console.WriteLine("Warning: No Applications section found in configuration"); } // Read data.yaml and extract DataModel section diff --git a/BLOCKERS.md b/BLOCKERS.md new file mode 100644 index 0000000..994ae88 --- /dev/null +++ b/BLOCKERS.md @@ -0,0 +1,7 @@ +# Known Implementation Blockers + +No current blockers. + +--- + +**Last Updated:** 2026-03-17 diff --git a/CLAUDE.md b/CLAUDE.md index a34c66c..0a0aad6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,404 +10,392 @@ **Consequence:** Using forbidden git commands breaks the codebase. This rule is non-negotiable. ## Developer Profile + You're an expert .NET/C# engineer with deep knowledge of: -- ASP.NET Core Web APIs -- Entity Framework Core +- ASP.NET Core Web APIs + Entity Framework Core - Modern C# patterns and best practices -- RESTful API design -- Fullstack development with excellent programming skills in Javascript, HTML & CSS +- Blazor Server + Radzen UI components - Database schema modeling (DDL-first) +- Multi-tenant architectures ## Project Overview -This is a .NET 8 Web API + Blazor Server SPA with Entity Framework Core and a SQL DDL-driven data model/branding configuration. + +.NET 8 Web API + Blazor Server SPA with SQL DDL-driven data model and Radzen UI components. + +**Architecture:** Hybrid EF Core (writes) + Dapper (reads) with SQL-first everything. ## 🎨 UI Framework: Radzen Blazor Components **This project uses Radzen Blazor components for ALL UI elements.** -### When to Use the Radzen Skill -Consult the `radzen-blazor` skill (`.claude/skills/radzen-blazor/SKILL.md`) when: -- Creating or editing any `.razor` files in `Components/`, `Pages/`, `Shared/` -- Working with buttons, forms, inputs, data grids, layouts, dialogs, notifications -- User asks to "add a button", "create a form", "show a dialog", "display data", etc. -- Any UI-related requests, even if "Radzen" is not mentioned +### When to Use Radzen Skill + +Use `.claude/skills/radzen-blazor` when working with any `.razor` files or UI requests. ### Critical Radzen Rules -1. **Enum properties MUST use @ prefix:** `ButtonStyle="@ButtonStyle.Primary"` (NOT `ButtonStyle="ButtonStyle.Primary"`) + +1. **Enum properties MUST use @ prefix:** `ButtonStyle="@ButtonStyle.Primary"` 2. **Use Radzen components, NOT plain HTML:** `` not `