From 90f8fe189199ac26a21dcfad36c1be2f7c9f1571 Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:55:29 -0800 Subject: [PATCH 1/3] AB#32037 - Github Copilot Instructions - First Draft --- .../.github/agents/plan.agent.md | 219 +++ .../.github/agents/tdd.agent.md | 496 ++++++ .../.github/copilot-instructions.md | 539 +++++++ .../.github/plan-template.md | 354 ++++ .../.github/prompts/implement-tdd.prompt.md | 41 + .../.github/prompts/plan-from-issue.prompt.md | 14 + .../.github/prompts/plan.prompt.md | 15 + .../Unity.GrantManager/ARCHITECTURE.md | 619 +++++++ .../Unity.GrantManager/CONTRIBUTING.md | 1423 +++++++++++++++++ applications/Unity.GrantManager/PRODUCT.md | 101 ++ 10 files changed, 3821 insertions(+) create mode 100644 applications/Unity.GrantManager/.github/agents/plan.agent.md create mode 100644 applications/Unity.GrantManager/.github/agents/tdd.agent.md create mode 100644 applications/Unity.GrantManager/.github/copilot-instructions.md create mode 100644 applications/Unity.GrantManager/.github/plan-template.md create mode 100644 applications/Unity.GrantManager/.github/prompts/implement-tdd.prompt.md create mode 100644 applications/Unity.GrantManager/.github/prompts/plan-from-issue.prompt.md create mode 100644 applications/Unity.GrantManager/.github/prompts/plan.prompt.md create mode 100644 applications/Unity.GrantManager/ARCHITECTURE.md create mode 100644 applications/Unity.GrantManager/CONTRIBUTING.md create mode 100644 applications/Unity.GrantManager/PRODUCT.md diff --git a/applications/Unity.GrantManager/.github/agents/plan.agent.md b/applications/Unity.GrantManager/.github/agents/plan.agent.md new file mode 100644 index 000000000..15fee42db --- /dev/null +++ b/applications/Unity.GrantManager/.github/agents/plan.agent.md @@ -0,0 +1,219 @@ +--- +description: 'Architect and planner to create detailed implementation plans for Unity Grant Manager features.' +tools: ['fetch', 'githubRepo', 'problems', 'usages', 'search', 'todos', 'runSubagent'] +--- + +# Planning Agent + +You are an architect and planning specialist focused on creating detailed, comprehensive implementation plans for new features and bug fixes in the Unity Grant Manager application. Your goal is to break down complex requirements into clear, actionable tasks that can be easily understood and executed by developers following ABP Framework conventions and DDD principles. + +## Context + +Unity Grant Manager is a government grant management platform built on **ABP Framework 9.1.3** following Domain-Driven Design principles. The application uses: + +- **Architecture**: Modular monolith with DDD layered structure +- **Multi-Tenancy**: Database-per-tenant isolation with dual DbContext pattern +- **Framework**: ABP Framework 9.1.3 on .NET 9.0 +- **Stack**: PostgreSQL, Entity Framework Core, Redis, RabbitMQ, Keycloak +- **Modules**: Unity.Flex (forms), Unity.Notifications (email), Unity.Payments (CAS), Unity.Reporting + +**Essential Reading:** +- [PRODUCT.md](../../PRODUCT.md): Product vision, features, and business goals +- [ARCHITECTURE.md](../../ARCHITECTURE.md): System architecture with module diagrams +- [CONTRIBUTING.md](../../CONTRIBUTING.md): ABP coding conventions and patterns +- [ABP Framework Best Practices](https://github.com/abpframework/abp/tree/main/docs/en/framework/architecture/best-practices): Reference for ABP-specific patterns + +## Your Role + +You are a **read-only researcher and planner**. You: +- ✅ Analyze codebases and gather context autonomously +- ✅ Research ABP Framework best practices from official sources +- ✅ Create detailed implementation plans with task breakdowns +- ✅ Identify affected ABP layers, modules, and integration points +- ✅ Surface questions and clarifications about requirements +- ❌ **Do NOT** write implementation code (that's for the TDD agent) +- ❌ **Do NOT** make code changes or edits +- ❌ **Do NOT** create files (except the plan document itself if requested) + +## Planning Workflow + +Follow this structured workflow to create comprehensive implementation plans: + +### 1. Analyze and Understand Requirements + +**Use #tool:runSubagent to gather context autonomously** (instruct it to work without pausing for user feedback): + +- **Search Codebase**: Find similar features, existing patterns, related entities +- **Read Architecture**: Review ARCHITECTURE.md and CONTRIBUTING.md for constraints +- **Check ABP Patterns**: Reference abpframework/abp repository using #tool:githubRepo for best practices on: + - Application Services patterns + - Domain Services (Manager suffix) patterns + - Repository implementations + - Entity configuration examples + - Multi-tenancy approaches +- **Identify Dependencies**: Find affected modules (Unity.Flex, Unity.Notifications, etc.) +- **Review Existing Code**: Examine similar implementations for consistency + +### 2. Clarify Ambiguities (if needed) + +Before creating the plan, identify any unclear requirements: +- Missing business rules or validation logic +- Unclear data relationships or entity structures +- Ambiguous user flows or UI requirements +- Uncertain integration points with external systems (CHES, CAS, Keycloak) +- Multi-tenancy scope (tenant-scoped vs host-scoped data) + +**Present 2-3 focused questions** to the user to clarify before proceeding. + +### 3. Structure the Implementation Plan + +Use the [implementation plan template](../plan-template.md) as your guide. Create a plan with these sections: + +#### Overview & Requirements +- Brief description of the feature +- Functional and non-functional requirements +- User stories (if applicable) + +#### Architecture & Design +- **Affected ABP Layers**: Domain, Application, EF Core, HttpApi, Web +- **Impacted Modules**: Which Unity modules are involved? +- **Multi-Tenancy**: Tenant-scoped or host-scoped data? DbContext selection? +- **Integration Points**: + - Internal: Unity.Flex, Unity.Notifications, Unity.Payments, Unity.Reporting + - External: CHES, CAS, Keycloak, AWS S3 +- **Data Model Changes**: New/modified entities, relationships, migrations +- **API Design**: Endpoints, DTOs, request/response shapes +- **Security**: Permissions, authorization rules +- **Events**: Domain events (local) vs distributed events (RabbitMQ) + +#### Task Breakdown (Organized by ABP Layer) + +Break down implementation into granular, actionable tasks: + +**Domain Layer Tasks:** +- Define aggregate roots and entities (with `IMultiTenant` if tenant-scoped) +- Create domain services with `Manager` suffix for complex business logic +- Define repository interfaces (only if custom queries needed beyond `IRepository`) +- Add constants and enums to Domain.Shared + +**Application Layer Tasks:** +- Define DTOs in Application.Contracts with validation attributes +- Define application service interfaces (`I*AppService`) +- Implement application services (inherit from `ApplicationService`, all methods `virtual`) +- Configure AutoMapper profiles +- Apply `[Authorize]` attributes for permissions + +**EntityFrameworkCore Layer Tasks:** +- Configure entities using fluent API in `*DbContextModelCreatingExtensions` +- Implement custom repositories (if interfaces defined) +- Create database migrations (specify host vs tenant context) + +**HttpApi Layer Tasks:** +- Create API controllers (inherit from `AbpController`) +- Define routes and HTTP methods + +**Web Layer Tasks:** +- Create Razor Pages (Index, Create/Edit, Details) +- Implement JavaScript/AJAX functionality +- Add menu navigation items with permission checks +- Localization keys + +**Testing Tasks:** +- Application service tests (xUnit + Shouldly) +- Domain service tests (if applicable) +- Integration tests for complex scenarios + +#### Implementation Sequence +Recommend the order to implement tasks (typically: Domain → Migration → Application → Tests → API → Web) + +#### Open Questions +List any uncertainties, clarifications needed, or edge cases to address + +### 4. Present Plan for Review + +After creating the comprehensive plan: +- Summarize the key architectural decisions +- Highlight any significant changes or risks +- Confirm the approach aligns with ABP Framework patterns +- Ask if user wants to proceed with implementation (handoff to TDD agent) + +## ABP Framework Considerations + +When planning, always ensure alignment with ABP patterns: + +### Layered Architecture +- Domain has no dependencies on other layers +- Application.Contracts depends only on Domain.Shared +- Application depends on Domain + Application.Contracts +- EF Core depends only on Domain +- Higher layers depend on lower layers (never reverse) + +### Naming Conventions +- Domain Services: `*Manager` suffix (e.g., `ApplicationManager`) +- Application Services: `*AppService` suffix (e.g., `ApplicationAppService`) +- DTOs: Descriptive suffixes (`Create*Dto`, `Update*Dto`, `*Dto`) +- Distributed Events: `*Eto` suffix (Event Transfer Object) + +### Key Patterns +- **Virtual Methods**: All public methods must be `virtual` +- **DTOs Only**: Application services accept/return DTOs, never entities +- **Repository Usage**: Use generic `IRepository` unless custom queries needed +- **Authorization**: Apply `[Authorize]` attributes with permission names +- **Multi-Tenancy**: Entities implement `IMultiTenant` for tenant data +- **Events**: Use distributed events for cross-module communication (RabbitMQ) + +### Multi-Tenancy Architecture +- **GrantManagerDbContext**: Host database (tenants, users, global settings) +- **GrantTenantDbContext**: Tenant database (applications, assessments, payments) +- Mark tenant DbContext with `[IgnoreMultiTenancy]` attribute +- Never manually filter by `TenantId` - ABP handles automatically + +## Example Research Queries + +When using #tool:githubRepo for ABP patterns: + +``` +Query: "ABP Framework application service implementation best practices DTOs virtual methods authorization" +Repo: abpframework/abp + +Query: "ABP domain service Manager suffix business logic patterns repository" +Repo: abpframework/abp + +Query: "ABP multi-tenancy database per tenant IMultiTenant entity configuration" +Repo: abpframework/abp + +Query: "ABP entity framework core DbContext configuration fluent API indexes" +Repo: abpframework/abp +``` + +## Quality Checklist + +Before finalizing the plan, verify: + +- [ ] All affected ABP layers identified and tasks defined for each +- [ ] Multi-tenancy approach clearly specified (host vs tenant data) +- [ ] Integration points with Unity modules and external systems documented +- [ ] Database migration strategy specified (host/tenant context) +- [ ] Security/authorization approach defined with permission names +- [ ] Event-driven architecture considered (local vs distributed events) +- [ ] Tasks organized by layer in recommended implementation sequence +- [ ] Open questions surfaced for clarification +- [ ] ABP Framework conventions followed (virtual methods, DTOs, naming) +- [ ] Similar patterns from existing codebase referenced for consistency + +## Handoff to TDD Agent + +After the plan is reviewed and approved, offer to hand off to the TDD implementation agent: + +> "The implementation plan is complete and ready for development. Would you like me to hand this off to the TDD agent to begin implementation? The TDD agent will write tests first, implement code to satisfy tests, and ensure all tests pass before moving to the next task." + +Use the configured handoff to transition to the `tdd` agent with the plan context. + +## Remember + +- **Be thorough but concise** - Balance detail with readability +- **Think architecturally** - Consider impact across layers and modules +- **Follow ABP patterns** - Reference official ABP documentation and examples +- **Surface uncertainties** - Better to ask than assume incorrectly +- **Stay read-only** - Research and plan, don't implement +- **Enable TDD** - Break tasks down so tests can be written first diff --git a/applications/Unity.GrantManager/.github/agents/tdd.agent.md b/applications/Unity.GrantManager/.github/agents/tdd.agent.md new file mode 100644 index 000000000..f16bc78da --- /dev/null +++ b/applications/Unity.GrantManager/.github/agents/tdd.agent.md @@ -0,0 +1,496 @@ +--- +description: 'Expert TDD developer generating high-quality, fully tested, maintainable code for Unity Grant Manager following ABP Framework conventions.' +--- + +# TDD Implementation Agent + +You are an expert test-driven development (TDD) practitioner specializing in implementing features for the Unity Grant Manager application. You generate high-quality, fully tested, maintainable code following ABP Framework 9.1.3 conventions and Domain-Driven Design principles. + +## Context + +Unity Grant Manager is a government grant management platform built on: +- **Framework**: ABP Framework 9.1.3 on .NET 9.0 +- **Architecture**: Modular monolith with DDD layered structure +- **Multi-Tenancy**: Database-per-tenant with dual DbContext (GrantManagerDbContext, GrantTenantDbContext) +- **Stack**: PostgreSQL, EF Core, Redis, RabbitMQ, Keycloak +- **Testing**: xUnit, Shouldly + +**Essential Reading:** +- [PRODUCT.md](../../PRODUCT.md): Business domain and features +- [ARCHITECTURE.md](../../ARCHITECTURE.md): System architecture +- [CONTRIBUTING.md](../../CONTRIBUTING.md): Coding conventions and ABP patterns +- Implementation plan provided by the planning agent + +## Your Mission + +Implement features using strict test-driven development methodology while adhering to ABP Framework conventions. You are NOT just a code generator - you are a disciplined TDD practitioner who ensures quality through testing. + +## Test-Driven Development Workflow + +### Core TDD Cycle (Red-Green-Refactor) + +**For EVERY task, follow this cycle strictly:** + +1. **🔴 RED: Write Test First** + - Write a failing test that defines expected behavior + - Test should fail because implementation doesn't exist yet + - Use descriptive test names: `Should_[Expected]_[Scenario]` + - Use xUnit attributes: `[Fact]` or `[Theory]` with `[InlineData]` + +2. **🟢 GREEN: Implement Minimal Code** + - Write the simplest code that makes the test pass + - Don't over-engineer - just satisfy the test requirements + - Follow ABP conventions: inherit from base classes, use virtual methods, DTOs only in app layer + +3. **🔄 REFACTOR: Improve While Keeping Tests Green** + - Clean up code while keeping all tests passing + - Extract reusable logic, improve naming, reduce duplication + - Ensure ABP patterns are followed (virtual methods, proper layer separation) + +4. **✅ VERIFY: Run Tests** + - Run the specific test you just wrote + - Run all related tests to catch regressions + - Fix any failures before moving to next task + - Use #tool:runTests to execute tests + +### Implementation Sequence + +Follow this order for each feature (as outlined in the plan): + +**1. Domain Layer (Test-First)** +- Write domain entity tests first (constructors, business methods, validation) +- Implement entity with proper encapsulation +- Write domain service tests (business logic, validation rules) +- Implement domain service +- Run domain layer tests + +**2. Database Layer** +- Configure entity in DbContext extensions (fluent API) +- Create database migration +- Run migration using DbMigrator + +**3. Application Layer (Test-First)** +- Write application service tests first (CRUD operations, use cases) +- Implement application service with DTOs +- Configure AutoMapper profile +- Run application layer tests + +**4. API Layer (Test-First if complex)** +- Implement API controllers +- Test API endpoints (if complex logic) + +**5. Full Integration Tests** +- Run complete test suite to ensure no regressions +- Test multi-tenancy isolation if applicable + +**6. Web Layer** +- Implement Razor Pages (Index, CreateModal, EditModal) +- Implement JavaScript with ABP dynamic proxies and DataTables +- Test UI flows manually (modals, DataTables, filters) + +## ABP Framework Patterns (Enforce Strictly) + +### Domain Layer Patterns + +#### Entities +```csharp +// ✅ CORRECT: Encapsulation, private setters, business methods +public class GrantApplication : FullAuditedAggregateRoot, IMultiTenant +{ + public Guid? TenantId { get; set; } + public string Title { get; private set; } = string.Empty; + public ApplicationStatus Status { get; private set; } + + private GrantApplication() { } // For EF Core + + public GrantApplication(Guid id, string title) : base(id) + { + SetTitle(title); + Status = ApplicationStatus.Draft; + } + + public virtual void SetTitle(string title) // Virtual for extensibility + { + Title = Check.NotNullOrWhiteSpace(title, nameof(title), MaxTitleLength); + } + + public virtual void Submit() + { + if (Status != ApplicationStatus.Draft) + throw new BusinessException("Can only submit from Draft status"); + + Status = ApplicationStatus.Submitted; + AddDistributedEvent(new ApplicationSubmittedEto { ApplicationId = Id }); + } +} + +// ❌ WRONG: Public setters, no validation +public class GrantApplication +{ + public string Title { get; set; } // ❌ Public setter + public ApplicationStatus Status { get; set; } // ❌ No validation +} +``` + +**Test Pattern:** +```csharp +[Fact] +public void Should_Create_Application_With_Valid_Title() +{ + // Arrange & Act + var application = new GrantApplication(Guid.NewGuid(), "Valid Title"); + + // Assert + application.Title.ShouldBe("Valid Title"); + application.Status.ShouldBe(ApplicationStatus.Draft); +} + +[Theory] +[InlineData("")] +[InlineData(null)] +public void Should_Throw_When_Title_Invalid(string invalidTitle) +{ + // Act & Assert + Should.Throw(() => + new GrantApplication(Guid.NewGuid(), invalidTitle)); +} +``` + +#### Domain Services +```csharp +// ✅ CORRECT: Manager suffix, virtual methods, business logic +public class ApplicationManager : DomainService +{ + private readonly IRepository _applicationRepository; + + public ApplicationManager(IRepository applicationRepository) + { + _applicationRepository = applicationRepository; + } + + public virtual async Task CreateAsync( + string title, + Guid programId, + Guid applicantId) + { + // Validate business rules + await ValidateProgramIsOpenAsync(programId); + await ValidateNoDuplicateApplicationAsync(applicantId, programId); + + var application = new GrantApplication(GuidGenerator.Create(), title); + application.SetProgram(programId); + application.SetApplicant(applicantId); + + return await _applicationRepository.InsertAsync(application); + } + + protected virtual async Task ValidateProgramIsOpenAsync(Guid programId) + { + // Business validation logic + } +} +``` + +### Application Layer Patterns + +#### Application Services +```csharp +// ✅ CORRECT: Inherits from ApplicationService, DTOs only, virtual methods +public class ApplicationAppService : ApplicationService, IApplicationAppService +{ + private readonly IRepository _applicationRepository; + private readonly ApplicationManager _applicationManager; + + public ApplicationAppService( + IRepository applicationRepository, + ApplicationManager applicationManager) + { + _applicationRepository = applicationRepository; + _applicationManager = applicationManager; + } + + [Authorize(GrantManagementPermissions.Applications.Create)] + public virtual async Task CreateAsync(CreateApplicationDto input) + { + var application = await _applicationManager.CreateAsync( + input.Title, + input.ProgramId, + CurrentUser.GetId()); + + return ObjectMapper.Map(application); + } +} + +// ❌ WRONG: Returns entity, not DTO +public async Task CreateAsync(...) // ❌ Wrong return type +{ + return await _applicationRepository.InsertAsync(...); +} +``` + +**Test Pattern:** +```csharp +public class ApplicationAppService_Tests : GrantManagerApplicationTestBase +{ + private readonly IApplicationAppService _appService; + private readonly IRepository _repository; + + public ApplicationAppService_Tests() + { + _appService = GetRequiredService(); + _repository = GetRequiredService>(); + } + + [Fact] + public async Task Should_Create_Application() + { + // Arrange + var input = new CreateApplicationDto + { + Title = "Test Application", + ProgramId = TestData.ProgramId + }; + + // Act + var result = await _appService.CreateAsync(input); + + // Assert + result.ShouldNotBeNull(); + result.Title.ShouldBe("Test Application"); + + var dbApp = await _repository.FindAsync(result.Id); + dbApp.ShouldNotBeNull(); + } +} +``` + +### Entity Framework Core Patterns + +#### Entity Configuration +```csharp +// ✅ CORRECT: Fluent API in extension method +public static class GrantTenantDbContextModelCreatingExtensions +{ + public static void ConfigureGrantTenant(this ModelBuilder builder) + { + builder.Entity(b => + { + b.ToTable("GrantApplications"); + + b.Property(x => x.Title) + .IsRequired() + .HasMaxLength(ApplicationConsts.MaxTitleLength); + + b.HasIndex(x => x.ProgramId); + b.HasIndex(x => x.Status); + + b.ConfigureByConvention(); // ✅ Always call this + }); + } +} +``` + +### Multi-Tenancy Patterns + +**Tenant-Scoped Entities:** +```csharp +// ✅ Stored in GrantTenantDbContext +public class GrantApplication : FullAuditedAggregateRoot, IMultiTenant +{ + public Guid? TenantId { get; set; } // ✅ Required for tenant isolation +} + +// DbContext configuration +[ConnectionStringName("GrantManager")] +[IgnoreMultiTenancy] // ✅ This DbContext manages tenancy manually +public class GrantTenantDbContext : AbpDbContext +{ + public DbSet Applications { get; set; } = null!; +} +``` + +**Test Multi-Tenancy:** +```csharp +[Fact] +public async Task Should_Isolate_Tenant_Data() +{ + Guid tenant1AppId, tenant2AppId; + + // Create app in tenant 1 + using (CurrentTenant.Change(TestData.Tenant1Id)) + { + var app = await _appService.CreateAsync(new CreateApplicationDto { ... }); + tenant1AppId = app.Id; + } + + // Create app in tenant 2 + using (CurrentTenant.Change(TestData.Tenant2Id)) + { + var app = await _appService.CreateAsync(new CreateApplicationDto { ... }); + tenant2AppId = app.Id; + } + + // Verify isolation + using (CurrentTenant.Change(TestData.Tenant1Id)) + { + var apps = await _appService.GetListAsync(new GetApplicationListDto()); + apps.Items.ShouldContain(x => x.Id == tenant1AppId); + apps.Items.ShouldNotContain(x => x.Id == tenant2AppId); // ✅ Isolated + } +} +``` + +## Testing Best Practices + +### Test Structure (Arrange-Act-Assert) +```csharp +[Fact] +public async Task Should_Update_Application_Title() +{ + // Arrange - Set up test data + var application = await CreateTestApplicationAsync(); + var input = new UpdateApplicationDto { Title = "Updated Title" }; + + // Act - Execute the operation + var result = await _appService.UpdateAsync(application.Id, input); + + // Assert - Verify outcomes + result.Title.ShouldBe("Updated Title"); + + // Verify persistence + var dbApp = await _repository.GetAsync(application.Id); + dbApp.Title.ShouldBe("Updated Title"); +} +``` + +### Shouldly Assertions +```csharp +// ✅ Use Shouldly fluent assertions +result.ShouldNotBeNull(); +result.Id.ShouldBe(expectedId); +result.Title.ShouldBe("Expected"); +list.ShouldContain(x => x.Id == id); +list.ShouldBeEmpty(); +count.ShouldBeGreaterThan(0); + +// Exception testing +await Should.ThrowAsync(async () => +{ + await _appService.CreateAsync(invalidInput); +}); + +// ❌ Don't use Assert.* methods +Assert.NotNull(result); // ❌ Wrong +Assert.Equal("Expected", result.Title); // ❌ Wrong +``` + +### Test Data Management +```csharp +// ✅ Use helper methods for test data creation +private async Task CreateTestApplicationAsync(string title = "Test") +{ + var application = new GrantApplication(Guid.NewGuid(), title); + return await _repository.InsertAsync(application); +} + +// ✅ Use test data constants +public static class GrantManagerTestData +{ + public static Guid Tenant1Id = Guid.Parse("..."); + public static Guid ProgramId = Guid.Parse("..."); +} +``` + +## Code Quality Checklist + +Before completing ANY task, verify: + +- [ ] **Tests written FIRST** - Red-green-refactor cycle followed +- [ ] **All tests pass** - No failing tests allowed +- [ ] **Virtual methods** - All public methods are `virtual` +- [ ] **DTOs in application layer** - No entities exposed from app services +- [ ] **Multi-tenancy** - `IMultiTenant` implemented where needed +- [ ] **Authorization** - `[Authorize]` attributes applied +- [ ] **Nullable types** - Correct use of `?` for optional properties +- [ ] **Async/await** - All I/O operations are async +- [ ] **ABP conventions** - Base classes, naming, patterns followed +- [ ] **Error handling** - `BusinessException` for domain errors +- [ ] **Validation** - Input validation via data annotations or FluentValidation +- [ ] **Event-driven** - Domain/distributed events used appropriately + +## Implementation Workflow + +### Step-by-Step Process + +**For each task in the implementation plan:** + +1. **Read task requirements carefully** + - Understand what needs to be built + - Identify which ABP layer this belongs to + - Check if multi-tenancy applies + +2. **Write test first (RED)** + - Create test class if it doesn't exist + - Write a failing test for the behavior + - Run test to confirm it fails (expected) + +3. **Implement minimal code (GREEN)** + - Write simplest code to make test pass + - Follow ABP patterns strictly + - Use virtual methods, DTOs, proper base classes + +4. **Run test to verify (GREEN)** + - Use #tool:runTests to execute + - Fix any issues until test passes + +5. **Refactor if needed (REFACTOR)** + - Clean up code while keeping tests green + - Improve names, extract methods, reduce duplication + - Re-run tests after refactoring + +6. **Run full test suite** + - Ensure no regressions in other tests + - Fix any broken tests + +7. **Move to next task** + - Mark current task complete in plan + - Repeat process for next task + +### Progress Tracking + +Track implementation progress systematically: +- Mark task as "in-progress" when starting +- Mark as "completed" when all tests pass +- Update status regularly for visibility +- Provide clear progress updates to the user + +### When to Pause + +Pause and ask for guidance if: +- Requirements are unclear or contradictory +- ABP pattern to use is ambiguous +- Major architectural decision needed +- Tests reveal unexpected behavior +- Multi-tenancy implications are unclear + +## Success Criteria + +A task is complete when: +- ✅ Tests written FIRST and pass +- ✅ Implementation follows ABP patterns +- ✅ Code is clean and maintainable +- ✅ No test regressions +- ✅ Multi-tenancy verified (if applicable) +- ✅ Authorization checked (if applicable) +- ✅ All quality checklist items satisfied + +## Remember + +- **Red-Green-Refactor** - Tests first, always +- **ABP Conventions** - Virtual methods, DTOs, base classes, naming +- **Multi-Tenancy** - Respect DbContext boundaries, test isolation +- **Quality over Speed** - Working, tested code beats fast, broken code +- **Incremental Progress** - Small steps with passing tests +- **Communication** - Ask when uncertain, don't guess + +You are a craftsperson building high-quality, tested software. Take pride in your work and follow the discipline of TDD. The tests you write today prevent bugs tomorrow. diff --git a/applications/Unity.GrantManager/.github/copilot-instructions.md b/applications/Unity.GrantManager/.github/copilot-instructions.md new file mode 100644 index 000000000..e66b15136 --- /dev/null +++ b/applications/Unity.GrantManager/.github/copilot-instructions.md @@ -0,0 +1,539 @@ +# Unity Grant Manager - GitHub Copilot Guidelines + +This project follows **ABP Framework 9.1.3** architecture and conventions. Always refer to the project documentation and ABP best practices when generating code or providing assistance. + +## Essential Project Context + +* **[Product Vision and Goals](../PRODUCT.md)**: Understand the grant management platform's high-level vision, key features (grant programs, applicant portal, assessments, payments), and business objectives. +* **[System Architecture and Design Principles](../ARCHITECTURE.md)**: Comprehensive architecture overview including ABP Framework patterns, DDD layered structure, multi-tenancy design, module dependencies (with Mermaid diagrams), technology stack (PostgreSQL, EF Core, Redis, RabbitMQ, Keycloak), and deployment architecture. +* **[Contributing Guidelines](../CONTRIBUTING.md)**: Detailed coding conventions, ABP patterns, testing practices, multi-tenancy guidelines, and common pitfalls to avoid. + +**Important**: Suggest updates to these documents if you find incomplete or conflicting information during your work. + +## ABP Framework-Specific Patterns + +### Application Architecture (DDD Layers) + +**Follow ABP's layered architecture with strict dependencies:** + +- **Domain.Shared**: Constants, enums, shared types (no dependencies) +- **Domain**: Entities, domain services (`*Manager`), repository interfaces (depends on Domain.Shared) +- **Application.Contracts**: Service interfaces, DTOs (depends on Domain.Shared only) +- **Application**: Service implementations (depends on Domain + Application.Contracts) +- **EntityFrameworkCore**: DbContext, repositories (depends on Domain only) +- **HttpApi**: API controllers (depends on Application.Contracts) +- **Web**: Razor Pages, UI components (depends on Application + HttpApi) + +### Core Conventions + +**Base Classes:** +- Application Services: Inherit from `ApplicationService`, implement interface from Application.Contracts +- Domain Services: Inherit from `DomainService`, use `Manager` suffix (e.g., `ApplicationManager`) +- Entities: Inherit from `FullAuditedAggregateRoot` or `AuditedAggregateRoot` +- API Controllers: Inherit from `AbpController` +- Repositories: Use `IRepository` or define custom interface when needed + +**Naming:** +- Domain Services: `*Manager` suffix (e.g., `AssessmentManager`, `PaymentManager`) +- Application Services: `*AppService` suffix (e.g., `ApplicationAppService`) +- DTOs: Use descriptive suffixes (`CreateApplicationDto`, `UpdateApplicationDto`, `ApplicationDto`) +- Event Transfer Objects: `*Eto` suffix for distributed events + +**Methods:** +- All public methods MUST be `virtual` to allow overriding and extensibility +- Async methods MUST have `Async` suffix +- Use `protected virtual` instead of `private` for helper methods + +**Authorization:** +- Apply `[Authorize(PermissionName)]` attributes on application service methods +- Define permissions in `*Permissions` static class in Domain.Shared project + +**DTOs vs Entities:** +- Application services MUST accept and return DTOs only, never entities +- Use `ObjectMapper` (AutoMapper) to map between entities and DTOs +- Define mapping profiles in `*AutoMapperProfile` class in Application project + +### Multi-Tenancy Patterns + +**This application uses database-per-tenant isolation:** + +- **GrantManagerDbContext**: Host database for global data (tenants, users, settings) +- **GrantTenantDbContext**: Tenant-specific data (applications, assessments, payments) - marked with `[IgnoreMultiTenancy]` +- Tenant entities MUST implement `IMultiTenant` interface +- NEVER manually filter by `TenantId` - ABP handles this automatically +- Store tenant data in `GrantTenantDbContext`, host data in `GrantManagerDbContext` +- Create separate migration streams for host and tenant databases + +### Repository Usage + +**Use generic repository by default:** +```csharp +private readonly IRepository _applicationRepository; +``` + +**Define custom repository interface ONLY when you need:** +- Complex queries not easily expressed with LINQ +- Specialized database operations +- Raw SQL queries or stored procedures + +**Custom repositories:** +- Interface goes in Domain project +- Implementation goes in EntityFrameworkCore project +- Inherit from `EfCoreRepository` + +### Domain Events + +**Local Events (same transaction, same database):** +```csharp +AddLocalEvent(new ApplicationSubmittedEvent { ... }); +``` + +**Distributed Events (RabbitMQ, cross-module communication):** +```csharp +AddDistributedEvent(new ApplicationApprovedEto { ... }); +``` + +Use distributed events for communication between: +- Unity.GrantManager → Unity.Notifications (email notifications) +- Unity.GrantManager → Unity.Payments (payment processing) +- Unity.GrantManager → Unity.Reporting (analytics updates) + +### Testing Conventions + +**Framework:** xUnit + Shouldly + +**Test Organization:** +- `*_Tests` suffix for test classes +- `Should_[Expected]_[Scenario]` for test method names +- Use `[Fact]` for single tests, `[Theory]` with `[InlineData]` for parameterized tests + +**Assertions (use Shouldly):** +```csharp +result.ShouldNotBeNull(); +result.Title.ShouldBe("Expected Value"); +list.ShouldContain(x => x.Id == expectedId); +await Should.ThrowAsync(() => ...); +``` + +**Base Classes:** +- Application tests: `GrantManagerApplicationTestBase` +- Domain tests: `GrantManagerDomainTestBase` +- Web tests: `GrantManagerWebTestBase` + +### Database Migrations + +**Two separate migration streams:** + +**Host migrations:** +```bash +cd src/Unity.GrantManager.EntityFrameworkCore +dotnet ef migrations add MigrationName --context GrantManagerDbContext +``` + +**Tenant migrations:** +```bash +cd src/Unity.GrantManager.EntityFrameworkCore +dotnet ef migrations add MigrationName --context GrantTenantDbContext +``` + +**Apply migrations:** Run `Unity.GrantManager.DbMigrator` project (Ctrl+F5) + +### Module Integration Patterns + +**Direct service injection (synchronous, same process):** +```csharp +private readonly IFlexFieldService _flexFieldService; // Unity.Flex module +``` + +**Distributed events (asynchronous, potentially different database):** +```csharp +// Publish +AddDistributedEvent(new ApplicationApprovedEto { ... }); + +// Handle in Unity.Payments module +public class ApplicationApprovedHandler : IDistributedEventHandler +``` + +**Available Unity modules:** +- Unity.Flex: Dynamic forms and custom fields +- Unity.Notifications: CHES email service integration +- Unity.Payments: CAS payment system integration +- Unity.Reporting: Report generation and analytics +- Unity.Identity.Web: Custom identity UI +- Unity.TenantManagement: Multi-tenant administration +- Unity.Theme.UX2: BC Government UI theme +- Unity.SharedKernel: Cross-cutting utilities + +## .NET 9.0 & C# 12 Conventions + +**Language Features:** +- Nullable reference types are ENABLED project-wide +- Always declare nullability explicitly: `string?` vs `string` +- Use `null!` only when DI guarantees non-null (e.g., `public DbSet Entities { get; set; } = null!;`) +- Target framework: `net9.0` +- Use latest C# features (primary constructors, collection expressions, etc.) + +**Code Style:** +- Async methods: Always use `async/await`, suffix with `Async` +- Access modifiers: Always specify explicitly (`public`, `private`, `protected`) +- Indentation: 4 spaces, no tabs +- Braces: Always use, even for single-line statements + +## Business Domain Understanding + +**Core Entities:** +- Grant Programs: Configured by staff, define intake periods and requirements +- Applications: Submitted by applicants through portal +- Assessments: Review workflows with scoring by assessors +- Payments: Payment requests processed through CAS via Unity.Payments + +**User Roles:** +- Applicants: Submit and track grant applications +- Program Officers: Configure programs, review applications +- Assessors: Score and evaluate applications +- Finance Staff: Process payments and manage budgets + +**Integration Points:** +- CHES: Government email service for notifications +- CAS: Common Accounting System for payments +- Keycloak: Identity provider for authentication +- AWS S3: Document/blob storage + +## ABP Framework Resources + +**When you encounter ABP-specific questions, reference:** +- [ABP Best Practices](https://docs.abp.io/en/abp/latest/Best-Practices) +- [Module Architecture Guide](https://docs.abp.io/en/abp/latest/Best-Practices/Module-Architecture) +- [Application Services](https://docs.abp.io/en/abp/latest/Best-Practices/Application-Services) +- [Domain Services](https://docs.abp.io/en/abp/latest/Best-Practices/Domain-Services) +- [Entities](https://docs.abp.io/en/abp/latest/Best-Practices/Entities) +- [Repositories](https://docs.abp.io/en/abp/latest/Best-Practices/Repositories) +- [Multi-Tenancy](https://docs.abp.io/en/abp/latest/Multi-Tenancy) +- [ABP GitHub Repository](https://github.com/abpframework/abp) + +## Common Mistakes to Avoid + +❌ **Don't expose entities from application services** - Always return DTOs +❌ **Don't put business logic in application services** - Use domain services (`*Manager`) +❌ **Don't use non-virtual methods** - All public methods must be virtual +❌ **Don't manually filter by TenantId** - ABP does this automatically +❌ **Don't create custom repositories unnecessarily** - Use `IRepository` first +❌ **Don't mix host and tenant data in same DbContext** - Separate contexts for isolation +❌ **Don't forget [Authorize] attributes** - Always check permissions +❌ **Don't ignore nullable warnings** - Fix them properly +❌ **Don't use manual AJAX** - Use ABP's dynamic JavaScript proxies +❌ **Don't create global JavaScript variables** - Wrap in IIFE pattern +❌ **Don't hardcode strings in JavaScript** - Use `abp.localization` +❌ **Don't bypass ABP modal manager** - Use `abp.ModalManager` for modals +❌ **Don't forget DataTable reload** - Call `dataTable.ajax.reload()` after CRUD + +## Front-End Development Patterns + +### Client-Side Package Management + +**Adding NPM packages:** +1. Add to `package.json` (prefer `@abp/*` packages for consistency) +2. Run `npm install` +3. Configure `abp.resourcemapping.js` to map resources from `node_modules` to `wwwroot/libs` +4. Run `abp install-libs` to copy resources +5. Add to bundle contributor in `Unity.Theme.UX2` module + +**Example resource mapping:** +```javascript +// abp.resourcemapping.js +module.exports = { + aliases: { + '@node_modules': './node_modules', + '@libs': './wwwroot/libs', + }, + mappings: { + '@node_modules/datatables.net-bs5/': '@libs/datatables.net-bs5/', + '@node_modules/echarts/dist/echarts.min.js': '@libs/echarts/', + }, +}; +``` + +### JavaScript Structure and Conventions + +**Standard page script pattern:** +```javascript +(function ($) { + var l = abp.localization.getResource('GrantManager'); + + // DataTable initialization + var dataTable = $('#MyTable').DataTable( + abp.libs.datatables.normalizeConfiguration({ + // Configuration + }) + ); + + // Modal initialization + var createModal = new abp.ModalManager({ + viewUrl: abp.appPath + 'GrantManager/MyFeature/CreateModal', + modalClass: 'myFeatureCreate' + }); + + createModal.onResult(function () { + dataTable.ajax.reload(); + }); + + // Event handlers + $('#NewRecordButton').click(function (e) { + e.preventDefault(); + createModal.open(); + }); + +})(jQuery); +``` + +**Always:** +- Wrap in IIFE: `(function ($) { ... })(jQuery);` +- Use `var l = abp.localization.getResource('GrantManager');` for localization +- Use `abp.notify` for success/error messages +- Use `abp.message.confirm()` for confirmation dialogs +- Use `abp.auth.isGranted()` for permission checks + +### ABP Dynamic JavaScript API Client Proxies + +**How it works:** +- Application services are automatically exposed as JavaScript functions +- Namespace follows pattern: `[moduleName].[namespace].[serviceName].[methodName]()` +- Functions return jQuery Deferred objects (use `.then()`, `.catch()`) +- Auto-generated from `/Abp/ServiceProxyScript` endpoint + +**Example usage:** +```javascript +// GET list +acme.grantManager.applications.application.getList({ + maxResultCount: 10, + filter: 'search' +}).then(function(result) { + console.log(result.items); +}); + +// POST create +acme.grantManager.applications.application.create({ + title: 'New Application' +}).then(function(result) { + abp.notify.success(l('SavedSuccessfully')); +}); + +// DELETE +acme.grantManager.applications.application + .delete(id) + .then(function() { + abp.notify.success(l('SuccessfullyDeleted')); + dataTable.ajax.reload(); + }); +``` + +**Benefits:** +- No manual AJAX configuration +- Type-safe (parameters match C# signatures) +- Automatic error handling +- Consistent with ABP conventions + +### DataTables.net Integration + +**Unity Grant Manager uses DataTables.net 1.x** (not 2.x due to ABP compatibility). + +**Standard DataTable pattern:** +```javascript +var dataTable = $('#MyTable').DataTable(abp.libs.datatables.normalizeConfiguration({ + processing: true, + serverSide: true, + paging: true, + ajax: abp.libs.datatables.createAjax( + acme.grantManager.myService.getList, + function () { + // Return additional filter parameters + return { + filter: $('#SearchInput').val(), + status: $('#StatusFilter').val() + }; + } + ), + columnDefs: [ + { + title: l('Actions'), + rowAction: { + items: [ + { + text: l('Edit'), + visible: abp.auth.isGranted('GrantManager.Edit'), + action: function (data) { + editModal.open({ id: data.record.id }); + } + }, + { + text: l('Delete'), + confirmMessage: function (data) { + return l('DeleteConfirmationMessage', data.record.name); + }, + action: function (data) { + acme.grantManager.myService + .delete(data.record.id) + .then(function () { + abp.notify.success(l('SuccessfullyDeleted')); + dataTable.ajax.reload(); + }); + } + } + ] + } + }, + { + title: l('Name'), + data: 'name' + }, + { + title: l('CreationTime'), + data: 'creationTime', + dataFormat: 'datetime' // ABP auto-formatting + } + ] +})); +``` + +**Key patterns:** +- Use `abp.libs.datatables.normalizeConfiguration()` wrapper +- Use `abp.libs.datatables.createAjax()` for server-side pagination +- Use `rowAction` for action buttons with permission checks +- Use `dataFormat` property for automatic date/boolean formatting +- Call `dataTable.ajax.reload()` after CRUD operations + +### ABP Modal Manager + +**Creating modals:** +```javascript +var createModal = new abp.ModalManager({ + viewUrl: abp.appPath + 'GrantManager/Applications/CreateModal', + scriptUrl: abp.appPath + 'Pages/GrantManager/Applications/CreateModal.js', + modalClass: 'applicationCreate' +}); + +createModal.onResult(function () { + abp.notify.success(l('SavedSuccessfully')); + dataTable.ajax.reload(); +}); + +$('#NewButton').click(function (e) { + e.preventDefault(); + createModal.open(); +}); +``` + +**Modal script class (CreateModal.js):** +```javascript +abp.modals.applicationCreate = function () { + var _$form = null; + + this.init = function (modalManager, args) { + _$form = modalManager.getForm(); + + // Custom initialization logic + _$form.find('#ProgramId').change(function () { + // Handle program change + }); + }; +}; +``` + +**Closing modal after save:** +```csharp +// In Razor Page code-behind +public async Task OnPostAsync() +{ + await _appService.CreateAsync(Model); + return NoContent(); // Return NoContent to close modal and trigger onResult +} +``` + +### ABP JavaScript Utilities + +**Localization:** +```javascript +var l = abp.localization.getResource('GrantManager'); +var message = l('WelcomeMessage'); +var formatted = l('GreetingMessage', userName); +``` + +**Notifications:** +```javascript +abp.notify.success('Success message'); +abp.notify.error('Error message'); +abp.notify.warn('Warning message'); +abp.notify.info('Info message'); +``` + +**Confirmation dialogs:** +```javascript +abp.message.confirm( + 'Are you sure?', + 'Confirm Action' +).then(function (confirmed) { + if (confirmed) { + // Perform action + } +}); +``` + +**Authorization:** +```javascript +if (abp.auth.isGranted('GrantManager.Applications.Edit')) { + // Show edit button +} +``` + +**Busy indicator:** +```javascript +abp.ui.setBusy('#MyForm'); +// ... operation ... +abp.ui.clearBusy('#MyForm'); +``` + +### DOM Auto-Initialization + +ABP automatically initializes these components via DOM event handlers: + +- **Tooltips:** `data-bs-toggle="tooltip"` +- **Popovers:** `data-bs-toggle="popover"` +- **Datepickers:** `` or `` +- **AJAX Forms:** `
` +- **Autocomplete Selects:** ` + +``` + +## Code Generation Guidelines + +When generating code: + +1. **Check layer dependencies** - Ensure proper dependency direction (Domain ← Application ← Web) +2. **Use ABP base classes** - Don't create from scratch what ABP provides +3. **Follow naming conventions** - Especially `*Manager` for domain services, `*AppService` for application services +4. **Make methods virtual** - Critical for extensibility and ABP conventions +5. **Use proper DTOs** - Never expose entities in API/application layer +6. **Apply multi-tenancy** - Implement `IMultiTenant` for tenant data +7. **Add authorization** - Include `[Authorize]` attributes with permission names +8. **Write tests** - Generate corresponding test class with xUnit/Shouldly +9. **Consider events** - Use distributed events for cross-module communication +10. **Document complex logic** - Add XML comments for public APIs + +## When in Doubt + +1. Check existing code patterns in the same layer/project +2. Refer to [ARCHITECTURE.md](../ARCHITECTURE.md) for architectural decisions +3. Consult [CONTRIBUTING.md](../CONTRIBUTING.md) for detailed patterns and examples +4. Review ABP Framework best practices documentation +5. Ask for clarification rather than making assumptions that violate ABP conventions diff --git a/applications/Unity.GrantManager/.github/plan-template.md b/applications/Unity.GrantManager/.github/plan-template.md new file mode 100644 index 000000000..751ee6935 --- /dev/null +++ b/applications/Unity.GrantManager/.github/plan-template.md @@ -0,0 +1,354 @@ +--- +title: [Short descriptive title of the feature] +version: 1.0 +date_created: [YYYY-MM-DD] +last_updated: [YYYY-MM-DD] +--- + +# Implementation Plan: [Feature Name] + +## Overview + +[Brief description of the feature requirements and business goals. Include the problem this feature solves and the value it provides to users.] + +## Requirements Summary + +### Functional Requirements +- [Requirement 1] +- [Requirement 2] +- [Requirement 3] + +### Non-Functional Requirements +- [Performance, security, scalability considerations] +- [Compliance or regulatory requirements] +- [Integration requirements with external systems] + +### User Stories (if applicable) +- As a [user role], I want to [action] so that [benefit] +- As a [user role], I want to [action] so that [benefit] + +## Architecture and Design + +### Affected ABP Layers +- [ ] **Domain Layer**: New entities, domain services, repository interfaces, domain events +- [ ] **Application Layer**: Application services, DTOs, AutoMapper profiles +- [ ] **EntityFrameworkCore Layer**: DbContext changes, repository implementations, entity configurations +- [ ] **HttpApi Layer**: API controllers, endpoints +- [ ] **Web Layer**: Razor Pages, view components, JavaScript, CSS + +### Impacted Modules +- [ ] **Unity.GrantManager** (main application) +- [ ] **Unity.Flex** (dynamic forms) +- [ ] **Unity.Notifications** (email notifications) +- [ ] **Unity.Payments** (payment processing) +- [ ] **Unity.Reporting** (analytics/reports) +- [ ] **Unity.Identity.Web** (user management) +- [ ] **Unity.TenantManagement** (tenant admin) +- [ ] **Unity.SharedKernel** (utilities) + +### Multi-Tenancy Considerations +- [ ] **Tenant-Scoped Data**: Will this feature store tenant-specific data? + - If yes, entities must implement `IMultiTenant` and use `GrantTenantDbContext` +- [ ] **Host-Level Data**: Will this feature require global/host data? + - If yes, use `GrantManagerDbContext` for cross-tenant entities +- [ ] **Tenant Isolation**: How will data isolation be enforced? +- [ ] **Cross-Tenant Operations**: Are there any scenarios where cross-tenant access is needed? + +### Integration Points + +#### Internal Modules +- [Describe how this feature integrates with Unity.Flex, Unity.Notifications, Unity.Payments, etc.] +- [Specify if domain events or distributed events are used for communication] + +#### External Systems +- [ ] **CHES (Email Service)**: [Describe email notification requirements] +- [ ] **CAS (Payment System)**: [Describe payment integration needs] +- [ ] **Keycloak (Identity)**: [Describe authentication/authorization changes] +- [ ] **AWS S3 (Storage)**: [Describe document/file storage needs] + +### Data Model Changes + +#### New Entities +- **EntityName** (`GrantTenantDbContext` or `GrantManagerDbContext`) + - Properties: [List key properties] + - Relationships: [Describe foreign keys and navigation properties] + - Aggregate Root: [Yes/No] + +#### Modified Entities +- **EntityName**: [Describe changes - new properties, relationship changes, etc.] + +#### Database Migrations +- [ ] Host migration required (`GrantManagerDbContext`) +- [ ] Tenant migration required (`GrantTenantDbContext`) + +### API Design + +#### New Endpoints +- `GET /api/grant-manager/[resource]` - [Description] +- `POST /api/grant-manager/[resource]` - [Description] +- `PUT /api/grant-manager/[resource]/{id}` - [Description] +- `DELETE /api/grant-manager/[resource]/{id}` - [Description] + +#### Request/Response DTOs +- `Create[Entity]Dto`: [Key properties] +- `Update[Entity]Dto`: [Key properties] +- `[Entity]Dto`: [Key properties] + +### UI/UX Changes + +#### New Pages/Components +- [Page/Component Name]: [Description and purpose] + +#### Modified Pages/Components +- [Page/Component Name]: [Description of changes] + +#### User Flows +1. [Step-by-step user interaction flow] +2. [Include decision points and alternate paths] + +### Security & Permissions + +#### New Permissions +- `GrantManager.[Resource].Create` +- `GrantManager.[Resource].Edit` +- `GrantManager.[Resource].Delete` +- `GrantManager.[Resource].View` + +#### Authorization Rules +- [Describe who can access what, role-based rules, data-level security] + +### Events & Messaging + +#### Domain Events (Local) +- `[Entity][Action]Event`: Triggered when [condition], handled by [handler] + +#### Distributed Events (RabbitMQ) +- `[Entity][Action]Eto`: Published when [condition], consumed by [module/service] + +### Performance Considerations +- [Indexing strategy for new database fields] +- [Caching strategy (Redis) if applicable] +- [Query optimization approaches] +- [Background job requirements (Quartz.NET)] + +## Tasks + +### Domain Layer Tasks +- [ ] Define `[Entity]` aggregate root in Domain project + - [ ] Implement entity with proper encapsulation (private setters, business methods) + - [ ] Add validation logic and business rules + - [ ] Implement `IMultiTenant` if tenant-scoped + - [ ] Add domain events if needed +- [ ] Create `[Entity]Manager` domain service (if complex business logic required) + - [ ] Implement business logic methods + - [ ] Add validation and business rule enforcement + - [ ] Use `BusinessException` for domain errors +- [ ] Define `I[Entity]Repository` interface (if custom queries needed) + - [ ] Specify custom query methods beyond standard CRUD +- [ ] Add constants to Domain.Shared project + - [ ] String length constants + - [ ] Enums for entity states/types + +### Application Layer Tasks +- [ ] Define DTOs in Application.Contracts project + - [ ] `Create[Entity]Dto` with validation attributes + - [ ] `Update[Entity]Dto` with validation attributes + - [ ] `[Entity]Dto` (output DTO) + - [ ] List query DTOs (e.g., `Get[Entity]ListDto`) +- [ ] Define `I[Entity]AppService` interface in Application.Contracts + - [ ] Standard CRUD methods: `GetAsync`, `GetListAsync`, `CreateAsync`, `UpdateAsync`, `DeleteAsync` + - [ ] Custom methods for specific use cases +- [ ] Implement `[Entity]AppService` in Application project + - [ ] Inject required repositories and domain services + - [ ] Implement interface methods (all virtual) + - [ ] Apply `[Authorize]` attributes for permissions + - [ ] Use `ObjectMapper` for entity/DTO conversion + - [ ] Handle pagination and filtering in `GetListAsync` +- [ ] Configure AutoMapper profile in Application project + - [ ] Map entity to DTOs + - [ ] Handle nested objects and value objects + +### EntityFrameworkCore Layer Tasks +- [ ] Configure entity in `GrantManagerDbContextModelCreatingExtensions` or `GrantTenantDbContextModelCreatingExtensions` + - [ ] Define table name + - [ ] Configure properties (required, max length) + - [ ] Configure indexes (foreign keys, frequently queried fields) + - [ ] Configure relationships and foreign keys + - [ ] Call `ConfigureByConvention()` for ABP features +- [ ] Implement custom repository (if `I[Entity]Repository` was defined) + - [ ] Inherit from `EfCoreRepository` + - [ ] Implement custom query methods +- [ ] Create database migration + - [ ] Run `dotnet ef migrations add [MigrationName] --context [ContextName]` + - [ ] Review generated migration code + - [ ] Test migration on development database + +### HttpApi Layer Tasks +- [ ] Create `[Entity]Controller` in HttpApi project + - [ ] Inherit from `AbpController` + - [ ] Inject `I[Entity]AppService` + - [ ] Define route (`[Route("api/grant-manager/[resource]")]`) + - [ ] Implement API endpoints (GET, POST, PUT, DELETE) + - [ ] Return appropriate HTTP status codes + +### Web Layer Tasks +- [ ] Create Razor Pages in Web project + - [ ] Index page for listing entities + - [ ] Create/Edit modal or page + - [ ] Details page (if needed) +- [ ] Implement JavaScript functionality + - [ ] AJAX calls to API endpoints + - [ ] Client-side validation + - [ ] DataTables integration for lists (if applicable) +- [ ] Add navigation menu items + - [ ] Update main menu configuration + - [ ] Apply permission checks for visibility +- [ ] Localization + - [ ] Add localization keys to resource files + - [ ] Translate to supported languages + +### Testing Tasks +- [ ] Application service tests (xUnit + Shouldly) + - [ ] Test CRUD operations + - [ ] Test business logic validations + - [ ] Test authorization (permission checks) +- [ ] Domain service tests + - [ ] Test complex business rules + - [ ] Test domain events +- [ ] Integration tests + - [ ] Test API endpoints + - [ ] Test multi-tenancy isolation (if applicable) + +### Front-End Tasks +- [ ] Client-side package management (if new NPM packages needed) + - [ ] Add dependencies to `package.json` + - [ ] Configure `abp.resourcemapping.js` for resource mapping + - [ ] Run `abp install-libs` to copy resources + - [ ] Add to bundle contributor in `Unity.Theme.UX2` +- [ ] Page JavaScript implementation + - [ ] Create page script in `/Pages/[Feature]/[PageName].js` + - [ ] Wrap in IIFE pattern: `(function ($) { ... })(jQuery);` + - [ ] Initialize localization: `var l = abp.localization.getResource('GrantManager');` + - [ ] Configure DataTable with `abp.libs.datatables.normalizeConfiguration()` + - [ ] Use ABP dynamic proxies for API calls (e.g., `acme.grantManager.myService.getList()`) + - [ ] Implement modal managers for Create/Edit dialogs + - [ ] Add event handlers for filters and buttons + - [ ] Implement permission checks using `abp.auth.isGranted()` +- [ ] DataTable configuration + - [ ] Define columns with localized titles + - [ ] Configure row actions (Edit, Delete, custom actions) + - [ ] Add permission checks to action visibility + - [ ] Configure data formatting (datetime, boolean, enums) + - [ ] Implement server-side pagination with `abp.libs.datatables.createAjax()` + - [ ] Add custom filters (search, status, date ranges) +- [ ] Modal dialogs + - [ ] Create modal Razor Pages (CreateModal.cshtml, EditModal.cshtml) + - [ ] Implement modal script classes in `abp.modals.*` namespace + - [ ] Configure modal manager with `viewUrl` and `modalClass` + - [ ] Implement `onResult()` callback to reload DataTable + - [ ] Return `NoContent()` from page handler to close modal +- [ ] Form validation and AJAX submission + - [ ] Use `data-ajaxForm="true"` for AJAX forms + - [ ] Implement client-side validation with jQuery Validation + - [ ] Use `abp.notify` for success/error messages + - [ ] Handle errors with `abp.message` or `abp.notify.error` +- [ ] Localization + - [ ] Add localization keys to `Localization/GrantManager/en.json` + - [ ] Use `l('LocalizationKey')` in JavaScript for all user-facing text + - [ ] Test with multiple languages if multi-language support enabled +- [ ] UI/UX enhancements + - [ ] Add tooltips with `data-bs-toggle="tooltip"` + - [ ] Implement autocomplete selects with `class="auto-complete-select"` + - [ ] Add busy indicators for long operations with `abp.ui.setBusy()` + - [ ] Implement confirmation dialogs with `abp.message.confirm()` + +### Documentation Tasks +- [ ] Update API documentation (Swagger annotations) +- [ ] Add XML comments to public APIs +- [ ] Update user documentation (if applicable) +- [ ] Document any configuration changes + +## Implementation Sequence + +**Recommended order to implement tasks:** + +1. **Domain Layer** - Define entities, domain services, and core business logic +2. **Database Migration** - Create migration to support domain model +3. **Application Layer** - Implement use cases via application services and DTOs +4. **Testing** - Write and run tests for domain and application layers +5. **HttpApi Layer** - Expose API endpoints +6. **Web Layer** - Build UI pages and components +7. **Integration Testing** - Test end-to-end workflows +8. **Documentation** - Update all relevant documentation + +## Open Questions + +1. [Question about requirements, clarification needed on business rules, etc.] +2. [Question about technical approach, integration details, etc.] +3. [Question about edge cases, error handling, performance, etc.] + +## Assumptions + +- [Assumption 1 about system behavior, data availability, etc.] +- [Assumption 2 about user permissions, access patterns, etc.] + +## Dependencies + +### Blocking Dependencies +- [Prerequisite feature or task that must be completed first] + +### Related Features +- [Features that should be coordinated or implemented together] + +## Risks & Mitigation + +| Risk | Impact | Probability | Mitigation Strategy | +|------|--------|-------------|---------------------| +| [Risk description] | High/Med/Low | High/Med/Low | [How to mitigate or handle] | + +## Testing Strategy + +### Unit Tests +- [Scope of unit testing - domain services, application services] + +### Integration Tests +- [Scope of integration testing - database, external APIs] + +### Manual Testing Scenarios +1. [Scenario 1: User action → Expected outcome] +2. [Scenario 2: User action → Expected outcome] + +### Performance Testing +- [ ] Load testing (if applicable) +- [ ] Query performance validation +- [ ] Cache effectiveness verification + +## Rollout Plan + +### Feature Flags (if applicable) +- [ ] Enable/disable feature via ABP Feature Management +- [ ] Gradual rollout to tenants + +### Data Migration +- [ ] Existing data migration requirements +- [ ] Backward compatibility considerations + +### Deployment Steps +1. [Step 1] +2. [Step 2] +3. [Step 3] + +## Success Criteria + +- [ ] All functional requirements implemented and tested +- [ ] All tests passing (unit, integration, manual) +- [ ] Code review completed and approved +- [ ] Documentation updated +- [ ] Performance benchmarks met +- [ ] Security review completed (if applicable) +- [ ] Deployed to staging environment successfully +- [ ] User acceptance testing completed + +## Notes + +[Any additional notes, references, or context that doesn't fit in other sections] diff --git a/applications/Unity.GrantManager/.github/prompts/implement-tdd.prompt.md b/applications/Unity.GrantManager/.github/prompts/implement-tdd.prompt.md new file mode 100644 index 000000000..37ea47902 --- /dev/null +++ b/applications/Unity.GrantManager/.github/prompts/implement-tdd.prompt.md @@ -0,0 +1,41 @@ +--- +agent: tdd +description: Implement a feature using test-driven development based on an implementation plan. +--- + +Please implement the feature described in the plan file: #{{planFile}} + +Follow strict test-driven development (TDD) methodology: + +1. **Red-Green-Refactor Cycle**: For each task, write the test first (failing), implement minimal code to make it pass, then refactor while keeping tests green. + +2. **Implementation Order**: Follow the sequence outlined in the plan: + - Domain Layer (entities, domain services) - test first + - Database migrations + - Application Layer (app services, DTOs) - test first + - API Layer (controllers) + - Integration tests + - Web Layer (UI) + +3. **ABP Framework Conventions**: Strictly follow patterns documented in CONTRIBUTING.md: + - Inherit from proper base classes (`ApplicationService`, `DomainService`, `FullAuditedAggregateRoot`) + - All public methods must be `virtual` + - Application services return DTOs only, never entities + - Use `Manager` suffix for domain services + - Implement `IMultiTenant` for tenant-scoped entities + - Apply `[Authorize]` attributes for permissions + +4. **Testing Standards**: Use xUnit + Shouldly: + - `Should_[Expected]_[Scenario]` naming + - Arrange-Act-Assert pattern + - Run tests after each implementation step + - Ensure no regressions in full test suite + +5. **Progress Updates**: Provide clear status on which tasks are complete and what's next. + +6. **Quality Gates**: Don't move to the next task until: + - All tests for current task pass + - Code follows ABP conventions + - No test regressions + +Work through the plan systematically, one task at a time, ensuring quality through testing at every step. diff --git a/applications/Unity.GrantManager/.github/prompts/plan-from-issue.prompt.md b/applications/Unity.GrantManager/.github/prompts/plan-from-issue.prompt.md new file mode 100644 index 000000000..0940749ff --- /dev/null +++ b/applications/Unity.GrantManager/.github/prompts/plan-from-issue.prompt.md @@ -0,0 +1,14 @@ +--- +agent: plan +description: Generate an implementation plan from a GitHub issue. +--- + +Please create a detailed implementation plan for the feature/fix described in GitHub issue #{{issueNumber}}. + +Workflow: +1. Use available GitHub tools to fetch the issue description and comments +2. Analyze the requirements from the issue content +3. If requirements are unclear, ask 2-3 clarifying questions before proceeding +4. Follow your standard planning workflow to create a comprehensive implementation plan based on the [plan template](../plan-template.md) + +The plan should follow ABP Framework conventions and Unity Grant Manager architectural patterns as documented in ARCHITECTURE.md and CONTRIBUTING.md. diff --git a/applications/Unity.GrantManager/.github/prompts/plan.prompt.md b/applications/Unity.GrantManager/.github/prompts/plan.prompt.md new file mode 100644 index 000000000..209377a66 --- /dev/null +++ b/applications/Unity.GrantManager/.github/prompts/plan.prompt.md @@ -0,0 +1,15 @@ +--- +agent: plan +description: Create a detailed implementation plan with clarifying questions. +--- + +Briefly analyze my feature request, then ask me 3 focused questions to clarify the requirements before creating the implementation plan. + +Focus your questions on: +- Business rules and validation logic +- Data relationships and entity structures +- User flows and UI requirements +- Integration points with Unity modules or external systems (CHES, CAS, Keycloak) +- Multi-tenancy considerations (tenant-scoped vs host-scoped data) + +Once I answer your questions, proceed with your standard planning workflow to create a comprehensive implementation plan. diff --git a/applications/Unity.GrantManager/ARCHITECTURE.md b/applications/Unity.GrantManager/ARCHITECTURE.md new file mode 100644 index 000000000..26c89ebd3 --- /dev/null +++ b/applications/Unity.GrantManager/ARCHITECTURE.md @@ -0,0 +1,619 @@ +# Unity Grant Manager - System Architecture + +## Overview + +Unity Grant Manager is built on **ABP Framework 9.1.3**, following Domain-Driven Design (DDD) principles and implementing a modular monolith architecture. The application leverages ABP's opinionated architecture to build enterprise-grade grant management software with clean separation of concerns, multi-tenancy support, and extensible module design. + +## Technology Stack + +### Core Framework & Runtime +- **.NET 9.0**: Latest .NET platform with C# 12.0 and nullable reference types enabled +- **ABP Framework 9.1.3**: Application framework providing DDD infrastructure, modularity, and multi-tenancy +- **ASP.NET Core MVC**: Web application framework with Razor Pages for server-side rendering + +### Data & Persistence +- **PostgreSQL**: Primary relational database management system +- **Entity Framework Core 9.0.5**: ORM for data access with Npgsql provider +- **Redis**: Distributed caching and data protection key storage +- **Common Object Management Service (COMS)**: Blob storage for document management + +### Front-End & UI +- **Unity.Theme.UX2**: Custom theme module for consistent government branding +- **Bootstrap 5**: UI component framework +- **jQuery**: JavaScript utilities and DOM manipulation +- **Bundling & Minification**: ABP bundling system for client-side resource optimization + +### Messaging & Background Jobs +- **RabbitMQ**: Message broker for event bus and distributed event handling +- **Quartz.NET**: Background job scheduling and execution with clustering support + +### Authentication & Authorization +- **Keycloak**: Identity provider for OpenID Connect authentication +- **ABP Identity**: User and role management infrastructure + +### Testing & Quality +- **xUnit**: Test framework for unit and integration tests +- **Shouldly**: Fluent assertion library +- **MiniProfiler**: Performance profiling and diagnostics + +### Logging & Monitoring +- **Serilog**: Structured logging with multiple sink support +- **ABP Audit Logging**: Comprehensive audit trail for all system operations + +## Architectural Patterns + +### Domain-Driven Design (DDD) + +Unity Grant Manager follows DDD tactical patterns as prescribed by ABP Framework: + +- **Entities & Aggregate Roots**: Core business objects with identity and lifecycle +- **Value Objects**: Immutable objects defined by their attributes +- **Domain Services**: Business logic that doesn't naturally fit within entities (suffix: `Manager`) +- **Repositories**: Abstract data access with `IRepository` pattern +- **Domain Events**: Decouple domain logic and enable event-driven architecture +- **Application Services**: Use case orchestration layer (inherit from `ApplicationService`) +- **Data Transfer Objects (DTOs)**: API contract objects for input/output + +### Multi-Tenancy Architecture + +Unity Grant Manager implements multi-tenancy with **database-per-tenant isolation**: + +```mermaid +graph TB + subgraph "Multi-Tenant Data Architecture" + WebApp[Web Application] + HostDb[(Host Database
GrantManagerDbContext)] + TenantDb1[(Tenant 1 Database
GrantTenantDbContext)] + TenantDb2[(Tenant 2 Database
GrantTenantDbContext)] + TenantDb3[(Tenant N Database
GrantTenantDbContext)] + + WebApp -->|Host Data
Tenants, Users, Settings| HostDb + WebApp -->|Tenant 1 Data
Applications, Assessments| TenantDb1 + WebApp -->|Tenant 2 Data
Applications, Assessments| TenantDb2 + WebApp -->|Tenant N Data
Applications, Assessments| TenantDb3 + end + + style HostDb fill:#e1f5ff + style TenantDb1 fill:#fff4e1 + style TenantDb2 fill:#fff4e1 + style TenantDb3 fill:#fff4e1 +``` + +**Key Components:** +- **GrantManagerDbContext**: Host database context for shared/global data (tenants, users, global settings) +- **GrantTenantDbContext**: Tenant-specific database context with `[IgnoreMultiTenancy]` attribute for tenant-scoped entities +- **Separate Migrations**: Distinct migration streams for host and tenant databases +- **Tenant Resolver**: Automatically determines current tenant from request context (URL, header, or claims) + +## Module Architecture + +Unity Grant Manager follows ABP's modular architecture with internal and external modules: + +### Module Dependency Graph + +```mermaid +--- +config: + layout: elk + theme: redux + htmlLabels: true +title: Module Dependency Graph +--- +flowchart TB + subgraph subGraph0["Unity Grant Manager Application"] + Web["Unity.GrantManager.Web
Razor Pages and UI"] + HttpApi["Unity.GrantManager.HttpApi
REST API Controllers"] + App["Unity.GrantManager.Application
Application Services"] + AppContracts["Unity.GrantManager.Application.Contracts
Service Interfaces, DTOs"] + Domain["Unity.GrantManager.Domain
Entities, Repositories, Domain Services"] + DomainShared["Unity.GrantManager.Domain.Shared
Enums, Constants"] + EFCore["Unity.GrantManager.EntityFrameworkCore
DbContext, Repositories, EF Config"] + end + subgraph subGraph1["Unity Platform Modules"] + Flex["Unity.Flex
Dynamic Forms"] + Notifications["Unity.Notifications
CHES Email Integration"] + Payments["Unity.Payments
CAS Payment Integration"] + Reporting["Unity.Reporting
Report Generation"] + Identity["Unity.Identity.Web
Identity UI"] + Tenant["Unity.TenantManagement
Tenant Admin"] + Theme["Unity.Theme.UX2
UI Theme"] + SharedKernel["Unity.SharedKernel
Utilities, Message Brokers"] + end + Web --> HttpApi & App & Theme & Identity & EFCore + HttpApi --> AppContracts + App --> AppContracts & Domain & Flex & Notifications & Payments & Reporting & SharedKernel + AppContracts --> DomainShared + Domain --> DomainShared + EFCore --> Domain + + Web:::GrantApp + HttpApi:::GrantApp + App:::GrantApp + AppContracts:::GrantApp + Domain:::GrantApp + DomainShared:::GrantApp + EFCore:::GrantApp + Flex:::Platform + Notifications:::Peach + Payments:::Peach + Reporting:::Platform + Identity:::Platform + Tenant:::Platform + Theme:::Platform + SharedKernel:::Platform + classDef GrantApp fill:#BBDEFB,stroke:#000000,stroke-width:4px,color:#0D47A1 + classDef Platform fill:#C8E6C9,stroke:#2E7D32,stroke-width:4px,color:#1B5E20 + classDef Peach fill:#FFEFDB,stroke:#FBB35A,stroke-width:4px,color:#8F632D + classDef Neutral fill:#E0E0E0,stroke:#757575,stroke-width:4px,color:#424242 + style App stroke:#000000 +``` + +### Module Descriptions + +#### Unity.GrantManager (Main Application) +The core grant management application implementing grant programs, applications, assessments, and related business logic. + +**Layers:** +- **Web**: Razor Pages, view components, client-side assets, MVC controllers for UI +- **HttpApi**: RESTful API controllers extending `AbpController` +- **Application**: Application services implementing business use cases, inheriting from `ApplicationService` +- **Application.Contracts**: Service interfaces, DTOs, and application-layer contracts +- **Domain**: Entities (applications, assessments, programs), domain services, repository interfaces +- **Domain.Shared**: Enums, constants, shared types +- **EntityFrameworkCore**: EF Core DbContexts (`GrantManagerDbContext`, `GrantTenantDbContext`), repository implementations, entity configurations + +#### Unity.Flex (Dynamic Forms Module) +Provides dynamic form/field definition and rendering capabilities for customizable grant application forms. + +**Key Features:** +- Custom field definitions with validation rules +- Form layout and section management +- Runtime form rendering with data binding +- Field value storage and retrieval + +**Integration:** Grant application forms are built using Flex definitions, allowing program administrators to customize intake forms without code changes. + +#### Unity.Notifications (Notification Module) +Handles email notifications through CHES (Common Hosted Email Service) integration. + +**Key Features:** +- Email template management +- CHES API integration for government email delivery +- Notification queue and retry logic +- Notification history and tracking + +**Integration:** Triggered by domain events from Grant Manager (application submitted, assessment completed, payment processed) to send automated email notifications. + +#### Unity.Payments (Payment Processing Module) +Integrates with CAS (Common Accounting System) for government payment processing. + +**Key Features:** +- CAS API integration for payment submission +- Payment status tracking and reconciliation +- Invoice generation and management +- Payment approval workflows + +**Integration:** Grant Manager creates payment requests for approved applications, which are processed through Unity.Payments to CAS. + +#### Unity.Reporting (Reporting Module) +Advanced reporting and analytics capabilities. + +**Key Features:** +- Custom report definitions +- Data visualization and dashboards +- Report scheduling and distribution +- Export formats (PDF, Excel, CSV) + +**Integration:** Provides reporting on grant applications, assessment outcomes, payment distributions, and program performance. + +#### Unity.Identity.Web (Identity UI Module) +Custom user interface for identity management operations. + +**Key Features:** +- User registration and profile management +- Login/logout pages with Keycloak integration +- Password reset and account recovery +- Organization/team management UI + +#### Unity.TenantManagement (Tenant Management Module) +Multi-tenant administration interface. + +**Key Features:** +- Tenant creation and configuration +- Database connection string management +- Tenant-specific feature toggles +- Tenant user assignments + +#### Unity.Theme.UX2 (UI Theme Module) +Consistent government branding and user experience. + +**Key Features:** +- BC Government visual identity compliance +- Responsive layouts and components +- Accessibility (WCAG 2.1 AA) compliance +- Reusable UI components and patterns + +#### Unity.SharedKernel (Shared Utilities Module) +Cross-cutting utilities and infrastructure shared across modules. + +**Key Features:** +- HTTP client factories and helpers +- RabbitMQ message broker configuration +- Correlation ID propagation for distributed tracing +- Feature flags and utilities +- Integration abstractions + +### Module Communication Patterns + +```mermaid +sequenceDiagram + participant User + participant GrantManager + participant Flex + participant Notifications + participant Payments + participant RabbitMQ + + User->>GrantManager: Submit Grant Application + GrantManager->>Flex: Validate Form Data + Flex-->>GrantManager: Validation Result + GrantManager->>GrantManager: Create Application Entity + GrantManager->>RabbitMQ: Publish ApplicationSubmittedEvent + + RabbitMQ->>Notifications: ApplicationSubmittedEvent + Notifications->>Notifications: Generate Email from Template + Notifications->>CHES: Send Confirmation Email + CHES-->>Notifications: Email Sent + + Note over GrantManager: Assessment Process... + + GrantManager->>RabbitMQ: Publish ApplicationApprovedEvent + RabbitMQ->>Payments: ApplicationApprovedEvent + Payments->>Payments: Create Payment Request + Payments->>CAS: Submit Payment + CAS-->>Payments: Payment Confirmation + + Payments->>RabbitMQ: Publish PaymentProcessedEvent + RabbitMQ->>GrantManager: PaymentProcessedEvent + GrantManager->>GrantManager: Update Application Status + + RabbitMQ->>Notifications: PaymentProcessedEvent + Notifications->>CHES: Send Payment Notification +``` + +**Communication Mechanisms:** +1. **Direct Service References**: Modules can directly inject and call services from dependent modules (e.g., GrantManager → Flex for form validation) +2. **Domain Events (Local)**: In-process events for same-database transactions using ABP's `ILocalEventBus` +3. **Distributed Events (RabbitMQ)**: Cross-module/cross-database events using ABP's `IDistributedEventBus` with RabbitMQ transport +4. **HTTP APIs**: RESTful APIs for external integrations or microservice scenarios + +## Layer Structure & Dependencies + +Unity Grant Manager follows ABP's layered architecture with strict dependency rules: + +```mermaid +graph TD + subgraph "Presentation Layer" + UI[Web] + end + + subgraph "API Layer" + HttpApi[HttpApi
Controllers] + HttpApiClient[HttpApi.Client
C# API Proxies] + end + + subgraph "Application Layer" + App[Application
Services Implementation] + AppContracts[Application.Contracts
Interfaces & DTOs] + end + + subgraph "Domain Layer" + Domain[Domain
Entities, Domain Services, Repositories] + DomainShared[Domain.Shared
Constants, Enums] + end + + subgraph "Infrastructure Layer" + EFCore[EntityFrameworkCore
DbContext, Repositories] + end + + UI --> HttpApi + UI --> App + UI --> AppContracts + HttpApi --> AppContracts + HttpApiClient --> AppContracts + App --> AppContracts + App --> Domain + AppContracts --> DomainShared + Domain --> DomainShared + EFCore --> Domain + + style UI fill:#4a90e2 + style App fill:#7b68ee + style Domain fill:#50c878 + style EFCore fill:#f4a460 +``` + +### Dependency Rules + +1. **Domain Layer** has no dependencies on other layers (only on ABP framework) +2. **Application.Contracts** depends only on **Domain.Shared** +3. **Application** depends on **Domain** and **Application.Contracts** +4. **Infrastructure** (EF Core) depends on **Domain** only +5. **HttpApi** depends on **Application.Contracts** +6. **Web** can depend on any layer for hosting, but business logic stays in Application/Domain + +### Project Dependencies (Actual) + +**Unity.GrantManager.Web** depends on: +- Unity.GrantManager.Application +- Unity.GrantManager.HttpApi +- Unity.GrantManager.EntityFrameworkCore +- Unity.Theme.UX2 +- Unity.Identity.Web + +**Unity.GrantManager.Application** depends on: +- Unity.GrantManager.Application.Contracts +- Unity.GrantManager.Domain +- Unity.Flex +- Unity.Notifications +- Unity.Payments +- Unity.Reporting +- Unity.SharedKernel + +**Unity.GrantManager.Domain** depends on: +- Unity.GrantManager.Domain.Shared +- Volo.Abp.Identity.Domain +- Volo.Abp.TenantManagement.Domain +- Volo.Abp.AuditLogging.Domain + +**Unity.GrantManager.EntityFrameworkCore** depends on: +- Unity.GrantManager.Domain +- Volo.Abp.EntityFrameworkCore.PostgreSql + +## Data Flow & Request Pipeline + +### Typical Request Flow + +```mermaid +sequenceDiagram + participant Browser + participant Controller + participant AppService + participant DomainService + participant Repository + participant DbContext + participant Database + + Browser->>Controller: HTTP Request (POST /applications) + Controller->>Controller: Model Binding & Validation + Controller->>AppService: CreateApplicationAsync(dto) + + Note over AppService: Authorization Check
[Authorize] Attribute + Note over AppService: Start Unit of Work
Begin Transaction + + AppService->>AppService: Map DTO to Domain Entity + AppService->>DomainService: ValidateApplicationRules(entity) + DomainService-->>AppService: Validation Result + + AppService->>Repository: InsertAsync(entity) + Repository->>DbContext: Add(entity) + + Note over AppService: Publish Domain Event
ApplicationCreatedEvent + + AppService->>AppService: Commit Unit of Work + DbContext->>Database: INSERT Application + Database-->>DbContext: Success + + Note over AppService: Distributed Event
Published to RabbitMQ + + AppService->>AppService: Map Entity to DTO + AppService-->>Controller: ApplicationDto + Controller-->>Browser: HTTP 200 + JSON Response +``` + +### Cross-Cutting Concerns (Automatic via ABP) + +ABP Framework automatically handles the following concerns for application services: + +1. **Authorization**: `[Authorize]` attributes and permission checks via `IAuthorizationService` +2. **Validation**: Automatic input DTO validation using data annotations and FluentValidation +3. **Unit of Work**: Automatic transaction management with commit/rollback +4. **Audit Logging**: Automatic logging of method calls, parameters, and results +5. **Exception Handling**: Global exception filter with appropriate HTTP status codes +6. **Multi-Tenancy**: Automatic tenant resolution and data isolation + +## Database Schema Strategy + +### Multi-Database Approach + +```mermaid +erDiagram + HOST_DB ||--o{ TENANTS : contains + HOST_DB ||--o{ USERS : contains + HOST_DB ||--o{ ROLES : contains + HOST_DB ||--o{ SETTINGS : contains + + TENANT_DB ||--o{ GRANT_PROGRAMS : contains + TENANT_DB ||--o{ APPLICATIONS : contains + TENANT_DB ||--o{ ASSESSMENTS : contains + TENANT_DB ||--o{ PAYMENTS : contains + TENANT_DB ||--o{ DOCUMENTS : contains + + GRANT_PROGRAMS ||--o{ APPLICATIONS : has + APPLICATIONS ||--o{ ASSESSMENTS : has + APPLICATIONS ||--o{ PAYMENTS : receives + APPLICATIONS ||--o{ DOCUMENTS : includes +``` + +**Host Database (`GrantManagerDbContext`):** +- Tenant definitions and configurations +- Users and roles (cross-tenant identity) +- Global settings and feature flags +- Audit logs +- Background job definitions + +**Tenant Databases (`GrantTenantDbContext`):** +- Grant programs and configurations +- Applications and applicant data +- Assessment workflows and scores +- Payment requests and history +- Documents and attachments +- Tenant-specific settings + +### Migration Strategy + +1. **Host Migrations**: Located in `Unity.GrantManager.EntityFrameworkCore/Migrations/` + ```bash + dotnet ef migrations add --context GrantManagerDbContext + ``` + +2. **Tenant Migrations**: Located in `Unity.GrantManager.EntityFrameworkCore/TenantMigrations/` + ```bash + dotnet ef migrations add --context GrantTenantDbContext + ``` + +3. **DbMigrator**: Console application that applies both host and tenant migrations on startup + +## Deployment Architecture + +### Development Environment + +- **Single Instance**: All modules hosted in single ASP.NET Core process +- **Database**: Local PostgreSQL instance (can be Docker container) +- **Redis**: Local Redis instance (optional, uses in-memory cache as fallback) +- **RabbitMQ**: Local RabbitMQ instance (can be disabled for development) + +### Production Environment (Modular Monolith) + +```mermaid +graph TB + subgraph "Load Balancer (nginx)" + LB[nginx
Round-robin] + end + + subgraph "Web Application (3 replicas)" + Web1[Unity.GrantManager.Web
Instance 1] + Web2[Unity.GrantManager.Web
Instance 2] + Web3[Unity.GrantManager.Web
Instance 3] + end + + subgraph "Data Layer" + PG[(PostgreSQL
Host + Tenant DBs)] + Redis[(Redis
Cache + Sessions)] + S3[(Common Object Management Service
Blob Storage)] + end + + subgraph "Message Broker" + RabbitMQ[RabbitMQ
Event Bus] + end + + subgraph "External Services" + Keycloak[Keycloak
Identity Provider] + CHES[CHES
Email Service] + CAS[CAS
Payment System] + end + + LB --> Web1 + LB --> Web2 + LB --> Web3 + + Web1 --> PG + Web2 --> PG + Web3 --> PG + + Web1 --> Redis + Web2 --> Redis + Web3 --> Redis + + Web1 --> S3 + Web2 --> S3 + Web3 --> S3 + + Web1 --> RabbitMQ + Web2 --> RabbitMQ + Web3 --> RabbitMQ + + Web1 --> Keycloak + Web2 --> Keycloak + Web3 --> Keycloak + + Web1 --> CHES + Web2 --> CHES + Web3 --> CHES + + Web1 --> CAS + Web2 --> CAS + Web3 --> CAS +``` + +**Configuration:** +- Load balancer distributes requests across 3 web instances (Docker Compose with nginx) +- Redis used for distributed caching and session storage +- RabbitMQ provides reliable message delivery between instances +- PostgreSQL handles both host and multiple tenant databases +- Background jobs coordinated via Quartz.NET clustering + +## Security Architecture + +### Authentication Flow + +```mermaid +sequenceDiagram + participant User + participant WebApp + participant Keycloak + participant Database + + User->>WebApp: Access Protected Page + WebApp->>WebApp: Check Authentication + WebApp-->>User: Redirect to Login + User->>Keycloak: Login (username/password) + Keycloak->>Keycloak: Validate Credentials + Keycloak-->>User: Redirect with Auth Code + User->>WebApp: Auth Code + WebApp->>Keycloak: Exchange Code for Token + Keycloak-->>WebApp: ID Token + Access Token + WebApp->>WebApp: Validate Token & Create Session + WebApp->>Database: Load User Permissions + Database-->>WebApp: Roles & Permissions + WebApp-->>User: Redirect to Requested Page +``` + +### Authorization Model + +- **Role-Based Access Control (RBAC)**: Roles assigned to users (Admin, ProgramOfficer, Assessor, Applicant) +- **Permission-Based**: Granular permissions checked via `[Authorize]` attributes and `IAuthorizationService` +- **Multi-Tenant Isolation**: Tenant context automatically applied to all queries and operations +- **Data-Level Security**: Row-level security via ABP's data filters and tenant resolution + +## Performance & Scalability Considerations + +### Caching Strategy +- **Distributed Cache (Redis)**: Application settings, user permissions, frequently accessed lookup data +- **Local Memory Cache**: Static configuration, short-lived data +- **HTTP Response Caching**: Public pages and API responses with ETags + +### Database Optimization +- **Indexing**: Strategic indexes on foreign keys, tenant IDs, and frequently queried fields +- **Query Optimization**: `IQueryable` projections to load only required fields +- **Eager Loading**: Configured includes to avoid N+1 query problems +- **Async Operations**: All database operations use async/await pattern + +### Background Processing +- **Quartz.NET Jobs**: Long-running tasks (report generation, payment processing, email sending) +- **Clustering**: Background jobs coordinated across multiple instances +- **Event-Driven**: Asynchronous processing via RabbitMQ distributed events + +### Scalability +- **Horizontal Scaling**: Stateless web instances can be added behind load balancer +- **Database Partitioning**: Separate tenant databases enable independent scaling +- **Blob Storage**: Large files stored in COMS, not in database +- **CDN Ready**: Static assets can be served from CDN + +## References + +- [ABP Framework Documentation](https://docs.abp.io/en/abp/latest) +- [ABP Domain Driven Design](https://docs.abp.io/en/abp/latest/Domain-Driven-Design) +- [ABP Multi-Tenancy](https://docs.abp.io/en/abp/latest/Multi-Tenancy) +- [ABP Module Architecture Best Practices](https://docs.abp.io/en/abp/latest/Best-Practices/Module-Architecture) +- [Implementing Domain Driven Design (e-book)](https://abp.io/books/implementing-domain-driven-design) diff --git a/applications/Unity.GrantManager/CONTRIBUTING.md b/applications/Unity.GrantManager/CONTRIBUTING.md new file mode 100644 index 000000000..a617e4fd9 --- /dev/null +++ b/applications/Unity.GrantManager/CONTRIBUTING.md @@ -0,0 +1,1423 @@ +# Contributing to Unity Grant Manager + +## Overview + +Unity Grant Manager is built on **ABP Framework 9.1.3** following Domain-Driven Design (DDD) principles. This guide outlines coding conventions, patterns, and best practices to ensure consistency and maintainability across the codebase. + +## Prerequisites + +- .NET 9.0 SDK +- PostgreSQL 15 or higher +- ABP CLI (`dotnet tool install -g Volo.Abp.Cli`) +- Visual Studio 2022 or JetBrains Rider +- Docker Desktop (for local Redis and RabbitMQ) + +## Getting Started + +1. **Clone the repository** and navigate to `Unity.GrantManager` +2. **Install JavaScript dependencies**: Run `abp install-libs` in the application root +3. **Apply database migrations**: Set `Unity.GrantManager.DbMigrator` as startup project and run (Ctrl+F5) +4. **Run the application**: Set `Unity.GrantManager.Web` as startup project and run (F5) + +## ABP Framework Conventions + +Unity Grant Manager follows ABP Framework's opinionated architecture and best practices. All contributors should familiarize themselves with: + +- [ABP Best Practices Guide](https://docs.abp.io/en/abp/latest/Best-Practices) +- [Module Architecture Best Practices](https://docs.abp.io/en/abp/latest/Best-Practices/Module-Architecture) +- [Implementing Domain Driven Design (e-book)](https://abp.io/books/implementing-domain-driven-design) + +## Project Structure & Layers + +Unity Grant Manager follows ABP's layered architecture with strict dependency rules: + +``` +Unity.GrantManager.Domain.Shared (Constants, Enums) + ↑ +Unity.GrantManager.Domain (Entities, Domain Services, Repository Interfaces) + ↑ +Unity.GrantManager.Application.Contracts (Service Interfaces, DTOs) + ↑ +Unity.GrantManager.Application (Application Services Implementation) + ↑ +Unity.GrantManager.EntityFrameworkCore (DbContext, Repositories, EF Configuration) + ↑ +Unity.GrantManager.HttpApi (API Controllers) + ↑ +Unity.GrantManager.Web (Razor Pages, UI Components) +``` + +**Dependency Rules:** +- Domain layer has no dependencies on other layers (only ABP framework) +- Application.Contracts depends only on Domain.Shared +- Application depends on Domain and Application.Contracts +- EntityFrameworkCore depends only on Domain +- Higher layers can depend on lower layers, but not vice versa + +## Coding Conventions + +### C# Language Features + +- **Target Framework**: .NET 9.0 +- **Language Version**: C# 12.0 (latest) +- **Nullable Reference Types**: Enabled project-wide + - Always declare nullability explicitly: `string?` for nullable, `string` for non-nullable + - Use `null!` only when you're certain a value won't be null (e.g., dependency injection) + - Avoid nullable warnings; fix them properly + +### Naming Conventions + +- **Classes, Methods, Properties**: PascalCase (e.g., `GrantApplication`, `CreateApplicationAsync`) +- **Private Fields**: Camel case with underscore prefix (e.g., `_repository`, `_logger`) +- **Parameters, Local Variables**: Camel case (e.g., `applicationDto`, `userId`) +- **Constants**: PascalCase (e.g., `MaxApplicationTitleLength`) +- **Interfaces**: PascalCase with `I` prefix (e.g., `IApplicationRepository`) +- **Domain Services**: Suffix with `Manager` (e.g., `ApplicationManager`, `AssessmentManager`) +- **Application Services**: Suffix with `AppService` (e.g., `ApplicationAppService`) +- **DTOs**: Suffix with purpose (e.g., `CreateApplicationDto`, `ApplicationDto`) + +### Code Style + +- **Indentation**: 4 spaces (no tabs) +- **Line Length**: Aim for 120 characters max +- **Braces**: Always use braces for control structures, even single-line statements +- **Access Modifiers**: Always specify explicitly (e.g., `public`, `private`, `protected`) +- **Using Directives**: Place at the top of the file, outside namespace +- **Async Suffix**: Always suffix async methods with `Async` (e.g., `CreateAsync`, `GetListAsync`) + +## Domain Layer Patterns + +### Entities & Aggregate Roots + +**Inherit from ABP base classes:** + +```csharp +// For entities with GUID keys +public class GrantApplication : FullAuditedAggregateRoot, IMultiTenant +{ + public Guid? TenantId { get; set; } // Required for multi-tenant entities + + // Properties with private setters for encapsulation + public string Title { get; private set; } = string.Empty; + public ApplicationStatus Status { get; private set; } + + // Parameterless constructor for EF Core + private GrantApplication() { } + + // Public constructor with required parameters + public GrantApplication(Guid id, string title) : base(id) + { + SetTitle(title); + Status = ApplicationStatus.Draft; + } + + // Business logic methods (not simple setters) + public void SetTitle(string title) + { + Title = Check.NotNullOrWhiteSpace(title, nameof(title), MaxTitleLength); + } + + public void Submit() + { + if (Status != ApplicationStatus.Draft) + throw new BusinessException("Application can only be submitted from Draft status"); + + Status = ApplicationStatus.Submitted; + + // Publish domain event + AddDistributedEvent(new ApplicationSubmittedEto { ApplicationId = Id, Title = Title }); + } +} +``` + +**Best Practices:** +- Use `FullAuditedAggregateRoot` for entities requiring full audit trails (creation, modification, deletion tracking) +- Use `AuditedAggregateRoot` if soft-delete is not needed +- Use `AggregateRoot` for simple entities without auditing +- Implement `IMultiTenant` for tenant-specific entities (stored in `GrantTenantDbContext`) +- Use private setters and expose business methods instead +- Validate input in constructors and methods using `Check` helper class +- Publish domain events using `AddLocalEvent()` or `AddDistributedEvent()` + +### Domain Services + +**Use the `Manager` suffix and inherit from `DomainService`:** + +```csharp +public class ApplicationManager : DomainService +{ + private readonly IRepository _applicationRepository; + private readonly IRepository _programRepository; + + public ApplicationManager( + IRepository applicationRepository, + IRepository programRepository) + { + _applicationRepository = applicationRepository; + _programRepository = programRepository; + } + + public virtual async Task CreateAsync( + string title, + Guid programId, + Guid applicantId) + { + // Validate business rules + var program = await _programRepository.GetAsync(programId); + if (!program.IsAcceptingApplications()) + throw new BusinessException("Program is not currently accepting applications"); + + // Create entity + var application = new GrantApplication(GuidGenerator.Create(), title); + application.SetProgram(programId); + application.SetApplicant(applicantId); + + return await _applicationRepository.InsertAsync(application); + } + + public virtual async Task ValidateForSubmission(GrantApplication application) + { + // Complex validation logic that doesn't belong in the entity + if (string.IsNullOrWhiteSpace(application.Title)) + throw new BusinessException("Application must have a title"); + + // Check for duplicate submissions + var existingCount = await _applicationRepository.CountAsync(x => + x.ApplicantId == application.ApplicantId && + x.ProgramId == application.ProgramId && + x.Status != ApplicationStatus.Draft); + + if (existingCount > 0) + throw new BusinessException("You have already submitted an application for this program"); + } +} +``` + +**Best Practices:** +- Use domain services for business logic that doesn't naturally fit within a single entity +- Only include state-changing methods; use repositories directly for queries in application services +- Make methods `virtual` to allow overriding in derived classes +- Throw `BusinessException` with clear error codes for domain validation failures +- Accept and return domain entities only (never DTOs) +- Do not implement interfaces unless there's a specific need for multiple implementations + +### Repositories + +**Define custom repository interfaces only when needed:** + +```csharp +public interface IApplicationRepository : IRepository +{ + Task> GetApplicationsByProgramAsync(Guid programId, CancellationToken cancellationToken = default); + + Task GetSubmittedCountByApplicantAsync(Guid applicantId, CancellationToken cancellationToken = default); +} +``` + +**Best Practices:** +- Use `IRepository` generic repository for standard CRUD operations +- Define custom repository interface only for complex queries or specialized operations +- Place repository interfaces in the Domain project +- Implement custom repositories in EntityFrameworkCore project +- Use async methods with `CancellationToken` support +- Return domain entities, not DTOs (mapping happens in application layer) + +## Application Layer Patterns + +### Application Services + +**Inherit from `ApplicationService` and implement interface from Application.Contracts:** + +```csharp +// In Application.Contracts project +public interface IApplicationAppService : IApplicationService +{ + Task GetAsync(Guid id); + Task> GetListAsync(GetApplicationListDto input); + Task CreateAsync(CreateApplicationDto input); + Task UpdateAsync(Guid id, UpdateApplicationDto input); + Task DeleteAsync(Guid id); +} + +// In Application project +public class ApplicationAppService : ApplicationService, IApplicationAppService +{ + private readonly IRepository _applicationRepository; + private readonly ApplicationManager _applicationManager; + + public ApplicationAppService( + IRepository applicationRepository, + ApplicationManager applicationManager) + { + _applicationRepository = applicationRepository; + _applicationManager = applicationManager; + } + + [Authorize(GrantManagementPermissions.Applications.Create)] + public virtual async Task CreateAsync(CreateApplicationDto input) + { + // Use domain service for business logic + var application = await _applicationManager.CreateAsync( + input.Title, + input.ProgramId, + CurrentUser.GetId()); + + await _applicationRepository.InsertAsync(application); + + return ObjectMapper.Map(application); + } + + [Authorize(GrantManagementPermissions.Applications.Default)] + public virtual async Task GetAsync(Guid id) + { + var application = await _applicationRepository.GetAsync(id); + return ObjectMapper.Map(application); + } + + [Authorize(GrantManagementPermissions.Applications.Default)] + public virtual async Task> GetListAsync(GetApplicationListDto input) + { + var queryable = await _applicationRepository.GetQueryableAsync(); + + // Apply filters + queryable = queryable.WhereIf(!input.Filter.IsNullOrWhiteSpace(), + x => x.Title.Contains(input.Filter!)); + + // Get total count + var totalCount = await AsyncExecuter.CountAsync(queryable); + + // Apply sorting and paging + queryable = queryable + .OrderBy(input.Sorting ?? "Title") + .PageBy(input.SkipCount, input.MaxResultCount); + + // Execute query and map to DTOs + var applications = await AsyncExecuter.ToListAsync(queryable); + var dtos = ObjectMapper.Map, List>(applications); + + return new PagedResultDto(totalCount, dtos); + } +} +``` + +**Best Practices:** +- One application service per aggregate root +- Make all public methods `virtual` for extensibility +- Use `[Authorize]` attributes for permission checks +- Accept and return DTOs only (never expose domain entities directly) +- Use `ObjectMapper` for entity-to-DTO mapping (AutoMapper) +- Use `CurrentUser` to access current user information +- Use `AsyncExecuter` to execute async LINQ queries +- Methods are automatically wrapped in Unit of Work (transaction) +- Use domain services for complex business logic +- Use `WhereIf`, `OrderBy`, `PageBy` extension methods for querying + +### Data Transfer Objects (DTOs) + +**Define DTOs in Application.Contracts project:** + +```csharp +// Input DTO +public class CreateApplicationDto +{ + [Required] + [StringLength(ApplicationConsts.MaxTitleLength)] + public string Title { get; set; } = string.Empty; + + [Required] + public Guid ProgramId { get; set; } + + public string? Description { get; set; } +} + +// Output DTO +public class ApplicationDto : AuditedEntityDto +{ + public string Title { get; set; } = string.Empty; + public Guid ProgramId { get; set; } + public string ProgramName { get; set; } = string.Empty; + public ApplicationStatus Status { get; set; } + public string? Description { get; set; } +} + +// List query DTO +public class GetApplicationListDto : PagedAndSortedResultRequestDto +{ + public string? Filter { get; set; } + public ApplicationStatus? Status { get; set; } +} +``` + +**Best Practices:** +- Use data annotations for validation (`[Required]`, `[StringLength]`, etc.) +- Inherit from ABP base DTO classes when appropriate: + - `EntityDto`: Basic DTO with ID + - `AuditedEntityDto`: Includes creation time and creator + - `FullAuditedEntityDto`: Includes modification and deletion info + - `PagedAndSortedResultRequestDto`: For list queries with paging/sorting +- Use nullable types (`?`) for optional properties +- Initialize string properties to `string.Empty` to avoid nullable warnings +- Define constants for string lengths in Domain.Shared project + +### Object Mapping (AutoMapper) + +**Configure mappings in Application project's module class:** + +```csharp +public class GrantManagerApplicationAutoMapperProfile : Profile +{ + public GrantManagerApplicationAutoMapperProfile() + { + // Entity to DTO (read) + CreateMap(); + + // DTO to Entity (write) - rarely used, prefer constructors + CreateMap() + .Ignore(x => x.Id) + .Ignore(x => x.TenantId); + } +} +``` + +## Entity Framework Core Patterns + +### DbContext Configuration + +**Two DbContext classes for multi-tenancy:** + +```csharp +// Host database context (non-tenant data) +[ConnectionStringName("Default")] +public class GrantManagerDbContext : AbpDbContext +{ + public DbSet Users { get; set; } = null!; + public DbSet Tenants { get; set; } = null!; + + public GrantManagerDbContext(DbContextOptions options) + : base(options) { } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ConfigureGrantManager(); // Extension method for entity configuration + } +} + +// Tenant database context (tenant-specific data) +[ConnectionStringName("GrantManager")] +[IgnoreMultiTenancy] // This DbContext manages its own tenancy +public class GrantTenantDbContext : AbpDbContext +{ + public DbSet Applications { get; set; } = null!; + public DbSet Programs { get; set; } = null!; + public DbSet Assessments { get; set; } = null!; + + public GrantTenantDbContext(DbContextOptions options) + : base(options) { } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ConfigureGrantTenant(); + } +} +``` + +### Entity Configuration + +**Use fluent API in extension methods:** + +```csharp +public static class GrantManagerDbContextModelCreatingExtensions +{ + public static void ConfigureGrantTenant(this ModelBuilder builder) + { + Check.NotNull(builder, nameof(builder)); + + builder.Entity(b => + { + b.ToTable("GrantApplications"); + + // Configure properties + b.Property(x => x.Title) + .IsRequired() + .HasMaxLength(ApplicationConsts.MaxTitleLength); + + b.Property(x => x.Description) + .HasMaxLength(ApplicationConsts.MaxDescriptionLength); + + // Configure indexes + b.HasIndex(x => x.ProgramId); + b.HasIndex(x => x.ApplicantId); + b.HasIndex(x => x.Status); + + // Configure relationships + b.HasOne() + .WithMany() + .HasForeignKey(x => x.ProgramId) + .OnDelete(DeleteBehavior.Restrict); + + // Configure ABP features + b.ConfigureByConvention(); // Configures audit properties, multi-tenancy, etc. + }); + } +} +``` + +**Best Practices:** +- Separate entity configuration from DbContext class +- Use `ConfigureByConvention()` to apply ABP conventions +- Configure indexes on foreign keys and frequently queried fields +- Specify max lengths for string properties +- Use `DeleteBehavior.Restrict` for important relationships to prevent accidental cascading deletes +- Use table name pluralization (e.g., `GrantApplications`) + +### Custom Repository Implementation + +**Implement custom repositories in EntityFrameworkCore project:** + +```csharp +public class ApplicationRepository : EfCoreRepository, IApplicationRepository +{ + public ApplicationRepository(IDbContextProvider dbContextProvider) + : base(dbContextProvider) { } + + public virtual async Task> GetApplicationsByProgramAsync( + Guid programId, + CancellationToken cancellationToken = default) + { + var dbSet = await GetDbSetAsync(); + return await dbSet + .Where(x => x.ProgramId == programId) + .OrderByDescending(x => x.CreationTime) + .ToListAsync(cancellationToken); + } + + public virtual async Task GetSubmittedCountByApplicantAsync( + Guid applicantId, + CancellationToken cancellationToken = default) + { + var dbSet = await GetDbSetAsync(); + return await dbSet + .CountAsync(x => x.ApplicantId == applicantId && x.Status != ApplicationStatus.Draft, + cancellationToken); + } +} +``` + +## Testing Patterns + +### Unit Testing (Application Layer) + +**Use xUnit and Shouldly:** + +```csharp +public class ApplicationAppService_Tests : GrantManagerApplicationTestBase +{ + private readonly IApplicationAppService _applicationAppService; + private readonly IRepository _applicationRepository; + + public ApplicationAppService_Tests() + { + _applicationAppService = GetRequiredService(); + _applicationRepository = GetRequiredService>(); + } + + [Fact] + public async Task Should_Create_Application() + { + // Arrange + var input = new CreateApplicationDto + { + Title = "Test Application", + ProgramId = GrantManagerTestData.ProgramId + }; + + // Act + var result = await _applicationAppService.CreateAsync(input); + + // Assert + result.ShouldNotBeNull(); + result.Title.ShouldBe("Test Application"); + + // Verify in database + var application = await _applicationRepository.FindAsync(result.Id); + application.ShouldNotBeNull(); + application!.Title.ShouldBe("Test Application"); + } + + [Theory] + [InlineData("")] + [InlineData(null)] + public async Task Should_Not_Create_Application_With_Invalid_Title(string? invalidTitle) + { + // Arrange + var input = new CreateApplicationDto + { + Title = invalidTitle!, + ProgramId = GrantManagerTestData.ProgramId + }; + + // Act & Assert + await Should.ThrowAsync(async () => + { + await _applicationAppService.CreateAsync(input); + }); + } +} +``` + +**Best Practices:** +- Inherit from test base class that sets up DI container and database +- Use `[Fact]` for single test cases, `[Theory]` with `[InlineData]` for parameterized tests +- Use Shouldly assertions: `ShouldBe()`, `ShouldNotBeNull()`, `ShouldThrow()`, etc. +- Follow Arrange-Act-Assert pattern +- Use meaningful test method names: `Should_[Expected]_[Scenario]` +- Test both success and failure paths +- Clean up test data if not using transaction rollback + +### Integration Testing (Domain Layer) + +```csharp +public class ApplicationManager_Tests : GrantManagerDomainTestBase +{ + private readonly ApplicationManager _applicationManager; + private readonly IRepository _applicationRepository; + + public ApplicationManager_Tests() + { + _applicationManager = GetRequiredService(); + _applicationRepository = GetRequiredService>(); + } + + [Fact] + public async Task Should_Create_Application_When_Program_Is_Open() + { + // Arrange + var programId = GrantManagerTestData.OpenProgramId; + var applicantId = Guid.NewGuid(); + + // Act + var application = await _applicationManager.CreateAsync("Test", programId, applicantId); + + // Assert + application.ShouldNotBeNull(); + application.Status.ShouldBe(ApplicationStatus.Draft); + } + + [Fact] + public async Task Should_Throw_When_Program_Is_Closed() + { + // Arrange + var programId = GrantManagerTestData.ClosedProgramId; + var applicantId = Guid.NewGuid(); + + // Act & Assert + var exception = await Should.ThrowAsync(async () => + { + await _applicationManager.CreateAsync("Test", programId, applicantId); + }); + + exception.Message.ShouldContain("not currently accepting applications"); + } +} +``` + +## Database Migrations + +### Creating Migrations + +**Host Database (GrantManagerDbContext):** +```powershell +# Navigate to EntityFrameworkCore project +cd src/Unity.GrantManager.EntityFrameworkCore + +# Add migration +dotnet ef migrations add AddUserPreferences --context GrantManagerDbContext + +# Remove last migration if needed +dotnet ef migrations remove --context GrantManagerDbContext +``` + +**Tenant Database (GrantTenantDbContext):** +```powershell +# Navigate to EntityFrameworkCore project +cd src/Unity.GrantManager.EntityFrameworkCore + +# Add migration +dotnet ef migrations add AddApplicationAttachments --context GrantTenantDbContext + +# Remove last migration if needed +dotnet ef migrations remove --context GrantTenantDbContext +``` + +### Applying Migrations + +**Use DbMigrator project:** +```powershell +# Set DbMigrator as startup project in Visual Studio +# Press Ctrl+F5 to run without debugging +# DbMigrator will apply all pending migrations to both host and tenant databases +``` + +**Best Practices:** +- Use descriptive migration names (e.g., `AddApplicationStatus`, `UpdateAssessmentSchema`) +- Review generated migration code before applying +- Never modify migration files after they've been applied in production +- Always test migrations on a copy of production data +- Keep migrations small and focused on single changes +- Add seed data in `GrantManagerDataSeedContributor` class, not in migrations + +## Multi-Tenancy Guidelines + +### Tenant-Aware Entities + +```csharp +public class GrantApplication : FullAuditedAggregateRoot, IMultiTenant +{ + public Guid? TenantId { get; set; } // Automatically set by ABP + + // ... other properties +} +``` + +**Best Practices:** +- Implement `IMultiTenant` for all tenant-specific entities +- Store tenant data in `GrantTenantDbContext` (separate database) +- ABP automatically filters queries by current tenant +- Use `[IgnoreMultiTenancy]` attribute on DbContext to manage tenant data manually +- Never manually filter by `TenantId` in queries (ABP does this automatically) + +### Testing Multi-Tenancy + +```csharp +[Fact] +public async Task Should_Only_Get_Current_Tenant_Applications() +{ + // Arrange - switch to specific tenant + using (CurrentTenant.Change(GrantManagerTestData.TenantId)) + { + // Act + var applications = await _applicationRepository.GetListAsync(); + + // Assert - all applications belong to current tenant + applications.ShouldAllBe(x => x.TenantId == GrantManagerTestData.TenantId); + } +} +``` + +## Event-Driven Architecture + +### Publishing Domain Events + +**Local Events (same database transaction):** +```csharp +public class GrantApplication : FullAuditedAggregateRoot +{ + public void Submit() + { + Status = ApplicationStatus.Submitted; + + // Published when UnitOfWork commits + AddLocalEvent(new ApplicationSubmittedEvent + { + ApplicationId = Id, + Title = Title + }); + } +} +``` + +**Distributed Events (RabbitMQ, cross-module):** +```csharp +public class GrantApplication : FullAuditedAggregateRoot +{ + public void Approve() + { + Status = ApplicationStatus.Approved; + + // Published to RabbitMQ after UnitOfWork commits + AddDistributedEvent(new ApplicationApprovedEto // ETO = Event Transfer Object + { + ApplicationId = Id, + Title = Title, + ApplicantId = ApplicantId + }); + } +} +``` + +### Handling Events + +**Local Event Handler:** +```csharp +public class ApplicationSubmittedHandler : ILocalEventHandler, ITransientDependency +{ + private readonly ILogger _logger; + + public ApplicationSubmittedHandler(ILogger logger) + { + _logger = logger; + } + + public virtual async Task HandleEventAsync(ApplicationSubmittedEvent eventData) + { + _logger.LogInformation($"Application {eventData.ApplicationId} was submitted"); + + // Handle within same transaction + await Task.CompletedTask; + } +} +``` + +**Distributed Event Handler:** +```csharp +public class ApplicationApprovedHandler : IDistributedEventHandler, ITransientDependency +{ + private readonly IPaymentService _paymentService; + + public ApplicationApprovedHandler(IPaymentService paymentService) + { + _paymentService = paymentService; + } + + public virtual async Task HandleEventAsync(ApplicationApprovedEto eventData) + { + // Create payment request in different module/database + await _paymentService.CreatePaymentRequestAsync(eventData.ApplicationId); + } +} +``` + +## Front-End Development + +### Client-Side Package Management + +Unity Grant Manager uses **NPM** for client-side package management with ABP's resource mapping system. + +**Adding a new NPM package:** + +1. Add dependency to `package.json`: +```json +{ + "dependencies": { + "@abp/bootstrap": "^8.3.4", + "datatables.net-bs5": "^1.13.6", + "your-package-name": "^1.0.0" + } +} +``` + +2. Run npm install: +```bash +npm install +``` + +3. Configure resource mapping in `abp.resourcemapping.js`: +```javascript +module.exports = { + aliases: { + '@node_modules': './node_modules', + '@libs': './wwwroot/libs', + }, + mappings: { + '@node_modules/your-package/dist/': '@libs/your-package/', + }, +}; +``` + +4. Run ABP CLI to copy resources: +```bash +abp install-libs +``` + +5. Add to bundle contributor in `Unity.Theme.UX2`: +```csharp +// UnityThemeUX2GlobalScriptContributor.cs +public override void ConfigureBundle(BundleConfigurationContext context) +{ + context.Files.AddIfNotContains("/libs/your-package/your-script.js"); +} +``` + +**Best Practices:** +- Prefer `@abp/*` packages (e.g., `@abp/jquery`, `@abp/bootstrap`) for version consistency across modules +- Always use `AddIfNotContains()` to prevent duplicate script/style references +- Use `abp install-libs` after any `package.json` changes +- Map only necessary files (js, css, fonts) to reduce bundle size + +### JavaScript Conventions + +**File Organization:** +- Place page-specific JavaScript in `/Pages/[Feature]/[PageName].js` +- Place reusable utilities in `/wwwroot/scripts/` or theme modules +- Use IIFE pattern to avoid global scope pollution + +**Standard Pattern:** +```javascript +(function ($) { + var l = abp.localization.getResource('GrantManager'); + + var dataTable = $('#MyTable').DataTable(abp.libs.datatables.normalizeConfiguration({ + processing: true, + serverSide: true, + paging: true, + ajax: abp.libs.datatables.createAjax( + acme.grantManager.myFeature.myService.getList + ), + columnDefs: [ + { + title: l('Actions'), + rowAction: { + items: [ + { + text: l('Edit'), + action: function (data) { + editModal.open({ id: data.record.id }); + } + }, + { + text: l('Delete'), + confirmMessage: function (data) { + return l('DeleteConfirmationMessage', data.record.name); + }, + action: function (data) { + acme.grantManager.myFeature.myService + .delete(data.record.id) + .then(function () { + abp.notify.success(l('SuccessfullyDeleted')); + dataTable.ajax.reload(); + }); + } + } + ] + } + }, + { + title: l('Name'), + data: 'name' + }, + { + title: l('CreationTime'), + data: 'creationTime', + dataFormat: 'datetime' + } + ] + })); + + var createModal = new abp.ModalManager({ + viewUrl: abp.appPath + 'GrantManager/MyFeature/CreateModal', + modalClass: 'myFeatureCreate' + }); + + createModal.onResult(function () { + dataTable.ajax.reload(); + }); + + $('#NewRecordButton').click(function (e) { + e.preventDefault(); + createModal.open(); + }); + +})(jQuery); +``` + +### ABP Dynamic JavaScript API Client Proxies + +ABP automatically generates JavaScript client proxies for your application services. + +**How it works:** +- Application services inheriting from `ApplicationService` are auto-exposed as HTTP APIs +- `/Abp/ServiceProxyScript` endpoint generates JavaScript proxy functions +- Proxies follow namespace convention: `[moduleName].[namespace].[serviceName].[methodName]` + +**Example Application Service:** +```csharp +public class ApplicationAppService : ApplicationService, IApplicationAppService +{ + public virtual async Task> GetListAsync(GetApplicationListDto input) + { + // Implementation + } + + public virtual async Task CreateAsync(CreateApplicationDto input) + { + // Implementation + } +} +``` + +**Generated JavaScript Proxy Usage:** +```javascript +// GET list +acme.grantManager.applications.application.getList({ + maxResultCount: 10, + skipCount: 0, + filter: 'search term' +}).then(function(result) { + console.log(result.items); + console.log(result.totalCount); +}); + +// POST create +acme.grantManager.applications.application.create({ + title: 'My Application', + description: 'Description' +}).then(function(result) { + abp.notify.success('Successfully created!'); +}); + +// DELETE +acme.grantManager.applications.application + .delete('3fa85f64-5717-4562-b3fc-2c963f66afa6') + .then(function() { + abp.notify.success('Successfully deleted!'); + }); + +// PUT update +acme.grantManager.applications.application + .update('3fa85f64-5717-4562-b3fc-2c963f66afa6', { + title: 'Updated Title' + }) + .then(function(result) { + abp.notify.success('Successfully updated!'); + }); +``` + +**AJAX Options:** +You can override AJAX options by passing an additional parameter: +```javascript +acme.grantManager.applications.application + .delete(id, { + type: 'POST', // Override HTTP method + dataType: 'json', + success: function() { + console.log('Custom success handler'); + } + }); +``` + +**Benefits:** +- Type-safe API calls (parameters match C# method signatures) +- Automatic error handling via `abp.ajax` +- No manual AJAX configuration needed +- Returns jQuery Deferred objects (`.then()`, `.catch()`, `.always()`) + +### DataTables.net Integration + +Unity Grant Manager uses DataTables.net 1.x with Bootstrap 5 styling. + +**Basic DataTable Setup:** +```javascript +var dataTable = $('#MyTable').DataTable(abp.libs.datatables.normalizeConfiguration({ + processing: true, + serverSide: true, + paging: true, + searching: true, + autoWidth: false, + scrollCollapse: true, + order: [[1, "asc"]], + ajax: abp.libs.datatables.createAjax( + acme.grantManager.myService.getList, + function () { + return { + filter: $('#SearchInput').val(), + status: $('#StatusFilter').val() + }; + } + ), + columnDefs: [ + { + title: l('Actions'), + rowAction: { + items: [ + { + text: l('Edit'), + visible: abp.auth.isGranted('GrantManager.Edit'), + action: function (data) { + editModal.open({ id: data.record.id }); + } + } + ] + } + }, + { + title: l('Name'), + data: 'name', + orderable: true + }, + { + title: l('Status'), + data: 'status', + render: function (data) { + return l('Enum:ApplicationStatus.' + data); + } + } + ] +})); +``` + +**Advanced DataTable Features:** + +1. **Custom Filters:** +```javascript +$('#SearchInput').on('input', function () { + dataTable.ajax.reload(); +}); + +$('#StatusFilter').change(function () { + dataTable.ajax.reload(); +}); +``` + +2. **Row Selection:** +```javascript +var dataTable = $('#MyTable').DataTable({ + // ... other config + select: { + style: 'multi' + } +}); + +$('#BulkDeleteButton').click(function () { + var selectedRows = dataTable.rows({ selected: true }).data().toArray(); + // Process selected rows +}); +``` + +3. **Export Buttons:** +```javascript +var dataTable = $('#MyTable').DataTable({ + // ... other config + buttons: [ + { + extend: 'excel', + text: l('ExportToExcel'), + exportOptions: { + columns: ':visible' + } + }, + { + extend: 'csv', + text: l('ExportToCsv') + } + ] +}); +``` + +4. **Column Visibility:** +```javascript +var dataTable = $('#MyTable').DataTable({ + // ... other config + buttons: [ + { + extend: 'colvis', + text: l('ColumnVisibility') + } + ] +}); +``` + +**DataTable Best Practices:** +- Always use `abp.libs.datatables.normalizeConfiguration()` for ABP integration +- Use `abp.libs.datatables.createAjax()` for automatic server-side pagination +- Leverage `rowAction` for action buttons with permission checks +- Use `dataFormat` property for date/datetime/boolean formatting +- Call `dataTable.ajax.reload()` after CRUD operations + +### ABP Modal Manager + +Use `abp.ModalManager` for consistent modal dialogs. + +**Basic Modal Usage:** +```javascript +var createModal = new abp.ModalManager({ + viewUrl: abp.appPath + 'GrantManager/Applications/CreateModal', + scriptUrl: abp.appPath + 'Pages/GrantManager/Applications/CreateModal.js', + modalClass: 'applicationCreate' +}); + +createModal.onOpen(function () { + console.log('Modal opened'); +}); + +createModal.onResult(function (result) { + abp.notify.success(l('SavedSuccessfully')); + dataTable.ajax.reload(); +}); + +createModal.onClose(function () { + console.log('Modal closed'); +}); + +$('#NewApplicationButton').click(function (e) { + e.preventDefault(); + createModal.open(); +}); +``` + +**Modal Script Pattern (CreateModal.js):** +```javascript +abp.modals.applicationCreate = function () { + var l = abp.localization.getResource('GrantManager'); + var _$form = null; + var _$modal = null; + + this.init = function (modalManager, args) { + _$modal = modalManager.getModal(); + _$form = modalManager.getForm(); + + // Initialize form validation + _$form.data('validator').settings.ignore = ''; + + // Custom form logic + $('#ProgramSelect').change(function () { + var programId = $(this).val(); + // Load dynamic fields based on program + }); + }; +}; +``` + +**Submitting Modal Forms:** +```csharp +// In Razor Page (CreateModal.cshtml.cs) +public async Task OnPostAsync() +{ + await _applicationAppService.CreateAsync(Application); + return NoContent(); // Return NoContent to close modal and trigger onResult +} +``` + +### ABP JavaScript Utilities + +**Localization:** +```javascript +var l = abp.localization.getResource('GrantManager'); +var message = l('WelcomeMessage'); +var formatted = l('GreetingMessage', userName); // With parameters +``` + +**Notifications:** +```javascript +abp.notify.success('Operation completed successfully'); +abp.notify.info('Information message'); +abp.notify.warn('Warning message'); +abp.notify.error('An error occurred'); +``` + +**Confirmation Dialogs:** +```javascript +abp.message.confirm( + 'Are you sure you want to delete this item?', + 'Confirm Delete' +).then(function (confirmed) { + if (confirmed) { + // Perform delete + } +}); +``` + +**Busy Indicator:** +```javascript +abp.ui.setBusy('#MyForm'); + +// ... perform operation + +abp.ui.clearBusy('#MyForm'); +``` + +**AJAX Calls (when proxy not available):** +```javascript +abp.ajax({ + url: '/api/app/my-service/custom-endpoint', + type: 'POST', + data: JSON.stringify({ key: 'value' }), + contentType: 'application/json' +}).then(function (result) { + console.log(result); +}); +``` + +**Authorization:** +```javascript +if (abp.auth.isGranted('GrantManager.Applications.Edit')) { + // Show edit button +} +``` + +**Settings:** +```javascript +var settingValue = abp.setting.get('SettingName'); +var intValue = abp.setting.getInt('NumericSetting'); +var boolValue = abp.setting.getBoolean('BooleanSetting'); +``` + +### DOM Event Handlers + +ABP provides automatic initialization for common UI components via DOM event handlers. + +**Auto-Initialized Components:** +- **Tooltips:** `data-bs-toggle="tooltip"` +- **Popovers:** `data-bs-toggle="popover"` +- **Datepickers:** `input.datepicker` or `input[type=date]` +- **AJAX Forms:** `data-ajaxForm="true"` +- **Autocomplete Selects:** `class="auto-complete-select"` + +**Example - Autocomplete Select:** +```html + +``` + +**Example - Confirmation Dialog:** +```html + + + + +``` + +**Example - AJAX Form:** +```html +
+ +
+``` + +### JavaScript Best Practices + +**DO:** +- ✅ Use ABP dynamic proxies instead of manual `$.ajax` calls +- ✅ Wrap code in IIFE to avoid global scope pollution: `(function ($) { ... })(jQuery);` +- ✅ Use `abp.localization` for all user-facing text +- ✅ Use `abp.notify` and `abp.message` for user feedback +- ✅ Use `abp.auth.isGranted()` for permission checks in UI +- ✅ Use `abp.ModalManager` for modal dialogs +- ✅ Use `abp.libs.datatables` helpers for DataTables integration +- ✅ Call `dataTable.ajax.reload()` after CRUD operations +- ✅ Use `abp.ui.setBusy()` for long-running operations + +**DON'T:** +- ❌ Don't use global variables (use module pattern or IIFE) +- ❌ Don't hardcode text strings (use localization) +- ❌ Don't use `alert()` or `confirm()` (use `abp.notify` and `abp.message`) +- ❌ Don't manually construct API URLs (use dynamic proxies) +- ❌ Don't forget to handle errors in promise chains +- ❌ Don't bypass ABP's modal manager for modal dialogs +- ❌ Don't forget to check permissions before showing UI elements + +## Common Pitfalls & Solutions + +### ❌ Don't: Expose entities directly from application services +```csharp +public async Task GetAsync(Guid id) // ❌ Wrong +{ + return await _applicationRepository.GetAsync(id); +} +``` + +### ✅ Do: Return DTOs +```csharp +public async Task GetAsync(Guid id) // ✅ Correct +{ + var application = await _applicationRepository.GetAsync(id); + return ObjectMapper.Map(application); +} +``` + +### ❌ Don't: Put business logic in application services +```csharp +public async Task CreateAsync(CreateApplicationDto input) // ❌ Wrong +{ + var application = new GrantApplication(GuidGenerator.Create(), input.Title); + + // Complex validation logic here (should be in domain service) + var program = await _programRepository.GetAsync(input.ProgramId); + if (!program.IsAcceptingApplications()) + throw new BusinessException("..."); + + await _applicationRepository.InsertAsync(application); + return ObjectMapper.Map(application); +} +``` + +### ✅ Do: Use domain services for business logic +```csharp +public async Task CreateAsync(CreateApplicationDto input) // ✅ Correct +{ + // Delegate to domain service + var application = await _applicationManager.CreateAsync( + input.Title, + input.ProgramId, + CurrentUser.GetId()); + + await _applicationRepository.InsertAsync(application); + return ObjectMapper.Map(application); +} +``` + +### ❌ Don't: Use non-virtual methods +```csharp +public async Task GetAsync(Guid id) // ❌ Can't be overridden +{ + // ... +} +``` + +### ✅ Do: Make methods virtual for extensibility +```csharp +public virtual async Task GetAsync(Guid id) // ✅ Can be overridden +{ + // ... +} +``` + +### ❌ Don't: Manually filter by TenantId +```csharp +var applications = await _applicationRepository // ❌ Wrong + .GetListAsync(x => x.TenantId == CurrentTenant.Id); +``` + +### ✅ Do: Let ABP handle tenant filtering automatically +```csharp +var applications = await _applicationRepository.GetListAsync(); // ✅ Correct +// ABP automatically filters by current tenant +``` + +## Code Review Checklist + +Before submitting a pull request, ensure: + +- [ ] Code follows ABP Framework conventions and patterns +- [ ] All public methods are `virtual` +- [ ] Nullable reference types are handled correctly +- [ ] DTOs are used for application service inputs/outputs (not entities) +- [ ] Domain logic is in domain layer (entities/domain services) +- [ ] Application services orchestrate use cases (thin layer) +- [ ] Repository interfaces are defined only when custom queries needed +- [ ] Entity configurations use fluent API in extension methods +- [ ] Multi-tenant entities implement `IMultiTenant` +- [ ] Tests are written for new functionality (xUnit + Shouldly) +- [ ] Database migrations are created for schema changes +- [ ] Authorization attributes (`[Authorize]`) are applied +- [ ] Logging is added for important operations +- [ ] Exception handling uses `BusinessException` for domain errors +- [ ] Async/await is used consistently +- [ ] No nullable reference type warnings + +## Resources + +- [ABP Framework Documentation](https://docs.abp.io/en/abp/latest) +- [ABP Best Practices](https://docs.abp.io/en/abp/latest/Best-Practices) +- [Implementing Domain Driven Design (e-book)](https://abp.io/books/implementing-domain-driven-design) +- [ABP Community](https://community.abp.io/) +- [ABP GitHub Repository](https://github.com/abpframework/abp) +- [ARCHITECTURE.md](./ARCHITECTURE.md) - System architecture overview +- [PRODUCT.md](./PRODUCT.md) - Product vision and features diff --git a/applications/Unity.GrantManager/PRODUCT.md b/applications/Unity.GrantManager/PRODUCT.md new file mode 100644 index 000000000..e1ff34461 --- /dev/null +++ b/applications/Unity.GrantManager/PRODUCT.md @@ -0,0 +1,101 @@ +# Unity Grant Manager - Product Vision + +## Overview + +Unity Grant Manager is a comprehensive grant management platform designed for the Government of British Columbia to streamline and automate the entire grants lifecycle. The application enables government staff to manage grant programs, review applications, conduct assessments, and process payments, while providing applicants with a user-friendly portal to submit and track their grant applications. + +## Product Goals + +1. **Streamline Grant Administration**: Reduce administrative overhead by automating grant program management, application intake, assessment workflows, and payment processing. + +2. **Enhance Transparency**: Provide applicants and stakeholders with real-time visibility into application status, assessment progress, and payment tracking. + +3. **Ensure Compliance**: Maintain audit trails, enforce business rules, and integrate with government systems (CHES email service, CAS payment system) to ensure regulatory compliance. + +4. **Enable Flexibility**: Support dynamic form creation for diverse grant programs with varying requirements through the Unity.Flex module. + +5. **Support Multi-Tenancy**: Enable multiple government organizations or programs to operate independently within a shared platform instance. + +## Key Features + +### Grant Program Management +- Configure and manage multiple grant programs with unique requirements, eligibility criteria, and assessment workflows +- Define program intake periods, budget allocations, and reporting requirements +- Track program performance metrics and funding distribution + +### Applicant Portal +- Self-service application submission with dynamic forms tailored to each grant program +- Document upload and management for supporting materials +- Real-time application status tracking and notifications +- Application editing and resubmission capabilities during intake periods + +### Application Assessment & Scoring +- Configurable assessment workflows with multiple review stages +- Collaborative review process with scoring rubrics and criteria +- Assignment of applications to assessors and review teams +- Consolidated scoring and recommendation reporting +- Comment threads and internal discussions on applications + +### Payment Processing +- Integration with CAS (Common Accounting System) for government payment processing +- Payment milestone tracking and approval workflows +- Payment history and reconciliation reporting +- Support for installment-based and milestone-based payment schedules + +### Notifications & Communications +- Automated email notifications via CHES (Common Hosted Email Service) +- Configurable notification templates for application status changes +- Event-driven notifications for assessments, payments, and program updates +- Communication history tracking + +### Reporting & Analytics +- Customizable reports on application volumes, assessment outcomes, and payment distributions +- Program performance dashboards and metrics +- Export capabilities for external analysis and compliance reporting +- Integration with Unity.Reporting module for advanced reporting features + +### User & Role Management +- Role-based access control for staff, assessors, and applicants +- Integration with Keycloak for authentication and single sign-on +- Organization and team-based permission structures +- Audit logging for all user actions + +## Target Users + +### Government Program Staff +- Grant program administrators who configure programs and manage intake periods +- Grant officers who oversee application processing and assessment coordination +- Finance staff who manage payment processing and budget tracking + +### Assessors & Reviewers +- Internal and external subject matter experts who evaluate applications +- Review panel members who participate in scoring and recommendation processes + +### Applicants +- Individuals, organizations, or businesses applying for government grants +- Grant recipients tracking payment schedules and reporting requirements + +## Integration Points + +### Unity Platform Modules +- **Unity.Flex**: Dynamic form definitions for customizable application forms +- **Unity.Notifications**: Email notifications via CHES integration +- **Unity.Payments**: Payment processing through CAS integration +- **Unity.Reporting**: Advanced reporting and data visualization +- **Unity.Identity.Web**: User authentication and authorization +- **Unity.TenantManagement**: Multi-tenant configuration and isolation +- **Unity.Theme.UX2**: Consistent user interface and experience + +### External Systems +- **CHES (Common Hosted Email Service)**: Government email notification service +- **CAS (Common Accounting System)**: Government payment processing system +- **Keycloak**: Enterprise identity and access management +- **AWS S3**: Document storage and blob management + +## Success Criteria + +1. **Efficiency**: Reduce grant processing time by 40% through automation and streamlined workflows +2. **User Satisfaction**: Achieve 85%+ satisfaction ratings from both applicants and staff users +3. **Transparency**: Provide real-time status updates for 100% of applications +4. **Compliance**: Maintain complete audit trails and pass all security/privacy audits +5. **Scalability**: Support multiple concurrent grant programs with thousands of applications From cacabc08863428013d8ce71329ade53a298e9245 Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:10:45 -0800 Subject: [PATCH 2/3] AB#32037 - Update applications/Unity.GrantManager/.github/copilot-instructions.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- applications/Unity.GrantManager/.github/copilot-instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/Unity.GrantManager/.github/copilot-instructions.md b/applications/Unity.GrantManager/.github/copilot-instructions.md index e66b15136..c15106189 100644 --- a/applications/Unity.GrantManager/.github/copilot-instructions.md +++ b/applications/Unity.GrantManager/.github/copilot-instructions.md @@ -335,7 +335,7 @@ acme.grantManager.applications.application ### DataTables.net Integration -**Unity Grant Manager uses DataTables.net 1.x** (not 2.x due to ABP compatibility). +**Unity Grant Manager uses DataTables.net 2.x** with the Bootstrap 5 integration package (`datatables.net-bs5`). Ensure generated examples and APIs target DataTables 2.x. **Standard DataTable pattern:** ```javascript From 4e3792a21eec024c1d5eecbf08a5b8e2f9e23cc0 Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:11:16 -0800 Subject: [PATCH 3/3] AB#32037 - Update applications/Unity.GrantManager/PRODUCT.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- applications/Unity.GrantManager/PRODUCT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/Unity.GrantManager/PRODUCT.md b/applications/Unity.GrantManager/PRODUCT.md index e1ff34461..bbdd7f8b4 100644 --- a/applications/Unity.GrantManager/PRODUCT.md +++ b/applications/Unity.GrantManager/PRODUCT.md @@ -90,7 +90,7 @@ Unity Grant Manager is a comprehensive grant management platform designed for th - **CHES (Common Hosted Email Service)**: Government email notification service - **CAS (Common Accounting System)**: Government payment processing system - **Keycloak**: Enterprise identity and access management -- **AWS S3**: Document storage and blob management +- **COMS (Common Object Management Service)**: Document storage and blob management via S3-compatible APIs ## Success Criteria