diff --git a/.github/workflows/cypress-dev.yml b/.github/workflows/cypress-dev.yml index 9aaf565207..b010ab5519 100644 --- a/.github/workflows/cypress-dev.yml +++ b/.github/workflows/cypress-dev.yml @@ -16,7 +16,7 @@ on: workflow_dispatch: inputs: base_url: - description: "Override baseUrl (e.g. https://feature-branch.apps.silver.devops.gov.bc.ca/)" + description: "Override baseUrl (e.g. https://feature-branch.apps.gold.devops.gov.bc.ca/)" required: false type: string diff --git a/.github/workflows/cypress-e2e-runner.yml b/.github/workflows/cypress-e2e-runner.yml index 63eab4dc70..6dfab257b4 100644 --- a/.github/workflows/cypress-e2e-runner.yml +++ b/.github/workflows/cypress-e2e-runner.yml @@ -2,31 +2,20 @@ name: Cypress E2E (runner) # Shared job called by cypress-dev/test/uat/prod.yml. # -# Unlike Grants, this doesn't run Cypress on the GitHub-hosted runner directly — -# the Unity Grant Manager Route is IP-allowlisted to the BC Gov network, which -# GitHub-hosted runners can't reach. Instead this job launches an OpenShift Job +# Unity Grant Manager Route is IP-allowlisted to the BC Gov network, which +# GitHub-hosted runners can't reach. This GitHub job launches an OpenShift Job # (from the unity-cypress-job Template in ce395f-tools) that runs the existing # Unity.AutoUI Cypress suite from inside the cluster, waits for it to finish, # and pulls the logs/screenshots back into this Actions run. # -# This deliberately targets ce395f-tools on the Gold cluster, not d18498-tools -# on Silver, even though the app environments (dev/test/uat/prod) still run on -# Silver during the cluster migration — the Job's own baseUrl (via the -# base_url input, or each cypress-config-*.json's default) is what it tests -# against, independent of which cluster runs the Job itself. Building the CI -# plumbing against the migration target now avoids redoing it later. +# The Job pulls credentials from the unity-cypress-config Secret in ce395f-tools, +# synced from Vault (GH_UGM_CYPRESS_CONFIG) via External Secrets. # -# Test credentials are NOT GitHub secrets — the Job pulls them from the -# unity-cypress-config Secret in ce395f-tools, synced from Vault -# (GH_UGM_CYPRESS_CONFIG) via External Secrets. -# -# OpenShift auth reuses the pipeline ServiceAccount (ce395f-tools), which -# already holds ClusterRole/edit there — covers everything this job needs -# (templates, processedtemplates, jobs, pods/log, pods/exec), no additional -# RBAC required. Its token is CYPRESS_OPENSHIFT_TOKEN and its cluster is -# CYPRESS_OPENSHIFT_CLUSTER — both repo-level (not tied to a GitHub -# Environment, and deliberately separate from the shared OPENSHIFT_CLUSTER -# var docker-build-*.yml uses, since that one still points at Silver). +# OpenShift auth reuses the pipeline ServiceAccount (ce395f-tools), which already +# holds ClusterRole/edit this covers everything the job needs (templates, +# processedtemplates, jobs, pods/log, pods/exec), no additional RBAC required. +# +# Token is CYPRESS_OPENSHIFT_TOKEN and its cluster is CYPRESS_OPENSHIFT_CLUSTER. on: workflow_call: @@ -91,22 +80,24 @@ jobs: - name: Wait for Cypress Job to finish id: wait run: | - # Don't use `oc wait --for=condition=complete/failed` here: a freshly - # created Job has no .status.conditions entries yet, and - # --for=condition=X (without an explicit =true) is satisfied the - # instant the condition is merely absent rather than false — so it - # can report "condition met" seconds after creation, long before the - # Job has actually run. Poll the numeric succeeded/failed fields - # instead, which are only set once a pod genuinely finishes. + # Wait for the Job to finish by polling the logs for a sentinel line + # the cypress container (see cypress-job-template.yaml in + # tenant-gitops-ce395f) prints a "CYPRESS_DONE exit_code=N" + # sentinel line when the test run finishes and, on failure, + # sleeps for 600s before actually exiting. JOB_REF="${{ steps.launch.outputs.job_ref }}" + JOB_NAME="${JOB_REF#job.batch/}" DEADLINE=$(( $(date +%s) + JOB_TIMEOUT_SECONDS )) - SUCCEEDED=0 - FAILED=0 + POD="" + EXIT_CODE="" + while true; do - SUCCEEDED=$(oc get "$JOB_REF" -n $TOOLS_NAMESPACE -o jsonpath='{.status.succeeded}') - FAILED=$(oc get "$JOB_REF" -n $TOOLS_NAMESPACE -o jsonpath='{.status.failed}') - if [ "${SUCCEEDED:-0}" -ge 1 ] 2>/dev/null || [ "${FAILED:-0}" -ge 1 ] 2>/dev/null; then - break + POD=$(oc get pods -n $TOOLS_NAMESPACE -l job-name="$JOB_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true) + if [ -n "$POD" ]; then + EXIT_CODE=$(oc logs "$POD" -n $TOOLS_NAMESPACE -c cypress 2>/dev/null | grep -o 'CYPRESS_DONE exit_code=[0-9]*' | tail -1 | cut -d= -f2 || true) + if [ -n "$EXIT_CODE" ]; then + break + fi fi if [ "$(date +%s)" -ge "$DEADLINE" ]; then echo "Timed out after ${JOB_TIMEOUT_SECONDS}s waiting for $JOB_REF" @@ -115,7 +106,12 @@ jobs: sleep 10 done - echo "succeeded=${SUCCEEDED:-0}" >> "$GITHUB_OUTPUT" + echo "pod=${POD:-}" >> "$GITHUB_OUTPUT" + if [ "${EXIT_CODE:-1}" = "0" ]; then + echo "succeeded=1" >> "$GITHUB_OUTPUT" + else + echo "succeeded=0" >> "$GITHUB_OUTPUT" + fi - name: Show Cypress logs if: always() @@ -126,15 +122,13 @@ jobs: - name: Collect screenshots on failure if: steps.wait.outputs.succeeded != '1' run: | - JOB_NAME="${{ steps.launch.outputs.job_ref }}" - JOB_NAME="${JOB_NAME#job.batch/}" - POD=$(oc get pods -n $TOOLS_NAMESPACE -l job-name="$JOB_NAME" -o jsonpath='{.items[0].metadata.name}') + POD="${{ steps.wait.outputs.pod }}" mkdir -p cypress-screenshots oc cp "$TOOLS_NAMESPACE/$POD:/workspace/applications/Unity.AutoUI/cypress/screenshots" ./cypress-screenshots -c cypress || true - name: Upload screenshots on failure if: steps.wait.outputs.succeeded != '1' - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: cypress-screenshots-${{ inputs.env_name }}-${{ github.run_number }} path: cypress-screenshots diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..96ae0cbbf4 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,63 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +> This is **Unity Portal**, a grant management system for the Province of British Columbia. It is NOT the Unity game engine — do not suggest UnityEngine APIs. + +## Repository Layout + +`applications/Unity.GrantManager/` is where almost all work happens — a self-contained ABP Framework solution with its own extensive AI-agent instructions. `applications/Unity.AutoUI/` holds Cypress E2E tests; `applications/Unity.Tools/`, `database/`, and `documentation/` round out the rest (see root `README.md` for the full layout). + +**Read these before making non-trivial changes in `applications/Unity.GrantManager/`** — note this solution has its own `.github/`, separate from the root `.github/` above it: + +- `applications/Unity.GrantManager/.github/copilot-instructions.md` — authoritative project overview, layering, and conventions (trust this first) +- `applications/Unity.GrantManager/.github/instructions/*.instructions.md` — path-scoped rules for C#, EF Core, JavaScript, security, testing +- `applications/Unity.GrantManager/.github/skills/*/SKILL.md` — deep-dive patterns: DDD, application layer, EF Core, testing, ABP CLI, module structure +- `applications/Unity.GrantManager/.github/agents/*.agent.md` — planning agents for features, DDD modeling, EF migrations, permissions/localization audits, test strategy, PR readiness + +Where those files and this one overlap, prefer the more specific ones under `applications/Unity.GrantManager/.github/`. + +## Build & Test + +All commands run from `applications/Unity.GrantManager/`: + +```bash +dotnet restore Unity.GrantManager.sln +dotnet build Unity.GrantManager.sln --no-restore # ~3 min, 81 projects +dotnet test Unity.GrantManager.sln --no-build # ~470 tests, ~1-2 min + +# Single test project +dotnet test test/Unity.GrantManager.Application.Tests/ --no-build +``` + +- No PostgreSQL setup needed for tests — SQLite in-memory (most projects) or `EFCore.InMemory` (`Unity.GrantManager.Web.Tests`). +- `Unity.GrantManager.Web/Pages/Dashboard/Index.cshtml.cs` has one expected `CS8604` warning — don't fix it unless asked. +- `Directory.Build.props` / `common.props` (repo-wide MSBuild props) already suppress `NU1701`, `MSB3277`, `CS1591` — don't re-suppress per-project. + +### Local dev environment + +`docker-compose.yml` + `.env.example` in `applications/Unity.GrantManager/` spin up the web app, PostgreSQL, a DB migrator, and Redis. Copy `.env.example` to `.env` and fill in secrets before running `docker compose up`. + +### EF Core migrations + +There are **two separate database contexts** — always specify which one: + +```bash +cd applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore + +dotnet ef migrations add --context GrantManagerDbContext --output-dir Migrations/HostMigrations # host/shared tables +dotnet ef migrations add --context GrantTenantDbContext --output-dir Migrations/TenantMigrations # per-tenant data +``` + +## Architecture + +ABP Framework modular monolith, DDD-layered — see `applications/Unity.GrantManager/.github/copilot-instructions.md` and `applications/Unity.GrantManager/.github/skills/unity-module-structure/SKILL.md` for the full module list and dependency-direction diagram. + +Business rules belong in Domain entities/managers, not controllers or app services. Don't call another app service within the same module — push shared logic into a domain service. + +### Key conventions + +C#, EF Core, JavaScript, security, and testing conventions are detailed in `.claude/rules/*.md` (loaded automatically for matching files) and `applications/Unity.GrantManager/.github/instructions/*.instructions.md`. + +- **Branching**: `dev` → `test` → `main` promotion. Feature branches `feature/*`, fixes `bugfix/*`, urgent `hotfix/*`. PRs to `dev` come from `feature/*`/`bugfix/*`/`hotfix/*`; PRs to `main` only from `test` or `hotfix/*`. +- **Commit messages**: prefix with `[AB#]` extracted from the branch name (e.g. `feature/AB#32037-...` → `AB#32037`), then a short description. diff --git a/applications/Unity.AutoUI/CypressTestLauncher_README.md b/applications/Unity.AutoUI/CypressTestLauncher_README.md index ad3b336a99..4daaca93bc 100644 --- a/applications/Unity.AutoUI/CypressTestLauncher_README.md +++ b/applications/Unity.AutoUI/CypressTestLauncher_README.md @@ -21,7 +21,7 @@ But on your local system you still need to replace the tokens. These json files cypress.dev.env.json { - "webapp.url": "https://dev-unity.apps.silver.devops.gov.bc.ca/", + "webapp.url": "https://dev-unity.apps.gold.devops.gov.bc.ca/", "reporting.url": "{{reporting.url}}", "test1username": REQUIRED "test1password": REQUIRED @@ -35,7 +35,7 @@ cypress.dev.env.json cypress.dev2.env.json { - "webapp.url": "https://dev2-unity.apps.silver.devops.gov.bc.ca/",, + "webapp.url": "https://dev2-unity.apps.gold.devops.gov.bc.ca/",, "reporting.url": "{{reporting.url}}", "test1username": REQUIRED "test1password": REQUIRED @@ -49,7 +49,7 @@ cypress.dev2.env.json cypress.test.env.json { - "webapp.url": "https://test-unity.apps.silver.devops.gov.bc.ca/", + "webapp.url": "https://test-unity.apps.gold.devops.gov.bc.ca/", "reporting.url": "{{reporting.url}}", "test1username": REQUIRED "test1password": REQUIRED @@ -63,7 +63,7 @@ cypress.test.env.json cypress.uat.env.json { - "webapp.url": "https://uat-unity.apps.silver.devops.gov.bc.ca/", + "webapp.url": "https://uat-unity.apps.gold.devops.gov.bc.ca/", "reporting.url": "{{reporting.url}}", "test1username": REQUIRED "test1password": REQUIRED @@ -77,7 +77,7 @@ cypress.uat.env.json cypress.prod.env.json { - "webapp.url": "https://prod-unity.apps.silver.devops.gov.bc.ca/", + "webapp.url": "https://prod-unity.apps.gold.devops.gov.bc.ca/", "reporting.url": "{{reporting.url}}", "test1username": REQUIRED "test1password": REQUIRED diff --git a/applications/Unity.AutoUI/cypress.config.ts b/applications/Unity.AutoUI/cypress.config.ts index b1e381022d..53936349c8 100644 --- a/applications/Unity.AutoUI/cypress.config.ts +++ b/applications/Unity.AutoUI/cypress.config.ts @@ -65,7 +65,7 @@ export default defineConfig({ "cypress/scripts/**/*.cy.{js,jsx,ts,tsx}", "cypress/regression/**/*.cy.{js,jsx,ts,tsx}", ], - baseUrl: "https://dev-unity.apps.silver.devops.gov.bc.ca/", + baseUrl: "https://dev-unity.apps.gold.devops.gov.bc.ca/", defaultCommandTimeout: 20000, // Time, in milliseconds, to wait until most DOM based commands are considered timed out. viewportWidth: 1440, // Default width in pixels. viewportHeight: 900, // Default height in pixels. diff --git a/applications/Unity.AutoUI/cypress/config/dev.json.example b/applications/Unity.AutoUI/cypress/config/dev.json.example index 184c864869..b34148f6e7 100644 --- a/applications/Unity.AutoUI/cypress/config/dev.json.example +++ b/applications/Unity.AutoUI/cypress/config/dev.json.example @@ -1,5 +1,5 @@ { - "webapp.url": "https://dev-unity.apps.silver.devops.gov.bc.ca/", + "webapp.url": "https://dev-unity.apps.gold.devops.gov.bc.ca/", "environment": "DEV", "test1username": "", "test1password": "", diff --git a/applications/Unity.AutoUI/cypress/config/dev2.json.example b/applications/Unity.AutoUI/cypress/config/dev2.json.example index cf9669d754..2ac0b515be 100644 --- a/applications/Unity.AutoUI/cypress/config/dev2.json.example +++ b/applications/Unity.AutoUI/cypress/config/dev2.json.example @@ -1,5 +1,5 @@ { - "webapp.url": "https://dev2-unity.apps.silver.devops.gov.bc.ca/", + "webapp.url": "https://dev2-unity.apps.gold.devops.gov.bc.ca/", "environment": "DEV2", "test1username": "", "test1password": "", diff --git a/applications/Unity.AutoUI/cypress/config/prod.json.example b/applications/Unity.AutoUI/cypress/config/prod.json.example index 8daeac885c..d724364bb7 100644 --- a/applications/Unity.AutoUI/cypress/config/prod.json.example +++ b/applications/Unity.AutoUI/cypress/config/prod.json.example @@ -1,5 +1,5 @@ { - "webapp.url": "https://prod-unity.apps.silver.devops.gov.bc.ca/", + "webapp.url": "https://prod-unity.apps.gold.devops.gov.bc.ca/", "environment": "PROD", "test1username": "", "test1password": "", diff --git a/applications/Unity.AutoUI/cypress/config/test.json.example b/applications/Unity.AutoUI/cypress/config/test.json.example index 0d72840645..0b012b37d0 100644 --- a/applications/Unity.AutoUI/cypress/config/test.json.example +++ b/applications/Unity.AutoUI/cypress/config/test.json.example @@ -1,5 +1,5 @@ { - "webapp.url": "https://test-unity.apps.silver.devops.gov.bc.ca/", + "webapp.url": "https://test-unity.apps.gold.devops.gov.bc.ca/", "environment": "TEST", "test1username": "", "test1password": "", diff --git a/applications/Unity.AutoUI/cypress/config/uat.json.example b/applications/Unity.AutoUI/cypress/config/uat.json.example index 3f82b0d2b3..2ab1fe261f 100644 --- a/applications/Unity.AutoUI/cypress/config/uat.json.example +++ b/applications/Unity.AutoUI/cypress/config/uat.json.example @@ -1,5 +1,5 @@ { - "webapp.url": "https://uat-unity.apps.silver.devops.gov.bc.ca/", + "webapp.url": "https://uat-unity.apps.gold.devops.gov.bc.ca/", "environment": "UAT", "test1username": "", "test1password": "", diff --git a/applications/Unity.AutoUI/cypress/fixtures/metabase.json b/applications/Unity.AutoUI/cypress/fixtures/metabase.json index c161b874fa..051308782f 100644 --- a/applications/Unity.AutoUI/cypress/fixtures/metabase.json +++ b/applications/Unity.AutoUI/cypress/fixtures/metabase.json @@ -2,23 +2,23 @@ "metabaseDetails": [ { "unityEnv": "DEV", - "baseURL": "https://dev-unity-reporting.apps.silver.devops.gov.bc.ca/" + "baseURL": "https://dev-unity-reporting.apps.gold.devops.gov.bc.ca/" }, { "unityEnv": "DEV2", - "baseURL": "https://dev-unity-reporting.apps.silver.devops.gov.bc.ca/" + "baseURL": "https://dev-unity-reporting.apps.gold.devops.gov.bc.ca/" }, { "unityEnv": "TEST", - "baseURL": "https://test-unity-reporting.apps.silver.devops.gov.bc.ca/" + "baseURL": "https://test-unity-reporting.apps.gold.devops.gov.bc.ca/" }, { "unityEnv": "UAT", - "baseURL": "https://uat-unity-reporting.apps.silver.devops.gov.bc.ca/" + "baseURL": "https://uat-unity-reporting.apps.gold.devops.gov.bc.ca/" }, { "unityEnv": "PROD", - "baseURL": "https://unity-reporting.apps.silver.devops.gov.bc.ca/" + "baseURL": "https://unity-reporting.apps.gold.devops.gov.bc.ca/" } ] } \ No newline at end of file diff --git a/applications/Unity.GrantManager/.claude/agents/application-service-designer.md b/applications/Unity.GrantManager/.claude/agents/application-service-designer.md new file mode 100644 index 0000000000..c1ccceafe1 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/agents/application-service-designer.md @@ -0,0 +1,46 @@ +--- +name: application-service-designer +description: Designs ABP application service contracts, DTOs, authorization, and Mapperly mapping plans for Unity Grant Manager. Use when adding or changing app services, DTOs, or mapping profiles. +tools: Read, Grep, Glob, Bash +model: inherit +--- + +# ABP Application Service Designer Agent + +You are the application-layer design specialist for Unity Grant Manager. + +## Mission + +Produce ABP-compliant service contracts and implementation plans using DTO-first design. + +## Inputs + +- Use cases and API behavior. +- Existing service interfaces and DTOs. +- Target module and permissions. + +## Process + +1. Propose or update `I*AppService` method signatures. +2. Define DTOs per method intent (create, update, get, list). +3. Identify authorization requirements and permission constants. +4. Define Mapperly mapper changes. +5. Define validation and business-exception boundaries. + +## Output Format + +1. Contract changes. +2. DTO matrix. +3. Authorization matrix. +4. Mapping profile changes. +5. Service implementation checklist. +6. Test targets. + +## Guardrails + +- Apply the `unity-application-layer` skill's patterns. +- Follow `applications/Unity.GrantManager/.github/instructions/csharp.instructions.md`. +- Methods must be async and end with `Async`. +- Accept/return DTOs only, never entities. +- Use Mapperly with `ObjectMapper.Map<>()`, never AutoMapper. Mapper classes inherit `MapperBase` or `TwoWayMapperBase` and are decorated with `[Mapper]`. +- This agent designs only — it does not edit files. diff --git a/applications/Unity.GrantManager/.claude/agents/ddd-modeler.md b/applications/Unity.GrantManager/.claude/agents/ddd-modeler.md new file mode 100644 index 0000000000..9e400fe9cf --- /dev/null +++ b/applications/Unity.GrantManager/.claude/agents/ddd-modeler.md @@ -0,0 +1,47 @@ +--- +name: ddd-modeler +description: Designs and reviews ABP DDD models, aggregates, repositories, and domain managers for Unity Grant Manager. Use when creating or modifying entities, aggregates, repository contracts, or domain services. +tools: Read, Grep, Glob, Bash +model: inherit +--- + +# ABP DDD Modeler Agent + +You are the DDD modeling specialist for Unity Grant Manager. + +## Mission + +Design or review domain models so business invariants are enforced in the correct ABP layer. + +## Inputs + +- Business rules and scenarios. +- Existing entities and repository interfaces. +- Target module. + +## Process + +1. Define aggregate boundaries and ownership rules. +2. Identify entity/value object responsibilities. +3. Propose behavior methods that enforce invariants. +4. Define repository contract additions only for aggregate roots. +5. Define domain service responsibilities (`*Manager`) where orchestration is needed. +6. Propose business error codes and exception points. + +## Output Format + +1. Aggregate model proposal. +2. Invariants and rule enforcement table. +3. Repository contract changes. +4. Domain manager methods. +5. Error code list. +6. Anti-pattern checks. + +## Guardrails + +- Apply the `unity-domain-driven-design` skill's patterns. +- Follow `applications/Unity.GrantManager/.github/instructions/csharp.instructions.md`. +- Do not generate GUIDs in entity constructors. +- Reference external aggregates by Id only. +- Keep app-service logic out of the domain model design. +- This agent designs/reviews only — it does not edit files. diff --git a/applications/Unity.GrantManager/.claude/agents/efcore-migration-planner.md b/applications/Unity.GrantManager/.claude/agents/efcore-migration-planner.md new file mode 100644 index 0000000000..64e91f6487 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/agents/efcore-migration-planner.md @@ -0,0 +1,46 @@ +--- +name: efcore-migration-planner +description: Plans EF Core model updates and host versus tenant migrations safely for Unity Grant Manager. Use when a change touches entity mapping or requires a database migration. +tools: Read, Grep, Glob, Bash +model: inherit +--- + +# ABP EF Core Migration Planner Agent + +You are the EF Core migration planning specialist for Unity Grant Manager. + +## Mission + +Plan schema changes, mapping updates, and migration execution for the correct database context. + +## Inputs + +- Proposed entity/model changes. +- Whether data is host-wide or tenant-scoped. +- Existing migrations and repository code. + +## Process + +1. Classify each change as host, tenant, or both. +2. Propose `ModelBuilder` mapping updates. +3. Verify repository impact and query behavior. +4. Produce migration commands and ordering. +5. Identify rollback and data backfill considerations. + +## Output Format + +1. Context classification. +2. Mapping change checklist. +3. Migration command plan. +4. Data safety notes. +5. Repository update checklist. +6. Validation tests. + +## Guardrails + +- Apply the `unity-ef-core` skill's patterns. +- Follow `applications/Unity.GrantManager/.github/instructions/efcore.instructions.md`. +- Always call `ConfigureByConvention()` for mapped entities. +- Default repositories are currently registered with `includeAllEntities: true`; remove it only when you intentionally want aggregate-roots-only repositories and have verified no callers rely on entity repositories. +- Always specify context (`GrantManagerDbContext` or `GrantTenantDbContext`) for migration commands. +- This agent plans only — it does not run `dotnet ef migrations add` or edit files itself. diff --git a/applications/Unity.GrantManager/.claude/agents/feature-planner.md b/applications/Unity.GrantManager/.claude/agents/feature-planner.md new file mode 100644 index 0000000000..73a21bab90 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/agents/feature-planner.md @@ -0,0 +1,131 @@ +--- +name: feature-planner +description: Plans feature implementation across Domain, Application, EF Core, Web, and tests for Unity Grant Manager, respecting ABP layering. Use when a feature or bug needs a structured implementation plan before coding starts. +tools: Read, Grep, Glob, Bash, AskUserQuestion +model: inherit +--- + +# ABP Feature Planner Agent + +You are the FEATURE PLANNING AGENT for Unity Grant Manager, pairing with the user to create a detailed, actionable plan. + +You research the codebase → clarify with the user → produce a comprehensive plan that respects ABP modular layering and delivery flow. This iterative approach catches edge cases and non-obvious requirements BEFORE implementation begins. + +Your SOLE responsibility is planning. NEVER start implementation — you have no `Edit`/`Write` tools for that reason. + + +- Do not attempt to edit or write files — plans are for the user (or a follow-up implementation turn) to execute. +- Use `AskUserQuestion` freely to clarify requirements — don't make large assumptions. +- Present a well-researched plan with loose ends tied BEFORE handing off to implementation. + + + +Cycle through these phases based on user input. This is iterative, not linear. If the task is highly ambiguous, do only *Discovery* to outline a draft plan, then move to alignment before fleshing out the full plan. + +## 1. Discovery + +Read and search the codebase to gather context: analogous existing features to use as implementation templates, and potential blockers or ambiguities. + +Identify: +- Module ownership and whether the change is host, tenant, or both. +- Work split by ABP layer: Domain.Shared → Domain → Application.Contracts → Application → EntityFrameworkCore → HttpApi/Web → Tests. +- Dependencies and ordering constraints between layers. +- Cross-module impacts and permission/localization requirements. + +## 2. Alignment + +If research reveals major ambiguities or if you need to validate assumptions: +- Use `AskUserQuestion` to clarify intent with the user. +- Surface discovered technical constraints or alternative approaches. +- If answers significantly change the scope, loop back to **Discovery**. + +## 3. Design + +Once context is clear, draft a comprehensive implementation plan structured around ABP layers. + +The plan should reflect: +- Structured concisely enough to be scannable and detailed enough for effective execution. +- Step-by-step implementation with explicit dependencies — mark which steps can run in parallel vs. which block on prior steps. +- For plans with many steps, group into named phases that are each independently verifiable. +- Verification steps for validating the implementation, both automated and manual. +- Critical architecture to reuse or use as reference — reference specific functions, types, or patterns, not just file names. +- Critical files to be modified (with full paths). +- Explicit scope boundaries — what's included and what's deliberately excluded. +- Reference decisions from the discussion. +- Leave no ambiguity. + +Present the plan directly in your response — this agent has no persistent scratch file, so the plan you return IS the deliverable. + +## 4. Refinement + +On user input after showing the plan: +- Changes requested → revise and present updated plan. +- Questions asked → clarify, or use `AskUserQuestion` for follow-ups. +- Alternatives wanted → loop back to **Discovery**. +- Approval given → acknowledge; implementation is a separate turn/agent from here. + +Keep iterating until explicit approval. + + +## Inputs + +- Feature or bug statement. +- Acceptance criteria. +- Target module(s). +- Any constraints (timeline, migration risk, tenant scope, security requirements). + + +```markdown +## Plan: {Title (2-10 words)} + +{TL;DR - what, why, and how (your recommended approach).} + +**Steps** + +### Phase 1 — Domain & Contracts +1. {Domain.Shared changes — enums, consts, error codes} +2. {Domain entity/aggregate changes — note dependency ("*depends on N*") or parallelism ("*parallel with step N*") when applicable} +3. {Application.Contracts — DTOs, IAppService interfaces, permissions} + +### Phase 2 — Application & Persistence +4. {Application service implementation} +5. {EntityFrameworkCore — DbContext, entity config, migration} + +### Phase 3 — API & Frontend +6. {HttpApi controller / AutoAPI} +7. {Web — Pages, JS, localization} + +### Phase 4 — Tests +8. {Unit and integration tests} + +**Relevant files** +- `{full/path/to/file}` — {what to modify or reuse, referencing specific functions/patterns} + +**Migration & Data Impact** +- {Host vs tenant migration scope, data backfill needs, breaking schema changes} + +**Verification** +1. {Verification steps for validating the implementation (**Specific** tasks, tests, commands, etc; not generic statements)} + +**Decisions** (if applicable) +- {Decision, assumptions, and includes/excluded scope} + +**Risks & Mitigations** (if applicable) +- {Risk and mitigation strategy} + +**Definition of Done** +- [ ] {Checklist item} +``` + +Rules: +- NO code blocks — describe changes, link to files and specific symbols/functions. +- NO blocking questions at the end — ask during workflow via `AskUserQuestion`. +- The plan MUST be presented in full to the user, not just summarized. + + +## Guardrails + +- Enforce module dependency direction from the `unity-module-structure` skill. +- Enforce ABP app/domain rules from `applications/Unity.GrantManager/.github/instructions/csharp.instructions.md`. +- Do not use AutoMapper. Use Mapperly (`[Mapper]` attribute, `MapperBase`). +- Do not place business rules in controllers or app services. diff --git a/applications/Unity.GrantManager/.claude/agents/permissions-localization-auditor.md b/applications/Unity.GrantManager/.claude/agents/permissions-localization-auditor.md new file mode 100644 index 0000000000..1e124f4d7a --- /dev/null +++ b/applications/Unity.GrantManager/.claude/agents/permissions-localization-auditor.md @@ -0,0 +1,43 @@ +--- +name: permissions-localization-auditor +description: Audits ABP changes in Unity Grant Manager for permission coverage, localization correctness, and policy compliance. Use before a PR to check for missing permissions or hardcoded user-facing strings. +tools: Read, Grep, Glob, Bash +model: inherit +--- + +# ABP Permissions and Localization Auditor Agent + +You are the ABP compliance auditing specialist for Unity Grant Manager. + +## Mission + +Review code changes for missing permissions, hardcoded strings, and user-facing policy gaps. + +## Inputs + +- Diff or list of changed files. +- Affected user flows and roles. + +## Process + +1. Check service methods and endpoints for authorization attributes/policies. +2. Verify permission constants and definition provider coverage. +3. Scan for hardcoded user-facing text. +4. Verify localization key usage and resource updates. +5. Identify likely regressions and required tests. + +## Output Format + +1. Findings by severity. +2. Missing permissions list. +3. Localization findings list. +4. Required code changes. +5. Validation checklist. + +## Guardrails + +- Follow `applications/Unity.GrantManager/.github/copilot-instructions.md` and `applications/Unity.GrantManager/.github/instructions/csharp.instructions.md`. +- All user-facing text must be localized. +- Permissions must be defined in Application.Contracts permission providers. +- Do not propose hardcoded strings in services, controllers, or UI code. +- This agent audits only — it reports findings rather than editing files. diff --git a/applications/Unity.GrantManager/.claude/agents/pr-readiness-deep.md b/applications/Unity.GrantManager/.claude/agents/pr-readiness-deep.md new file mode 100644 index 0000000000..a8178f1d05 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/agents/pr-readiness-deep.md @@ -0,0 +1,102 @@ +--- +name: pr-readiness-deep +description: Deep PR quality gate for Unity Grant Manager that checks ABP architecture, runs backend and Cypress E2E tests. Use for a more thorough pre-PR check than pr-readiness, when you specifically need Cypress E2E coverage included. +tools: Read, Grep, Glob, Bash +model: inherit +--- + +# PR Readiness Agent (Deep Scan) + +Final quality gate for Unity Grant Manager PRs, covering ABP architecture, backend tests, and Cypress E2E. + +> **Scope note**: the original Copilot version of this agent also drove SonarQube (`sonarqube_analyze_file`, `sonarqube_list_potential_security_issues`) and CodeQL scanning/auto-fix. Those depend on VS Code extension tooling that isn't wired into this Claude Code setup (no SonarQube/CodeQL MCP server is configured in this project). This version keeps the parts that work standalone — ABP architecture review, build/test, and Cypress E2E — and applies the security-pattern checks below via code review instead of a scanner. If SonarQube/CodeQL MCP tools are added to this project later, re-introduce the scanning steps. + +## Inputs +- Branch diff, build/test status, target branch + +## Quality Checks Workflow + +### Step 1: ABP Architecture Review +- Layer boundaries (Domain → Application → Web) — see the `unity-module-structure` skill. +- Repository/DTO/Mapperly conventions — see the `unity-application-layer` skill. +- Permissions and localization keys present for all new user-facing behavior. +- EF migrations correct (host vs tenant context) if schema changes exist — see the `unity-ef-core` skill. + +### Step 2: Security Pattern Review (manual, in lieu of SonarQube/CodeQL) +Review changed files for the patterns in **Common Fixes** below. Flag any match as a blocking issue. + +### Step 3: Build & Backend Tests +```bash +dotnet build Unity.GrantManager.sln --no-restore +dotnet test Unity.GrantManager.sln --no-build +``` + +### Step 4: Cypress E2E Testing +```bash +cd applications/Unity.AutoUI +npm install +npx cypress run # headless +# npx cypress open # interactive, for debugging failures +``` + +Check for: all specs passing, no failed assertions, no unexpected console errors. On failure, review `cypress/screenshots/` and `cypress/videos/`, determine whether it's a stale selector (UI changed) or a real regression, then report which. + +## Common Fixes (patterns to flag during Step 2) + +```csharp +// ❌ SQL Injection +var sql = $"SELECT * FROM Users WHERE Email = '{email}'"; + +// ✅ Use EF LINQ +var users = await _dbContext.Users.Where(u => u.Email == email).ToListAsync(); + +// ❌ Missing authorization +public async Task DeleteAsync(Guid id) + +// ✅ Add attribute +[Authorize(GrantManagerPermissions.Applications.Delete)] +public async Task DeleteAsync(Guid id) + +// ❌ Return entity +public async Task GetAsync(Guid id) + +// ✅ Return DTO +public async Task GetAsync(Guid id) +{ + var entity = await _repository.GetAsync(id); + return ObjectMapper.Map(entity); +} + +// ❌ Path traversal +public async Task GetDocumentAsync(string fileName) +{ + var path = Path.Combine(root, "Documents", fileName); + return await File.ReadAllBytesAsync(path); +} + +// ✅ Validate path +public async Task GetDocumentAsync(Guid documentId) +{ + var doc = await _repository.GetAsync(documentId); + var safeFileName = Path.GetFileName(doc.FileName); + var fullPath = Path.GetFullPath(Path.Combine(root, "Documents", safeFileName)); + var allowedPath = Path.GetFullPath(Path.Combine(root, "Documents")); + + if (!fullPath.StartsWith(allowedPath)) + throw new BusinessException("Invalid path"); + + return await File.ReadAllBytesAsync(fullPath); +} +``` + +Also flag: hardcoded credentials/secrets, resource leaks (missing `using`/disposal), empty catch blocks, and logging of sensitive data. + +## Output + +1. **Summary**: files reviewed, issues found by severity, backend test result (X passed / Y failed), Cypress result (X passed / Y failed, with screenshot/video paths for failures). +2. **Go/No-Go**: + - ✅ GO — no blocking issues, all tests pass. + - ❌ NO-GO — blocking issues, test failures, or flagged security patterns need resolution. + - ⚠️ CONDITIONAL — minor issues present but mergeable with a follow-up task. +3. **Detailed findings**: file:line for each issue, with the specific fix. +4. **Validation commands run**, so the user can reproduce. diff --git a/applications/Unity.GrantManager/.claude/agents/pr-readiness.md b/applications/Unity.GrantManager/.claude/agents/pr-readiness.md new file mode 100644 index 0000000000..0294a5be73 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/agents/pr-readiness.md @@ -0,0 +1,43 @@ +--- +name: pr-readiness +description: Performs a pre-PR quality gate for Unity Grant Manager - build, tests, ABP layering, and policy compliance. Use before opening a PR to get a go/no-go readiness check. +tools: Read, Grep, Glob, Bash +model: inherit +--- + +# ABP PR Readiness Agent + +You are the final quality gate specialist for Unity Grant Manager pull requests. + +## Mission + +Evaluate if a branch is ready for PR against ABP architecture, policy, and CI expectations. + +## Inputs + +- Branch diff. +- Build and test status. +- Target branch. + +## Process + +1. Verify branch policy and PR source/target compatibility (`dev` from `feature/*`/`bugfix/*`/`hotfix/*`; `main` only from `test` or `hotfix/*`). +2. Check layering boundaries and module dependency direction. +3. Check mapping, DTO boundaries, localization, and permissions. +4. Check migration context correctness when EF changes exist. +5. Confirm test coverage and CI command readiness. + +## Output Format + +1. Go/No-go recommendation. +2. Blocking issues. +3. Non-blocking improvements. +4. Required validation commands. +5. PR description checklist. + +## Guardrails + +- Follow `applications/Unity.GrantManager/.github/copilot-instructions.md`. +- Require `dotnet build Unity.GrantManager.sln --no-restore` and `dotnet test Unity.GrantManager.sln --no-build` readiness — run them if not already confirmed clean. +- Enforce ABP module layering rules from the `unity-module-structure` skill. +- Enforce Mapperly, localization, and permissions conventions. diff --git a/applications/Unity.GrantManager/.claude/agents/test-strategy.md b/applications/Unity.GrantManager/.claude/agents/test-strategy.md new file mode 100644 index 0000000000..42e943fb18 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/agents/test-strategy.md @@ -0,0 +1,45 @@ +--- +name: test-strategy +description: Builds a risk-based test strategy for Unity Grant Manager using xUnit, Shouldly, NSubstitute, and layered coverage. Use when planning test coverage for a new feature or bug fix. +tools: Read, Grep, Glob, Bash +model: inherit +--- + +# ABP Test Strategy Agent + +You are the testing strategy specialist for Unity Grant Manager. + +## Mission + +Create a practical, risk-focused test plan for new features or bug fixes across ABP layers. + +## Inputs + +- Feature scope or code diff. +- Changed modules and layers. +- Known edge cases. + +## Process + +1. Identify impacted behavior per layer. +2. Split test coverage into unit, integration, and optional web tests. +3. Propose fixtures and test data setup. +4. Map scenarios to concrete test cases. +5. Prioritize tests for fastest feedback. + +## Output Format + +1. Coverage scope summary. +2. Unit test cases. +3. Integration test cases. +4. Test data and fixture requirements. +5. Execution order and commands. + +## Guardrails + +- Apply the `unity-testing` skill's patterns. +- Follow `applications/Unity.GrantManager/.github/instructions/testing.instructions.md`. +- Use xUnit with Shouldly and NSubstitute. +- Avoid `Assert.*` and Moq patterns. +- Keep tests deterministic and isolated. +- This agent plans only — it does not write test code itself. diff --git a/applications/Unity.GrantManager/.claude/agents/test-triage.md b/applications/Unity.GrantManager/.claude/agents/test-triage.md new file mode 100644 index 0000000000..f73f49b8e9 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/agents/test-triage.md @@ -0,0 +1,45 @@ +--- +name: test-triage +description: Diagnoses failing Unity Grant Manager tests, isolates root cause, and proposes minimal-risk fixes. Use when tests are failing and you need to find the smallest reliable fix. +tools: Read, Grep, Glob, Bash +model: inherit +--- + +# ABP Test Triage Agent + +You are the failure triage specialist for Unity Grant Manager tests. + +## Mission + +Analyze failing tests and identify the smallest reliable fix path while minimizing regressions. + +## Inputs + +- Test output logs. +- Recent code diff. +- Affected project/module. + +## Process + +1. Classify failure type (assertion mismatch, setup, infrastructure, async timing, mapping, auth). +2. Correlate failing tests with changed code paths. +3. Identify probable root cause and confidence level. +4. Propose minimum fix sequence with verification steps. +5. Identify regression tests that must be added or updated. + +## Output Format + +1. Failure summary. +2. Root-cause hypotheses ranked by probability. +3. Recommended fix path. +4. Verification command checklist. +5. Regression prevention tests. + +## Guardrails + +- Use module/layer rules from the `unity-module-structure` skill. +- Use testing conventions from the `unity-testing` skill. +- You may run `dotnet test` (e.g. `dotnet test Unity.GrantManager.sln --no-build`) to reproduce failures and verify hypotheses. +- Prefer minimal changes over broad refactors during triage. +- Do not bypass failing tests by weakening assertions without justification. +- This agent diagnoses and proposes fixes — it does not apply code edits itself. diff --git a/applications/Unity.GrantManager/.claude/rules/csharp.md b/applications/Unity.GrantManager/.claude/rules/csharp.md new file mode 100644 index 0000000000..8ec10b8ffc --- /dev/null +++ b/applications/Unity.GrantManager/.claude/rules/csharp.md @@ -0,0 +1,120 @@ +--- +globs: "**/*.cs" +--- + +# C# Conventions for Unity Grant Manager + +> C# and .NET 10 development standards for ABP Framework 10.5. + +- Target framework: .NET 10.0 with `latest`. +- Nullable reference types are enabled in most projects. +- This is an ABP Framework project. Use ABP base classes, not raw ASP.NET Core. +- This is NOT the Unity game engine. Do not suggest UnityEngine APIs. + +## ABP Base Classes + +- Application Services: Inherit `ApplicationService`, implement interface from Application.Contracts +- Domain Services: Inherit `DomainService`, use `Manager` suffix +- Entities: Inherit `FullAuditedAggregateRoot` or `AuditedAggregateRoot` +- API Controllers: Inherit `AbpController` +- Repositories: Use `IRepository` by default; custom only when needed + +### Injected Properties Available in Base Classes + +These properties are pre-injected in `ApplicationService`, `DomainService`, and `AbpController`: + +| Property | Purpose | +|---|---| +| `GuidGenerator` | Create new entity IDs — never use `Guid.NewGuid()` | +| `Clock` | Use `Clock.Now` — never use `DateTime.Now` or `DateTime.UtcNow` | +| `CurrentUser` | Access authenticated user (Id, Name, Email, Roles) | +| `CurrentTenant` | Access current tenant context (Id, Name) | +| `L` / `L["Key"]` | Localization shortcut | +| `ObjectMapper` | Mapperly-based mapping | +| `Logger` | Structured logging via `ILogger` | +| `AuthorizationService` | Programmatic authorization checks | +| `UnitOfWorkManager` | Manual unit-of-work control | + +## Dependency Injection + +- ABP auto-registers services using marker interfaces — do NOT manually call `services.AddScoped<>()` +- `ITransientDependency` — new instance per injection +- `ISingletonDependency` — single shared instance +- `IScopedDependency` — one per request +- Application services, domain services, and repositories are auto-registered by ABP + +## Entities & Domain + +- Entities use rich domain model: private/protected setters, behaviour via methods. +- Include `protected` parameterless constructor for EF Core deserialization. +- Do not generate `Guid` keys inside constructors; accept `id` from `IGuidGenerator`. +- Reference other aggregate roots by Id only, not navigation properties. +- Domain services use `*Manager` suffix. +- Throw `BusinessException` with namespaced error codes for rule violations. + +## Application Services + +- Interface naming: `I*AppService` inheriting `IApplicationService`. +- All methods `async`, name ends with `Async`. +- Accept/return DTOs only, never entities. Define DTOs in `*.Application.Contracts`. +- Make all public methods `virtual`. +- Use **Mapperly** (`ObjectMapper.Map<>()`) for DTO mapping. Do NOT use AutoMapper. +- Mapper classes: `*MapperlyProfile.cs` decorated with `[Mapper]`, inheriting `MapperBase` or `TwoWayMapperBase`. + +## Code Style + +- 4 spaces indentation, no tabs +- No emojis in comments +- Always use braces, even for single-line statements +- Use `nameof` instead of string literals when referring to member names +- Prefer pattern matching and switch expressions where appropriate +- All user-facing text must be localized via `L["Key"]`. No hardcoded English strings. +- Permissions defined in `*PermissionDefinitionProvider` in Application.Contracts. +- Do not call other application services within the same module; push shared logic to domain services. + +## Naming Conventions + +- Follow PascalCase for public members, types, and methods +- Use camelCase for private fields and local variables +- Prefix interface names with `I` +- Domain Services: `*Manager` suffix (e.g., `AssessmentManager`) +- Application Services: `*AppService` suffix (e.g., `ApplicationAppService`) +- DTOs: Descriptive suffixes (`CreateApplicationDto`, `UpdateApplicationDto`, `ApplicationDto`) +- Event Transfer Objects: `*Eto` suffix for distributed events + +## DTOs vs Entities + +- Application services MUST accept and return DTOs only, never entities +- Use `ObjectMapper` (Mapperly) to map between entities and DTOs +- Define mappers in `*MapperlyProfile` class in Application project + +## Authorization + +- Apply `[Authorize(PermissionName)]` attributes on application service methods +- Define permissions in `*Permissions` static class in Domain.Shared project + +## Multi-Tenancy + +- Tenant entities MUST implement `IMultiTenant` interface +- NEVER manually filter by `TenantId` — ABP handles this automatically +- Use `GrantTenantDbContext` for tenant data, `GrantManagerDbContext` for host data + +## Error Handling + +- Use `BusinessException` for domain-level errors with namespaced error codes (e.g., `"GrantManager:ApplicationNotFound"`) +- Map error codes to localization keys for user-friendly messages +- Use `.WithData("key", value)` for localized message interpolation +- Catch specific exception types, not generic `Exception` + +## 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 +- Don't create custom repositories unnecessarily — use generic `IRepository` first +- Don't mix host and tenant data in same DbContext +- Don't ignore nullable warnings — fix them properly +- Don't use `DateTime.Now` — use `Clock.Now` or inject `IClock` +- Don't use `Guid.NewGuid()` — use `GuidGenerator.Create()` +- Don't use `services.AddScoped<>()` for ABP services — use marker interfaces +- Don't call application services from within the same module — extract shared logic to a domain service +- Don't embed entity name in app service methods — use `GetAsync`, not `GetApplicationAsync` diff --git a/applications/Unity.GrantManager/.claude/rules/efcore.md b/applications/Unity.GrantManager/.claude/rules/efcore.md new file mode 100644 index 0000000000..07bd97e41a --- /dev/null +++ b/applications/Unity.GrantManager/.claude/rules/efcore.md @@ -0,0 +1,25 @@ +--- +globs: "**/EntityFrameworkCore/**/*.cs" +--- + +# EF Core Conventions for Unity Grant Manager + +- Provider: **Npgsql** (PostgreSQL 17). +- Two database contexts: `GrantManagerDbContext` (host) and `GrantTenantDbContext` (tenant). +- Entity configuration is done inline in `OnModelCreating` of `GrantManagerDbContext` and `GrantTenantDbContext`. +- When configuring entities, follow ABP conventions (e.g., table naming, key configuration) consistently. +- Use `options.AddDefaultRepositories(includeAllEntities: true)` in `GrantManagerEntityFrameworkCoreModule`. +- Prefer ABP's generated default repositories; add custom repositories only when additional behavior is required. +- Tests use **SQLite in-memory** databases, not PostgreSQL. + +## Migrations + +Always specify the context when adding migrations: + +```bash +# Host migrations +dotnet ef migrations add --context GrantManagerDbContext --output-dir Migrations/HostMigrations + +# Tenant migrations +dotnet ef migrations add --context GrantTenantDbContext --output-dir Migrations/TenantMigrations +``` diff --git a/applications/Unity.GrantManager/.claude/rules/javascript.md b/applications/Unity.GrantManager/.claude/rules/javascript.md new file mode 100644 index 0000000000..5885faa808 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/rules/javascript.md @@ -0,0 +1,57 @@ +--- +globs: "**/*.js" +--- + +# JavaScript Development Standards + +> JavaScript development standards for ABP Framework frontend patterns. + +- Variables should be declared with "let" or "const" instead of "var" + +## General Patterns + +- Wrap all page scripts in IIFE: `(function ($) { ... })(jQuery);` +- Never create global JavaScript variables +- Use `var l = abp.localization.getResource('GrantManager');` for all user-facing text +- Use ABP's dynamic JavaScript API client proxies instead of manual AJAX + +## ABP JavaScript Utilities + +- Notifications: `abp.notify.success()`, `.error()`, `.warn()`, `.info()` +- Confirmation: `abp.message.confirm()` for destructive actions +- Authorization: `abp.auth.isGranted()` for permission checks +- Busy indicators: `abp.ui.setBusy()` / `abp.ui.clearBusy()` +- Localization: `l('LocalizationKey')` — never hardcode user-facing strings + +## DataTables Integration + +- Use DataTables.net 2.x with Bootstrap 5 integration (`datatables.net-bs5`) +- Always wrap configuration with `abp.libs.datatables.normalizeConfiguration()` +- Use `abp.libs.datatables.createAjax()` for server-side pagination +- Use `rowAction` for action buttons with `abp.auth.isGranted()` visibility checks +- Use `dataFormat` property for automatic date/boolean formatting +- Always call `dataTable.ajax.reload()` after CRUD operations + +## Modal Manager + +- Use `abp.ModalManager` for all modal dialogs +- Configure with `viewUrl`, `scriptUrl`, and `modalClass` +- Implement `onResult()` callback to reload DataTable after save +- Modal script classes: register in `abp.modals.*` namespace +- Return `NoContent()` from Razor Page handler to close modal + +## DOM Auto-Initialization + +- ABP auto-initializes: tooltips, popovers, datepickers, AJAX forms, autocomplete selects +- Use `data-bs-toggle="tooltip"` for tooltips +- Use `class="auto-complete-select"` with `data-autocomplete-*` attributes for lookups +- Use `data-ajaxForm="true"` for AJAX form submission + +## Client-Side Package Management + +- Add NPM packages to `package.json`, prefer `@abp/*` packages +- Configure `abp.resourcemapping.js` to map from `node_modules` to `wwwroot/libs` +- Run `abp install-libs` to copy resources +- Add to bundle contributor in `Unity.Theme.UX2` module + + diff --git a/applications/Unity.GrantManager/.claude/rules/security.md b/applications/Unity.GrantManager/.claude/rules/security.md new file mode 100644 index 0000000000..73f0eecac5 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/rules/security.md @@ -0,0 +1,49 @@ +--- +globs: "**/*.cs, **/*.cshtml, **/*.js" +--- + +# Security Standards + +> Security best practices for Unity Grant Manager. + +## Authorization + +- Apply `[Authorize(PermissionName)]` attributes on all application service methods +- Define permissions in `*Permissions` static class in Domain.Shared project +- Use `abp.auth.isGranted()` in JavaScript for UI permission checks +- Never rely solely on UI-level permission hiding — always enforce server-side + +## Multi-Tenancy Security + +- Never manually filter by `TenantId` — ABP handles tenant isolation automatically +- Ensure tenant-scoped entities implement `IMultiTenant` +- Test cross-tenant data isolation explicitly +- Use `GrantTenantDbContext` for tenant data, `GrantManagerDbContext` for host data +- Be cautious with `[IgnoreMultiTenancy]` — understand the security implications + +## Input Validation + +- Validate all inputs at the application service boundary using data annotations or FluentValidation +- Use ABP's `Check.*` methods for domain-level validation (e.g., `Check.NotNullOrWhiteSpace`) +- Sanitize user inputs before storage — prevent XSS and injection attacks +- Use parameterized queries — never concatenate user input into SQL + +## Secrets Management + +- Never commit secrets, connection strings, or API keys to source code +- Use environment variables or secure configuration providers +- Reference `.env.example` for required environment variables +- Sensitive configuration is stored in OpenShift secrets and HashiCorp Vault when deployed + +## Authentication + +- Authentication is handled via Keycloak (OpenID Connect) +- Do not implement custom authentication — use ABP's identity infrastructure +- Ensure all API endpoints require authentication unless explicitly public + +## Data Protection + +- Use Redis-backed data protection for key storage in distributed deployments +- Encrypt sensitive data at rest when required by compliance +- Follow government security standards (BC Government policies) +- Audit logging is enabled via ABP — ensure sensitive operations are captured diff --git a/applications/Unity.GrantManager/.claude/rules/testing.md b/applications/Unity.GrantManager/.claude/rules/testing.md new file mode 100644 index 0000000000..538fd25278 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/rules/testing.md @@ -0,0 +1,30 @@ +--- +globs: "**/test/**/*.cs" +--- + +# Testing Conventions for Unity Grant Manager + +- Framework: **xUnit 2.9.3** with **Shouldly 4.3.0** assertions and **NSubstitute 5.3.0** mocks. +- Tests use in-memory database providers (SQLite in-memory for most test projects; `Unity.GrantManager.Web.Tests` uses `Microsoft.EntityFrameworkCore.InMemory`). No external PostgreSQL/database setup is required. +- Test class naming: `*Tests.cs`. +- Base class hierarchy: `AbpIntegratedTest` → `GrantManagerTestBase` → domain-specific bases. +- Use `[Fact]` for single tests, `[Theory]` with `[InlineData]` for parameterized. +- Assertions: Shouldly (`result.ShouldBe(expected)`, `result.ShouldNotBeNull()`). Do NOT use `Assert.*`. +- Mocking: NSubstitute (`Substitute.For()`). Do NOT use Moq. +- JSON test fixtures loaded from `AppDomain.CurrentDomain.BaseDirectory`. +- Run all tests: `dotnet test Unity.GrantManager.sln --no-build` +- Test method naming: `Should_[Expected]_[Scenario]` +- Follow Arrange-Act-Assert pattern consistently +- Do not emit "Arrange", "Act", or "Assert" comments in generated tests + +## Multi-Tenancy Testing + +- Test tenant data isolation using `CurrentTenant.Change(tenantId)` +- Verify that data created in one tenant is not visible in another +- Test both host-level and tenant-level operations + +## Test Data Management + +- Use helper methods for test data creation (e.g., `CreateTestApplicationAsync()`) +- Use static test data constants for well-known IDs +- Keep test data self-contained — each test should set up its own state diff --git a/applications/Unity.GrantManager/.claude/skills/abp-cli/SKILL.md b/applications/Unity.GrantManager/.claude/skills/abp-cli/SKILL.md new file mode 100644 index 0000000000..8ef9306fc6 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/skills/abp-cli/SKILL.md @@ -0,0 +1,78 @@ +--- +name: abp-cli +description: ABP CLI commands - generate-proxy, install-libs, add-package-ref, new-module, install-module, abp update, abp clean, abp suite generate. Use when the user asks how to run ABP CLI commands, generate proxies, install NPM libraries, or use ABP Suite. +--- + +# ABP CLI Commands + +> **Full documentation**: https://abp.io/docs/latest/cli +> Use `abp help [command]` for detailed options. + +## Generate Client Proxies + +```bash +# URL flag: `-u` (short) or `--url` (long). Use whichever your team prefers, but keep it consistent. +# +# Angular (host must be running) +abp generate-proxy -t ng + +# C# client proxies +abp generate-proxy -t csharp -u https://localhost:44300 + +# Integration services only (microservices) +abp generate-proxy -t csharp -u https://localhost:44300 -st integration + +# JavaScript +abp generate-proxy -t js -u https://localhost:44300 +``` + +## Install Client-Side Libraries + +```bash +# Install NPM packages for MVC/Blazor Server +abp install-libs +``` + +## Add Package Reference + +```bash +# Add project reference with module dependency +abp add-package-ref Acme.BookStore.Domain +abp add-package-ref Acme.BookStore.Domain -t Acme.BookStore.Application +``` + +## Module Operations + +```bash +# Create new module in solution +abp new-module Acme.OrderManagement -t module:ddd + +# Install published module +abp install-module Volo.Blogging + +# Add ABP NuGet package +abp add-package Volo.Abp.Caching.StackExchangeRedis +``` + +## Update & Clean + +```bash +abp update # Update all ABP packages +abp update --version 8.0.0 # Specific version +abp clean # Delete bin/obj folders +``` + +## Quick Reference + +| Task | Command | +|------|---------| +| Angular proxies | `abp generate-proxy -t ng` | +| C# proxies | `abp generate-proxy -t csharp -u URL` | +| Install JS libs | `abp install-libs` | +| Add reference | `abp add-package-ref PackageName` | +| Create module | `abp new-module ModuleName` | +| Install module | `abp install-module ModuleName` | +| Update packages | `abp update` | +| Clean solution | `abp clean` | +| Suite CRUD | `abp suite generate -e entity.json -s solution.sln` | +| Get help | `abp help [command]` | diff --git a/applications/Unity.GrantManager/.claude/skills/unity-application-layer/SKILL.md b/applications/Unity.GrantManager/.claude/skills/unity-application-layer/SKILL.md new file mode 100644 index 0000000000..ea2111c494 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/skills/unity-application-layer/SKILL.md @@ -0,0 +1,102 @@ +--- +name: unity-application-layer +description: ABP Application Services, DTOs, Mapperly mapping, validation, and error handling for Unity. Use when creating or modifying app services, DTOs, or mapping profiles in Application or Application.Contracts projects. +--- + +# Unity Application Layer Patterns + +## Application Service Contracts (Application.Contracts) + +- Interface naming: `I*AppService` inheriting `IApplicationService`. +- Define DTOs in `*.Application.Contracts` — never in Domain or Web. +- All methods async, end with `Async`. +- Do NOT repeat entity name in method names: use `GetAsync`, not `GetGrantAsync`. + +```csharp +public interface IGrantAppService : IApplicationService +{ + Task GetAsync(Guid id); + Task> GetListAsync(GetGrantListInput input); + Task CreateAsync(CreateGrantDto input); + Task UpdateAsync(Guid id, UpdateGrantDto input); // ID separate from DTO + Task DeleteAsync(Guid id); +} +``` + +## DTO Conventions + +| Purpose | Convention | Example | +|---------|------------|---------| +| Query input | `Get{Entity}Input` | `GetGrantInput` | +| List query | `Get{Entity}ListInput` | `GetGrantListInput` | +| Create input | `Create{Entity}Dto` | `CreateGrantDto` | +| Update input | `Update{Entity}Dto` | `UpdateGrantDto` | +| Output | `{Entity}Dto` | `GrantDto` | + +- Use data annotations for validation; reuse constants from Domain.Shared. +- Do NOT share input DTOs between methods. +- Do NOT put logic in DTOs (except `IValidatableObject` when necessary). + +## Implementation (Application) + +- Inherit from `ApplicationService`. +- Make all public methods `virtual`. +- Prefer `protected virtual` over `private` for helper methods. +- Use dedicated repositories, not inline LINQ in app services. +- Call `repository.UpdateAsync()` explicitly after mutations (don't assume change tracking). +- Do NOT use web types (`IFormFile`, `Stream`) — accept `byte[]` from controllers. +- Do NOT call other app services in the same module. Use domain services or repositories. + +## Object Mapping (Mapperly) + +This project uses **Mapperly** (not AutoMapper). Mapper classes are defined using `Riok.Mapperly.Abstractions` and `Volo.Abp.Mapperly`: + +```csharp +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; + +[Mapper] +public partial class GrantToGrantDtoMapper : MapperBase +{ + public override partial GrantDto Map(Grant source); + public override partial void Map(Grant source, GrantDto destination); +} + +// For bidirectional mapping: +[Mapper] +public partial class ZoneGroupDefinitionMapper : TwoWayMapperBase +{ + public override partial ZoneGroupDefinitionDto Map(ZoneGroupDefinition source); + public override partial void Map(ZoneGroupDefinition source, ZoneGroupDefinitionDto destination); + public override partial ZoneGroupDefinition ReverseMap(ZoneGroupDefinitionDto source); + public override partial void ReverseMap(ZoneGroupDefinitionDto source, ZoneGroupDefinition destination); +} +``` + +- Mapper files follow `*MapperlyProfile.cs` naming; each Application and Web project has its own file. +- Use `[MapperIgnoreTarget(nameof(...))]` to skip properties, `[MapProperty]` to rename, and `[MapPropertyFromSource]` for custom resolver methods. +- Call sites still use `ObjectMapper.Map(source)` — Mapperly provides the source-generated implementation. + +## Error Handling + +```csharp +// Business rule violation — use namespaced error code +throw new BusinessException("GrantManager:DuplicateName") + .WithData("Name", name); + +// Entity not found +throw new EntityNotFoundException(typeof(Grant), id); + +// User-facing message (use localized string) +throw new UserFriendlyException(L["GrantNotAvailable"]); +``` + +## Authorization + +- Use `[Authorize(PermissionName)]` on service methods. +- Permission names defined as constants in `*Permissions` classes in Application.Contracts. + +## Cross-Module Calls + +- You MAY call other modules' app services via their Application.Contracts interfaces. +- Do NOT call app services within the same module — use domain services or repositories. diff --git a/applications/Unity.GrantManager/.claude/skills/unity-domain-driven-design/SKILL.md b/applications/Unity.GrantManager/.claude/skills/unity-domain-driven-design/SKILL.md new file mode 100644 index 0000000000..f33f45ffc8 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/skills/unity-domain-driven-design/SKILL.md @@ -0,0 +1,105 @@ +--- +name: unity-domain-driven-design +description: DDD patterns for Unity - Entities, Aggregate Roots, Repositories, Domain Services, Domain Events. Use when creating or modifying entities, repositories, or domain services in Domain or Domain.Shared projects. +--- + +# Unity ABP DDD Patterns + +> Based on ABP Framework DDD conventions. This project uses ABP 10.5 with PostgreSQL 17 and EF Core 10. + +## Entities + +- Define entities in `*.Domain` projects. +- Use **rich domain model**: private/protected setters with methods that enforce invariants. +- Always provide a `protected` parameterless constructor for EF Core. +- Accept `Guid id` in the primary constructor; do NOT generate GUIDs inside constructors. Use `IGuidGenerator` from calling code. +- Make members `virtual` for ORM proxy compatibility. +- Initialize sub-collections in the primary constructor. + +```csharp +public class Grant : AuditedAggregateRoot +{ + public string Name { get; private set; } + public GrantStatus Status { get; private set; } + public ICollection Applications { get; private set; } + + protected Grant() { } // For EF Core + + public Grant(Guid id, string name) : base(id) + { + Name = Check.NotNullOrWhiteSpace(name, nameof(name)); + Status = GrantStatus.Draft; + Applications = new List(); + } + + public void SetName(string name) + { + Name = Check.NotNullOrWhiteSpace(name, nameof(name)); + } +} +``` + +## Aggregate Roots + +- Use a single `Id` property, prefer `Guid` keys. +- Inherit from `AggregateRoot` or audited base classes (`AuditedAggregateRoot`, `FullAuditedAggregateRoot`). +- Reference other aggregate roots **by Id only** — no cross-aggregate navigation properties. +- Keep aggregates small. + +## Repositories + +- Define repository interfaces in the Domain layer. +- One repository per aggregate root only. Never create repositories for child entities. +- Custom repository interface should inherit `IRepository`. +- All methods async with `CancellationToken cancellationToken = default`. +- Single-entity methods: `includeDetails = true` by default. +- List methods: `includeDetails = false` by default. + +```csharp +public interface IGrantRepository : IRepository +{ + Task FindByNameAsync(string name, bool includeDetails = true, CancellationToken cancellationToken = default); + Task> GetListByStatusAsync(GrantStatus status, bool includeDetails = false, CancellationToken cancellationToken = default); +} +``` + +## Domain Services + +- Naming: `*Manager` suffix (e.g., `GrantManager`). +- No interface by default unless multiple implementations are needed. +- Accept/return domain objects, not DTOs. +- Do NOT depend on authenticated user; accept required values from application layer. +- Use `GuidGenerator`, `Clock` from base class properties. + +```csharp +public class GrantManager : DomainService +{ + private readonly IGrantRepository _grantRepository; + + public GrantManager(IGrantRepository grantRepository) + { + _grantRepository = grantRepository; + } + + public async Task CreateAsync(string name) + { + var existing = await _grantRepository.FindByNameAsync(name); + if (existing != null) + throw new BusinessException("GrantManager:NameAlreadyExists").WithData("Name", name); + + return new Grant(GuidGenerator.Create(), name); + } +} +``` + +## Domain Events + +- `AddLocalEvent()` — same transaction, can access full entity state. +- `AddDistributedEvent()` — async, use ETOs defined in Domain.Shared. +- This project uses **RabbitMQ** for distributed events via `IDistributedEventBus`. + +## Shared Constants + +- Define constants, enums, and error codes in `*.Domain.Shared`. +- Localization resources (JSON) live under `Domain.Shared/Localization/*/en.json`. +- Error codes: namespaced as `ModuleName:ErrorCode`. diff --git a/applications/Unity.GrantManager/.claude/skills/unity-ef-core/SKILL.md b/applications/Unity.GrantManager/.claude/skills/unity-ef-core/SKILL.md new file mode 100644 index 0000000000..6ed9e666a4 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/skills/unity-ef-core/SKILL.md @@ -0,0 +1,111 @@ +--- +name: unity-ef-core +description: ABP Entity Framework Core for Unity - DbContext configuration, entity mapping, repository implementation, EF migrations. Use when working in EntityFrameworkCore projects, adding migrations, or implementing repositories. +--- + +# Unity EF Core Patterns + +> This project uses EF Core 10 with PostgreSQL 17 (Npgsql). Tests use SQLite in-memory. + +## Database Contexts + +This project has **two distinct database contexts**: + +| Context | Purpose | Migrations Directory | +|---------|---------|---------------------| +| `GrantManagerDbContext` | Host/shared system tables | `Migrations/HostMigrations` | +| `GrantTenantDbContext` | Per-tenant isolated data | `Migrations/TenantMigrations` | + +Always specify the context when adding migrations: + +```bash +cd src/Unity.GrantManager.EntityFrameworkCore + +# Host migration +dotnet ef migrations add --context GrantManagerDbContext --output-dir Migrations/HostMigrations + +# Tenant migration +dotnet ef migrations add --context GrantTenantDbContext --output-dir Migrations/TenantMigrations +``` + +## Entity Configuration + +Entity mapping is primarily configured inline in `OnModelCreating` in `GrantManagerDbContext` / `GrantTenantDbContext`. Extension methods on `ModelBuilder` are also used for shared/module-specific configuration (e.g., `modelBuilder.ConfigureAI()`). +```csharp +public static class GrantManagerDbContextModelCreatingExtensions +{ + public static void ConfigureGrantManager(this ModelBuilder builder) + { + Check.NotNull(builder, nameof(builder)); + + builder.Entity(b => + { + b.ToTable(GrantManagerConsts.DbTablePrefix + "Grants", GrantManagerConsts.DbSchema); + b.ConfigureByConvention(); // Always call this first + + b.Property(x => x.Name) + .IsRequired() + .HasMaxLength(GrantConsts.MaxNameLength); + + b.HasIndex(x => x.Name); + }); + } +} +``` + +**Rules:** +- Always call `b.ConfigureByConvention()` for every entity. +- Use table prefix from constants (not hardcoded). +- Default schema should be `null`. + +## Repository Implementation + +```csharp +public class GrantRepository : EfCoreRepository, IGrantRepository +{ + public GrantRepository(IDbContextProvider dbContextProvider) + : base(dbContextProvider) { } + + public async Task FindByNameAsync( + string name, + bool includeDetails = true, + CancellationToken cancellationToken = default) + { + var dbSet = await GetDbSetAsync(); + return await dbSet + .IncludeDetails(includeDetails) + .FirstOrDefaultAsync(g => g.Name == name, GetCancellationToken(cancellationToken)); + } +} +``` + +- Use DbContext interface as generic parameter. +- Pass cancellation tokens via `GetCancellationToken(cancellationToken)`. +- Use `IncludeDetails()` extensions per aggregate root. + +## Module Registration + +```csharp +context.Services.AddAbpDbContext(options => +{ + options.AddDefaultRepositories(); // Aggregate roots only, NOT includeAllEntities: true +}); + +Configure(options => +{ + options.UseNpgsql(); // PostgreSQL +}); +``` + +## Never Do + +| Don't | Do Instead | +|-------|-----------| +| `AddDefaultRepositories(includeAllEntities: true)` | `AddDefaultRepositories()` — aggregate roots only | +| Skip `ConfigureByConvention()` | Always call it first in entity config | +| Inject DbContext in app/domain services | Use `IRepository` or custom repository interface | +| Use lazy loading | Explicit `.Include()` via `IncludeDetails()` | + +## Migrations .editorconfig + +The `Migrations/` folder has its own `.editorconfig` suppressing analyzer warnings (S1128, S1192, CS8981, CA1861, IDE naming rules). This is intentional — do not modify migration files for style. diff --git a/applications/Unity.GrantManager/.claude/skills/unity-module-structure/SKILL.md b/applications/Unity.GrantManager/.claude/skills/unity-module-structure/SKILL.md new file mode 100644 index 0000000000..851e3db082 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/skills/unity-module-structure/SKILL.md @@ -0,0 +1,113 @@ +--- +name: unity-module-structure +description: ABP module architecture and layering rules for Unity. Use when creating new modules, adding cross-module dependencies, or understanding project organization and dependency direction. +--- + +# Unity Module Architecture + +## Module Layout + +Each ABP module follows a standard layered structure under `modules/`: + +``` +Unity.{ModuleName}/ + src/ + Unity.{ModuleName}.Domain.Shared/ ← Enums, constants, localization, ETOs + Unity.{ModuleName}.Domain/ ← Entities, repository interfaces, domain services + Unity.{ModuleName}.Application.Contracts/ ← DTOs, app service interfaces + Unity.{ModuleName}.Application/ ← App service implementations, Mapperly mappers + Unity.{ModuleName}.EntityFrameworkCore/ ← DbContext, migrations (if module has own DB tables) + Unity.{ModuleName}.HttpApi/ ← REST controllers + Unity.{ModuleName}.HttpApi.Client/ ← Remote client proxies + Unity.{ModuleName}.Web/ ← Razor Pages, view components + test/ + Unity.{ModuleName}.TestBase/ + Unity.{ModuleName}.Application.Tests/ + Unity.{ModuleName}.Domain.Tests/ + Unity.{ModuleName}.EntityFrameworkCore.Tests/ +``` + +Not all modules have every layer. Simpler modules may only have `Application`, `Application.Contracts`, `Shared`, and `Web`. + +## Current Modules + +| Module | Layers Present | Purpose | +|--------|---------------|---------| +| **Unity.Flex** | Shared, App.Contracts, App, Web, Tests | Dynamic forms/worksheets | +| **Unity.Notifications** | Full stack (Domain→Web, HttpApi, EF) | Email/messaging | +| **Unity.Payments** | Shared, App.Contracts, App, Web, Tests | Financial transactions | +| **Unity.Reporting** | Shared, App.Contracts, App, Web, Tests | Analytics & reports | +| **Unity.AI** | Shared, App.Contracts, App, Web | AI analysis (OpenAI) | +| **Unity.TenantManagement** | App.Contracts, App, HttpApi, Web, Tests | Multi-tenant admin | +| **Unity.Identity.Web** | Web, Tests | OIDC authentication UI | +| **Unity.Theme.UX2** | Theme package, Tests | Custom Razor Pages theme | +| **Unity.SharedKernel** | Single project | Cross-cutting utilities | + +## Dependency Direction (Strict) + +``` +Web → HttpApi → Application.Contracts +Application → Domain + Application.Contracts +Domain → Domain.Shared +EntityFrameworkCore → Domain only +``` + +### Rules + +- Web/HttpApi must NEVER depend on Application (only Application.Contracts). +- Application must NEVER depend on Web or EF Core. +- Domain must NEVER depend on Application, Web, or EF Core. +- Domain.Shared must have NO dependencies on other layers. +- EF Core must ONLY depend on Domain. + +## ABP Module Classes + +Every package has exactly one `AbpModule` class with `[DependsOn]` attributes. + +```csharp +[DependsOn( + typeof(GrantManagerDomainModule), + typeof(AbpEntityFrameworkCoreModule) +)] +public class GrantManagerEntityFrameworkCoreModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddAbpDbContext(options => + { + options.AddDefaultRepositories(includeAllEntities: true); + }); + } +} +``` + +## Multi-Tenancy + +- The system uses ABP multi-tenancy with separate database per tenant. +- `GrantManagerDbContext` = host context, `GrantTenantDbContext` = tenant context. +- Tenant-scoped data is accessed via `ICurrentTenant` / tenant switching. +- The `Unity.TenantManagement` module handles tenant administration. + +## Adding a New Feature + +1. Identify which module the feature belongs to. +2. Add entities/repositories in Domain layer. +3. Add DTOs/interfaces in Application.Contracts. +4. Implement app services in Application. +5. Add EF Core configuration if new tables are needed. +6. Add UI in Web layer. +7. Add tests in the module's test projects. +8. Register the module class with `[DependsOn]`. +9. Run `dotnet build Unity.GrantManager.sln` and `dotnet test Unity.GrantManager.sln` to verify. + +## Localization + +Each module with Domain.Shared has its own localization under: +`src/Unity.{ModuleName}.Domain.Shared/Localization/{ModuleName}/en.json` + +Use `L["Key"]` in application services and pages. All user-facing text must be localized. + +## Permissions + +Define in `*PermissionDefinitionProvider` in Application.Contracts. +Permission names follow `{ModuleName}.{Resource}.{Action}` convention. diff --git a/applications/Unity.GrantManager/.claude/skills/unity-testing/SKILL.md b/applications/Unity.GrantManager/.claude/skills/unity-testing/SKILL.md new file mode 100644 index 0000000000..4ddd9b1d0c --- /dev/null +++ b/applications/Unity.GrantManager/.claude/skills/unity-testing/SKILL.md @@ -0,0 +1,155 @@ +--- +name: unity-testing +description: Testing patterns for Unity - xUnit, Shouldly assertions, NSubstitute mocks, ABP test infrastructure. Use when writing or modifying unit tests or integration tests. +--- + +# Unity Testing Patterns + +## Test Infrastructure + +| Aspect | Value | +|--------|-------| +| Framework | xUnit 2.9.3 | +| Assertions | Shouldly 4.3.0 | +| Mocking | NSubstitute 5.3.0 | +| Database | In-memory (SQLite for most projects; EFCore.InMemory for Web tests – no PostgreSQL required) | +| Base Classes | ABP `AbpIntegratedTest` | +| Target | .NET 10 | + +## Test Project Locations + +``` +test/ + Unity.GrantManager.TestBase/ ← Shared fixtures & test data + Unity.GrantManager.Application.Tests/ ← App service tests + Unity.GrantManager.Domain.Tests/ ← Domain logic tests + Unity.GrantManager.EntityFrameworkCore.Tests/ + Unity.GrantManager.Web.Tests/ +modules/Unity.*/test/ ← Each module has its own test projects +``` + +## Running Tests + +```bash +# All tests (~470 tests, ~2 min) +dotnet test Unity.GrantManager.sln + +# Single project +dotnet test test/Unity.GrantManager.Application.Tests/ + +# After build (faster) +dotnet test Unity.GrantManager.sln --no-build +``` + +## Base Class Hierarchy + +``` +AbpIntegratedTest (Volo.Abp.Testing) +└── GrantManagerTestBase (shared UoW helpers) + ├── GrantManagerDomainTestBase (domain tests) + ├── GrantManagerEntityFrameworkCoreTestBase + └── Module-specific bases: + ├── FlexTestBaseModule + ├── TenantManagementTestBase + └── ReportingTestBase +``` + +## Writing Tests + +### Unit Test Example (with mocking) + +```csharp +public class MyServiceTests +{ + private readonly IMyRepository _repository; + private readonly MyService _sut; + + public MyServiceTests() + { + _repository = Substitute.For(); + _sut = new MyService(_repository); + } + + [Fact] + public async Task CreateAsync_WithValidInput_ShouldSucceed() + { + // Arrange + _repository.FindByNameAsync(Arg.Any()).Returns((MyEntity?)null); + + // Act + var result = await _sut.CreateAsync("test"); + + // Assert + result.ShouldNotBeNull(); + result.Name.ShouldBe("test"); + } +} +``` + +### Integration Test Example (ABP) + +```csharp +public class GrantAppServiceTests : GrantManagerApplicationTestBase +{ + private readonly IGrantAppService _grantAppService; + + public GrantAppServiceTests() + { + _grantAppService = GetRequiredService(); + } + + [Fact] + public async Task Should_Get_Grant_By_Id() + { + var result = await _grantAppService.GetAsync(GrantManagerTestData.GrantId); + result.ShouldNotBeNull(); + result.Id.ShouldBe(GrantManagerTestData.GrantId); + } +} +``` + +### Parameterized Tests + +```csharp +[Theory] +[InlineData("schema1.json", 128)] +[InlineData("schema2.json", 10)] +public void TestMapping(string filename, int expectedCount) +{ + var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TestData", filename); + var json = File.ReadAllText(path); + var result = Parse(json); + result.Count.ShouldBe(expectedCount); +} +``` + +## Test Data + +- JSON fixtures are loaded from `AppDomain.CurrentDomain.BaseDirectory` subdirectories. +- Domain tests include JSON files in `Intake/Files/*.json` and `Intake/Mapping/*.json` (copied to output via `.csproj`). +- Shared test data constants live in `*TestData.cs` classes within TestBase projects. + +## Web Tests + +Web tests use `[Collection]` fixture pattern: + +```csharp +[Collection(WebTestCollection.Name)] +public class MyWidgetTests +{ + private readonly IAbpLazyServiceProvider _lazyServiceProvider; + + public MyWidgetTests(WebTestFixture fixture) + { + _lazyServiceProvider = fixture.Services.GetRequiredService(); + } +} +``` + +## Conventions + +- Test class naming: `*Tests.cs` +- Method naming: `Should_ExpectedBehavior_When_Condition` or `MethodName_Scenario_ExpectedResult` +- Always use `Shouldly` for assertions (not `Assert.Equal`) +- Always use `NSubstitute` for mocking (not Moq) +- Test runner config: `xunit.runner.json` with `"shadowCopy": false` diff --git a/applications/Unity.GrantManager/modules/Unity.AI/docs/prompt-map.md b/applications/Unity.GrantManager/modules/Unity.AI/docs/prompt-map.md index f686766ffc..c5b346acf7 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/docs/prompt-map.md +++ b/applications/Unity.GrantManager/modules/Unity.AI/docs/prompt-map.md @@ -8,8 +8,7 @@ - `FormWorksheet` - worksheet generation ## Versions -- `v0`, `v1`, `v2` live under `Runtime/Prompts/Versions` -- The seeder loads built-in prompt rows from those versions +- Built-in `v0`, `v1`, and `v2` prompt rows are defined and seeded by `AIPromptDataSeeder` - Runtime selects the newest active prompt by family. ## Tenant selection diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Prompts/UnityPromptAssetManifest.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Prompts/UnityPromptAssetManifest.cs deleted file mode 100644 index 93bdb236cf..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Prompts/UnityPromptAssetManifest.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Unity.AI.Prompts; - -public sealed record UnityPromptAssetManifest( - [property: JsonPropertyName("operationName")] string OperationName, - [property: JsonPropertyName("promptVersion")] string PromptVersion, - [property: JsonPropertyName("inputContractName")] string InputContractName, - [property: JsonPropertyName("outputContractName")] string OutputContractName, - [property: JsonPropertyName("modelHint")] string? ModelHint = null, - [property: JsonPropertyName("profileHint")] string? ProfileHint = null); diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/DataSeed/AIModelDataSeeder.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/DataSeed/AIModelDataSeeder.cs index 7839db8e01..269ba47299 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/DataSeed/AIModelDataSeeder.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/DataSeed/AIModelDataSeeder.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text.Json; using System.Threading.Tasks; using Unity.AI.Domain; @@ -14,9 +15,9 @@ public class AIModelDataSeeder( { private static readonly BuiltInModelDefinition[] BuiltInModels = [ - new("Gpt4oMini", true, 0.3d), - new("Gpt5Mini", false, null), - new("Gpt5Nano", false, null) + new("gpt-4o-mini", "OpenAI", true, 0.3d), + new("gpt-5-mini", "OpenAI", false, null), + new("gpt-5-nano", "OpenAI", false, null) ]; public async Task SeedAsync(DataSeedContext context) @@ -40,9 +41,11 @@ private async Task EnsureModelAsync(BuiltInModelDefinition definition) Temperature = definition.Temperature }; - var existing = await modelRepository.FirstOrDefaultAsync(model => model.Name == definition.Name); + var existing = (await modelRepository.GetListAsync(model => + model.Name == definition.Name)).SingleOrDefault(); if (existing != null) { + existing.Provider = definition.Provider; existing.IsActive = true; existing.SettingsJson = JsonSerializer.Serialize(settings); await modelRepository.UpdateAsync(existing, autoSave: true); @@ -50,7 +53,7 @@ private async Task EnsureModelAsync(BuiltInModelDefinition definition) } await modelRepository.InsertAsync( - new AIModel(Guid.CreateVersion7(), definition.Name) + new AIModel(Guid.CreateVersion7(), definition.Name, definition.Provider) { IsActive = true, SettingsJson = JsonSerializer.Serialize(settings) @@ -60,6 +63,7 @@ await modelRepository.InsertAsync( private sealed record BuiltInModelDefinition( string Name, + string Provider, bool MaxOutputTokenCountSupported, double? Temperature); } diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/DataSeed/AIOperationDataSeeder.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/DataSeed/AIOperationDataSeeder.cs index d4504d69c7..8c96b3d3bf 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/DataSeed/AIOperationDataSeeder.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/DataSeed/AIOperationDataSeeder.cs @@ -19,7 +19,7 @@ public class AIOperationDataSeeder( ICurrentTenant currentTenant, ILogger logger) : ITransientDependency { - private const string DefaultModelName = "Gpt5Mini"; + private const string DefaultModelName = "gpt-5-mini"; private static readonly BuiltInOperationDefinition[] BuiltInOperations = [ diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/DataSeed/AIPromptDataSeeder.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/DataSeed/AIPromptDataSeeder.cs index 0d1af31c57..9fb8b7659d 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/DataSeed/AIPromptDataSeeder.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/DataSeed/AIPromptDataSeeder.cs @@ -177,7 +177,7 @@ await promptRepository.InsertAsync(new AIPrompt( } // ═════════════════════════════════════════════════════════════════════════ - // PROMPT CONTENT — mirrors Runtime/Prompts/Versions/ text files verbatim + // PROMPT CONTENT — authoritative built-in database seed definitions // ═════════════════════════════════════════════════════════════════════════ // ── v0/analysis.system.txt ─────────────────────────────────────────────── diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Domain/AIModel.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Domain/AIModel.cs index 511ba071ec..4fd8d90936 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Domain/AIModel.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Domain/AIModel.cs @@ -7,6 +7,8 @@ public class AIModel : AuditedAggregateRoot { public string Name { get; set; } = default!; + public string Provider { get; set; } = default!; + public bool IsActive { get; set; } = true; /// Free-form model settings stored as JSON for dynamic runtime options. @@ -16,9 +18,10 @@ protected AIModel() { } - public AIModel(Guid id, string name) + public AIModel(Guid id, string name, string provider) { Id = id; Name = name; + Provider = provider; } } diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/EntityFrameworkCore/AIDbContextModelCreatingExtensions.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/EntityFrameworkCore/AIDbContextModelCreatingExtensions.cs index 99633a9030..421939ef02 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/EntityFrameworkCore/AIDbContextModelCreatingExtensions.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/EntityFrameworkCore/AIDbContextModelCreatingExtensions.cs @@ -54,6 +54,10 @@ public static void ConfigureAI(this ModelBuilder modelBuilder) .IsRequired() .HasMaxLength(200); + b.Property(x => x.Provider) + .IsRequired() + .HasMaxLength(100); + b.Property(x => x.IsActive) .IsRequired(); diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Operations/AIExecutionModeResolver.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Operations/AIExecutionModeResolver.cs index e988120367..5b111136b8 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Operations/AIExecutionModeResolver.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Operations/AIExecutionModeResolver.cs @@ -1,17 +1,19 @@ -using Microsoft.Extensions.Configuration; using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Unity.AI.Domain; using Unity.AI.Runtime.Prompts; using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; namespace Unity.AI.Operations; /// -/// Resolves the configured for an AI operation. -/// Configuration keys: -/// Azure:Operations:{operationName}:ExecutionMode - "Sequential" | "Parallel" | "Batch" (case-insensitive) -/// Azure:Operations:Defaults:ExecutionMode - required default when operation override is absent +/// Resolves the persisted for an active AI operation. /// -public class AIExecutionModeResolver(IConfiguration configuration) : ITransientDependency +public class AIExecutionModeResolver( + IRepository operationRepository) : ITransientDependency { public const string AttachmentSummaryOperation = AIPromptTypes.AttachmentSummary; public const string ApplicationScoringOperation = AIPromptTypes.ApplicationScoring; @@ -19,20 +21,20 @@ public class AIExecutionModeResolver(IConfiguration configuration) : ITransientD public const string FormWorksheetOperation = AIPromptTypes.FormWorksheet; public const string FormScoresheetOperation = AIPromptTypes.FormScoresheet; - public AIExecutionMode ResolveMode(string operationName) + public async Task ResolveModeAsync( + string operationName, + CancellationToken cancellationToken = default) { - var configured = configuration[$"Azure:Operations:{operationName}:ExecutionMode"]; - if (string.IsNullOrWhiteSpace(configured)) + var operations = await operationRepository.GetListAsync( + candidate => candidate.IsActive, + cancellationToken: cancellationToken); + var operation = operations.FirstOrDefault(candidate => + string.Equals(candidate.Name, operationName, StringComparison.OrdinalIgnoreCase)); + if (operation == null) { - configured = configuration["Azure:Operations:Defaults:ExecutionMode"]; + throw new InvalidOperationException($"AI operation '{operationName}' is not configured."); } - return configured?.Trim().ToLowerInvariant() switch - { - "sequential" => AIExecutionMode.Sequential, - "parallel" => AIExecutionMode.Parallel, - "batch" => AIExecutionMode.Batch, - _ => throw new InvalidOperationException($"AI execution mode is not configured or is invalid for operation '{operationName}'.") - }; + return operation.ExecutionMode; } } diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Operations/ApplicationScoringService.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Operations/ApplicationScoringService.cs index aeb4dadb1e..af4385a329 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Operations/ApplicationScoringService.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Operations/ApplicationScoringService.cs @@ -21,7 +21,9 @@ public class ApplicationScoringService( public async Task RegenerateAsync(ApplicationScoringOperationInputDto input, CancellationToken cancellationToken = default) { var sections = input.Sections; - var mode = executionModeResolver.ResolveMode(AIExecutionModeResolver.ApplicationScoringOperation); + var mode = await executionModeResolver.ResolveModeAsync( + AIExecutionModeResolver.ApplicationScoringOperation, + cancellationToken); var perSectionResults = await AIExecutionStrategy.RunAsync( sections, diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Execution/OpenAIConfigurationResolver.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Execution/OpenAIConfigurationResolver.cs index e88fb4e731..8f536e9e40 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Execution/OpenAIConfigurationResolver.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Execution/OpenAIConfigurationResolver.cs @@ -23,6 +23,7 @@ public class OpenAIConfigurationResolver( IDataFilter multiTenantDataFilter, ICurrentTenant currentTenant) : ITransientDependency { + private const string DefaultProviderName = "OpenAI"; private static readonly TimeSpan SettingsCacheDuration = TimeSpan.FromMinutes(5); private static readonly JsonSerializerOptions JsonOptions = new() { @@ -37,12 +38,11 @@ public class OpenAIConfigurationResolver( private readonly IDataFilter _multiTenantDataFilter = multiTenantDataFilter; private readonly ICurrentTenant _currentTenant = currentTenant; - public string ResolveProviderName() => Required("Azure:Operations:Defaults:Provider"); + public string ResolveProviderName() => DefaultProviderName; public Task ResolveApiKeyAsync(string? modelName = null, CancellationToken cancellationToken = default) { - var providerName = Required("Azure:Operations:Defaults:Provider"); - return Task.FromResult(Required($"Azure:{providerName}:ApiKey")); + return Task.FromResult(Required($"Azure:{DefaultProviderName}:ApiKey")); } public async Task ResolveOperationSettingsAsync( @@ -68,7 +68,12 @@ public async Task ResolveOperationSettingsAsync( } var modelSettings = ResolveModelSettings(model); - var providerName = Required("Azure:Operations:Defaults:Provider"); + var providerName = model.Provider; + if (!string.Equals(providerName, DefaultProviderName, StringComparison.Ordinal)) + { + throw new InvalidOperationException($"AI provider '{providerName}' is not supported."); + } + var endpoint = Required($"Azure:{providerName}:Endpoint"); if (!Uri.TryCreate(endpoint, UriKind.Absolute, out _)) { @@ -91,7 +96,7 @@ public async Task ResolveOperationSettingsAsync( model.Name, apiKey, new Uri(endpoint), - Required($"Azure:{providerName}:Profiles:{model.Name}:DeploymentName"), + model.Name, modelSettings.MaxOutputTokenCountSupported, modelSettings.Temperature, operation.CompletionTokens, @@ -117,19 +122,6 @@ public async Task ResolveMaxOutputTokenCountSupportedAsync(string? modelNa var modelConfiguration = await ResolveModelConfigurationAsync(modelName, cancellationToken); if (modelConfiguration != null) { - var providerName = Required("Azure:Operations:Defaults:Provider"); - var profileName = modelConfiguration.Value.Model.Name; - var configuredValue = Optional($"Azure:{providerName}:Profiles:{profileName}:MaxOutputTokenCountSupported"); - if (configuredValue != null) - { - if (bool.TryParse(configuredValue, out var parsedValue)) - { - return parsedValue; - } - - throw new InvalidOperationException($"Azure:{providerName}:Profiles:{profileName}:MaxOutputTokenCountSupported is not a valid boolean."); - } - return modelConfiguration.Value.Settings.MaxOutputTokenCountSupported; } @@ -168,8 +160,7 @@ public async Task ResolveEndpointAsync(string? modelName = null, Cancellati var modelConfiguration = await ResolveModelConfigurationAsync(modelName, cancellationToken); if (modelConfiguration != null) { - var providerName = Required("Azure:Operations:Defaults:Provider"); - return new Uri(Required($"Azure:{providerName}:Endpoint")); + return new Uri(Required($"Azure:{modelConfiguration.Value.Model.Provider}:Endpoint")); } throw new InvalidOperationException("AI model is not configured."); @@ -180,8 +171,7 @@ public async Task ResolveDeploymentNameAsync(string? modelName = null, C var modelConfiguration = await ResolveModelConfigurationAsync(modelName, cancellationToken); if (modelConfiguration != null) { - var providerName = Required("Azure:Operations:Defaults:Provider"); - return Required($"Azure:{providerName}:Profiles:{modelConfiguration.Value.Model.Name}:DeploymentName"); + return modelConfiguration.Value.Model.Name; } throw new InvalidOperationException("AI model is not configured."); @@ -242,17 +232,6 @@ private static AIModelSettings ResolveModelSettings(AIModel model) string.Equals(model.Name, modelName, StringComparison.OrdinalIgnoreCase)); } - var configuredDefaultProfile = Optional("Azure:Operations:Defaults:Profile"); - if (!string.IsNullOrWhiteSpace(configuredDefaultProfile)) - { - var configuredDefaultModel = activeModels.FirstOrDefault(model => - string.Equals(model.Name, configuredDefaultProfile, StringComparison.OrdinalIgnoreCase)); - if (configuredDefaultModel != null) - { - return configuredDefaultModel; - } - } - return null; } diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v0/application-analysis.system.txt b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v0/application-analysis.system.txt deleted file mode 100644 index f7d3be3bb3..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v0/application-analysis.system.txt +++ /dev/null @@ -1,12 +0,0 @@ -You are an expert grant application reviewer for the BC Government. - -Conduct a thorough, comprehensive analysis across all rubric areas. Identify substantive issues, concerns, and opportunities for improvement. - -Classify findings by their effect on the application's quality and fundability: -- ERRORS: important missing information, significant gaps, compliance issues, or major concerns affecting eligibility -- WARNINGS: areas needing clarification, moderate issues, or concerns that should be addressed -- SUMMARIES: concise reviewer-facing recommendations or follow-up considerations - -Evaluate content quality, clarity, and appropriateness. Be thorough but fair and avoid nitpicking. - -Respond only with valid JSON in the exact format requested. diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v0/application-analysis.user.txt b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v0/application-analysis.user.txt deleted file mode 100644 index 8d7c84c90e..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v0/application-analysis.user.txt +++ /dev/null @@ -1,93 +0,0 @@ -APPLICATION CONTENT: -{{DATA}} - -ATTACHMENT SUMMARIES: -{{ATTACHMENTS}} - -FORM FIELD CONFIGURATION: -{{SCHEMA}} - -MANDATORY FIELDS: -- Determine mandatory fields from FORM FIELD CONFIGURATION. -- Report missing mandatory fields as findings when they materially affect review quality. - -OPTIONAL FIELDS (may be left blank): -- Determine optional fields from FORM FIELD CONFIGURATION. -- Do not flag optional fields when blank unless they materially weaken rubric evidence. - -EVALUATION RUBRIC: -BC GOVERNMENT GRANT EVALUATION RUBRIC: - -1. ELIGIBILITY REQUIREMENTS: - - Project must align with program objectives - - Applicant must be eligible entity type - - Budget must be reasonable and well-justified - - Project timeline must be realistic - -2. COMPLETENESS CHECKS: - - All required fields completed - - Necessary supporting documents provided - - Budget breakdown detailed and accurate - - Project description clear and comprehensive - -3. FINANCIAL REVIEW: - - Requested amount is within program limits - - Budget is reasonable for scope of work - - Matching funds or in-kind contributions identified - - Cost per outcome/beneficiary is reasonable - -4. RISK ASSESSMENT: - - Applicant capacity to deliver project - - Technical feasibility of proposed work - - Environmental or regulatory compliance - - Potential for cost overruns or delays - -5. QUALITY INDICATORS: - - Clear project objectives and outcomes - - Well-defined target audience/beneficiaries - - Appropriate project methodology - - Sustainability plan for long-term impact - -EVALUATION CRITERIA: -- HIGH: Meets all requirements, well-prepared application, low risk -- MEDIUM: Meets most requirements, minor issues or missing elements -- LOW: Missing key requirements, significant concerns, high risk - -Analyze this grant application comprehensively across all five rubric categories (Eligibility, Completeness, Financial Review, Risk Assessment, and Quality Indicators). Identify issues, concerns, and areas for improvement. - -OUTPUT -{ - "decision": "", - "warnings": [ - { - "title": "", - "detail": "" - } - ], - "errors": [ - { - "title": "", - "detail": "" - } - ], - "summaries": [ - { - "title": "", - "detail": "" - } - ], - "recommendations": [ - { - "title": "", - "detail": "" - } - ] -} - -Important: -- Use only APPLICATION CONTENT, ATTACHMENT SUMMARIES, FORM FIELD CONFIGURATION, and EVALUATION RUBRIC as evidence. -- decision must be PROCEED or HOLD. -- Use summaries for overall application quality/readiness synthesis. -- Use recommendations for reviewer-facing follow-up actions or considerations before scoring or decision-making. -- Use "title" and "detail" keys for all finding objects. -- Return valid plain JSON only in the exact OUTPUT shape. diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v0/application-scoring.system.txt b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v0/application-scoring.system.txt deleted file mode 100644 index 0a629a6a97..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v0/application-scoring.system.txt +++ /dev/null @@ -1,5 +0,0 @@ -You are an expert grant application reviewer for the BC Government. -Analyze the provided application and answer only the questions in the specified scoresheet section. -Be thorough, objective, and fair. Base answers strictly on provided evidence. -Always provide evidence-grounded rationale and an honest confidence score. -Respond only with valid JSON in the exact format requested. diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v0/application-scoring.user.txt b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v0/application-scoring.user.txt deleted file mode 100644 index 6230fe0ea3..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v0/application-scoring.user.txt +++ /dev/null @@ -1,38 +0,0 @@ -APPLICATION CONTENT: -{{DATA}} - -ATTACHMENT SUMMARIES: -{{ATTACHMENTS}} - -SCORESHEET SECTION: -{{SECTION}} - -RESPONSE TEMPLATE: -{{RESPONSE}} - -Please analyze this grant application and provide answers for each question in the specified section only. - -For each question, provide: -1. The answer based on the application evidence -2. A brief rationale (1-2 complete sentences) citing concrete supporting evidence -3. A confidence score from 0-100 (integer) indicating certainty in the selected answer - -OUTPUT -{ - "": { - "answer": "", - "rationale": "", - "confidence": - } -} - -Important: -- Use only APPLICATION CONTENT and ATTACHMENT SUMMARIES as evidence. -- Answer only the question IDs in the specified section. -- Every question must include answer, rationale, and confidence. -- Use RESPONSE TEMPLATE as the contract and fill every placeholder value. -- answer type must match the question type. -- For select list questions, return only the option number as a string, never label text. -- rationale must be 1-2 complete sentences grounded in evidence. -- confidence must be an integer from 0 to 100 in increments of 5. -- Return valid plain JSON only in the exact OUTPUT shape. diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v0/attachment-summary.system.txt b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v0/attachment-summary.system.txt deleted file mode 100644 index 6e2775e346..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v0/attachment-summary.system.txt +++ /dev/null @@ -1,10 +0,0 @@ -You are a professional grant analyst for the BC Government. - -Please analyze this attachment and provide a concise reviewer-facing summary of its content, purpose, and key information. - -OUTPUT -{ - "summary": "" -} - -Use only ATTACHMENT as evidence. If ATTACHMENT.text is present, summarize actual content; otherwise provide a conservative file-level summary. Write 1-2 complete sentences and return valid plain JSON only in the exact OUTPUT shape. diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v0/attachment-summary.user.txt b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v0/attachment-summary.user.txt deleted file mode 100644 index 98da3a726e..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v0/attachment-summary.user.txt +++ /dev/null @@ -1,2 +0,0 @@ -ATTACHMENTS -{{ATTACHMENTS}} diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-analysis.output.txt b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-analysis.output.txt deleted file mode 100644 index 44093f50be..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-analysis.output.txt +++ /dev/null @@ -1,27 +0,0 @@ -{ - "decision": "", - "errors": [ - { - "title": "", - "detail": "" - } - ], - "warnings": [ - { - "title": "", - "detail": "" - } - ], - "summaries": [ - { - "title": "", - "detail": "" - } - ], - "recommendations": [ - { - "title": "", - "detail": "" - } - ] -} diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-analysis.rubric.txt b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-analysis.rubric.txt deleted file mode 100644 index 71ef4cb126..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-analysis.rubric.txt +++ /dev/null @@ -1,5 +0,0 @@ -ELIGIBILITY REQUIREMENTS: Project aligns with program objectives; Applicant is an eligible entity; Budget is reasonable and justified; Timeline is realistic. -COMPLETENESS CHECKS: Required information is present; Supporting materials are provided where applicable; Description is clear. -FINANCIAL REVIEW: Requested amount is within limits; Budget matches scope; Matching funds or contributions are identified. -RISK ASSESSMENT: Applicant capacity; Feasibility; Compliance considerations; Delivery risks. -QUALITY INDICATORS: Clear objectives; Defined beneficiaries; Appropriate approach; Long-term sustainability. diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-analysis.rules.txt b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-analysis.rules.txt deleted file mode 100644 index f1627fb26f..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-analysis.rules.txt +++ /dev/null @@ -1,32 +0,0 @@ -- Use only provided input sections as evidence. -- Do not invent fields, documents, requirements, or facts. -- Prefer, in order: direct evidence from DATA, specific supporting evidence from ATTACHMENTS, then broader context only when necessary. -- Treat missing or empty values as findings only when they weaken rubric evidence. -- Prefer material findings; avoid nitpicking. -- Do not restate basic application facts as findings unless they support a specific reviewer conclusion about readiness, feasibility, budget credibility, eligibility, or confidence in proceeding. -- Prefer direct evidence from DATA over derivative statements in ATTACHMENTS when both address the same point. -- If ATTACHMENTS evidence is used, cite the attachment by name in detail. -- Each detail must cite concrete evidence from DATA or ATTACHMENTS. -- Write reviewer-facing natural language. Do not refer to prompt section names, internal field keys, or schema labels such as DATA, ATTACHMENTS, ProjectSummary, CustomField1, or OrganizationType. -- Refer to evidence by its plain-language meaning, quoted text, or attachment name rather than internal key names. -- Only include warnings when the evidence shows a specific, concrete risk, inconsistency, or meaningful uncertainty; a stated risk label alone is not enough. -- Do not state that one amount exceeds, matches, or conflicts with another unless the comparison is directly supported by the provided values. -- Do not treat ordinary lack of detailed supporting explanation as a material gap unless the provided evidence creates real uncertainty about feasibility, eligibility, or budget credibility. -- Prefer neutral evidence descriptions over evaluative adjectives unless the evidence directly supports a strong conclusion. -- Do not describe capacity, feasibility, or justification as strong, detailed, or well-supported unless the evidence shows more than the existence of basic organizational, budget, or timeline information. -- Do not infer community support, established partnerships, or delivery capacity from a single partner reference, staff count, or basic organizational status alone. -- Do not describe a timeline as realistic or feasible based only on start and end dates unless additional evidence supports deliverability. -- Use 3-6 words for title. -- Summary titles should name the specific substantive reviewer conclusion, strength, or risk, not a generic evaluation label or abstract category. -- Each detail must be 1-2 complete sentences. -- Summaries and recommendations must be concrete, distinct, reviewer-relevant, and specific to this application's evidence. -- Avoid generic praise, checklist language, and repeated conclusions across lists. -- Do not use a summary merely to say that supporting documents were provided; summarize the specific substantive evidence they add, or omit the finding. -- Errors and warnings may be empty. -- Summaries and recommendations must each include at least one item. -- Decision must be PROCEED or HOLD. -- Use summaries for overall application quality/readiness synthesis. -- Use recommendations for concrete reviewer-facing next actions based on the provided evidence. -- Recommendations may include proceeding with the normal review process when the application appears ready for that step. -- When evidence shows a meaningful gap, inconsistency, or uncertainty, use recommendations for specific follow-up or verification actions. -- Return an empty array only when no concrete next action would help the reviewer. diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-analysis.score.txt b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-analysis.score.txt deleted file mode 100644 index 0ce56f3e81..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-analysis.score.txt +++ /dev/null @@ -1,3 +0,0 @@ -HIGH: Application demonstrates strong evidence across most rubric areas with few or no issues. -MEDIUM: Application has some gaps or weaknesses that require reviewer attention. -LOW: Application has significant gaps or risks across key rubric areas. diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-analysis.system.txt b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-analysis.system.txt deleted file mode 100644 index b05196b8ab..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-analysis.system.txt +++ /dev/null @@ -1,9 +0,0 @@ -ROLE -You are a careful grant review assistant for human reviewers. Do not fill gaps, assume compliance, or treat relevance as proof. - -TASK -Using SCHEMA, DATA, ATTACHMENTS, RUBRIC, SCORE, OUTPUT, and RULES: -1. Review the application and any provided attachments for the strongest reviewer-relevant evidence. -2. Determine which conclusions are directly supported by that evidence. -3. Exclude weak, repetitive, or loosely supported conclusions. -4. Return only the strongest evidence-backed reviewer conclusions. diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-analysis.user.txt b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-analysis.user.txt deleted file mode 100644 index b450089268..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-analysis.user.txt +++ /dev/null @@ -1,21 +0,0 @@ -SCHEMA -{{SCHEMA}} - -DATA -{{DATA}} - -ATTACHMENTS -{{ATTACHMENTS}} - -RUBRIC -{{RUBRIC}} - -SCORE -{{SCORE}} - -RESPONSE -{{RESPONSE}} - -RULES -{{RULES}} -{{COMMON_RULES}} diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-scoring.output.txt b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-scoring.output.txt deleted file mode 100644 index 8af7a3f2fd..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-scoring.output.txt +++ /dev/null @@ -1,7 +0,0 @@ -{ - "": { - "answer": "", - "rationale": "", - "confidence": - } -} diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-scoring.rules.txt b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-scoring.rules.txt deleted file mode 100644 index 81a4132069..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-scoring.rules.txt +++ /dev/null @@ -1,51 +0,0 @@ -- Use only DATA and ATTACHMENTS as evidence. -- Do not invent missing application details. -- Ignore fields or details that are not relevant to the specific question being answered. -- Prefer, in order: direct evidence of the exact condition asked, closely related supporting evidence, then general context only when necessary. -- If evidence is insufficient, partial, indirect, missing, or non-specific, choose the most conservative valid answer and explain the uncertainty. -- Do not convert general project descriptions into evidence for a specific scored condition unless that condition is directly supported. -- Treat prefilled labels, ratings, rankings, or statuses as background context only unless the question explicitly asks for that same item. -- Do not treat related concepts as equivalent; answer the specific question asked, not a nearby concept. -- Do not infer unsupported claims about requirements, conditions, relationships, compliance elements, mitigations, supports, or outcomes. -- Answer a specific condition positively only when that exact condition is directly evidenced in DATA or ATTACHMENTS. -- For eligibility, completeness, ownership, location, or compliance questions, do not answer positively unless the exact condition is directly confirmed in the provided evidence. -- If the evidence shows only involvement, presence, relevance, or association, do not treat that alone as proof that a requirement or condition is satisfied. -- Return exactly one answer object per question ID in SECTION.questions. -- Do not omit any question IDs from SECTION.questions. -- Do not add keys that are not question IDs from SECTION.questions. -- Use the exact question IDs from RESPONSE and SECTION.questions without alteration; never rewrite, normalize, or regenerate a question ID. -- Use RESPONSE as the output contract and fill every placeholder value. -- Each answer object must include: "answer", "rationale", and "confidence". -- Never omit "answer", "rationale", or "confidence" for any question type. -- The "answer" value type must match question type: Number => numeric; YesNo/SelectList/Text/TextArea => string. -- The "rationale" field must be 1-2 complete sentences grounded in concrete DATA/ATTACHMENTS evidence. -- In rationale, cite concrete source evidence from the provided input content in plain language rather than prompt section headers or internal field names. -- Write reviewer-facing natural language. Do not refer to prompt section names, internal field keys, or schema labels such as DATA, ATTACHMENTS, ProjectSummary, CustomField1, or OrganizationType. -- Refer to evidence by its plain-language meaning, quoted text, or attachment name rather than internal key names. -- For every question, rationale must justify both the selected answer and the selected confidence level based on evidence strength. -- The "confidence" field must be an integer from 0 to 100 in increments of 5 and represents confidence in the selected answer. -- Set confidence by certainty of the selected answer based on available evidence, regardless of which option is selected. -- Do not use maximum or near-maximum confidence when the answer depends on inference rather than an explicit statement of the exact condition. -- For yes/no questions, the "answer" field must be exactly "Yes" or "No". -- For numeric questions, answer must be a numeric value within the allowed range. -- For numeric questions, answer must never be blank. -- If evidence is insufficient for a numeric question, return the minimum allowed numeric value and explain uncertainty in rationale. -- If a required value is explicitly missing in DATA/ATTACHMENTS, set confidence high (80-100) when selecting the conservative minimum. -- For select list questions, use the matching SECTION.questions[].options entries and return only the selected options[].number as a string. -- For select list questions, the "answer" value must be one of the matching question.allowed_answers values exactly. -- For select list questions, return only the option number string, never the option label text such as "Yes", "No", or "N/A". -- Never return 0 for select list answers unless 0 exists as an explicit option number. -- For select list questions, choose the lowest option fully supported by the evidence; use a higher option only when the specific condition and required strength are directly supported. -- If evidence supports the existence of a topic but not the required strength, completeness, or specificity, choose the lowest option consistent with that evidence. -- If evidence is insufficient for a select list question, choose the lowest allowed answer value from question.allowed_answers and explain the uncertainty. -- Do not treat broad project descriptions, general goals, high-level timelines, budget presence, or a single indirect reference as sufficient evidence for a higher-scored select-list answer. -- For text and text area questions, answer must be concise, evidence-based, non-empty, and avoid boilerplate placeholders. -- For text and text area questions, answer is the reviewer comment, and rationale must explain the evidence basis and certainty for that comment. -- If no concerns are identified for a text or text area question, return a short non-empty evidence-based comment rather than leaving answer blank. -- For comment fields, summarize only the evidence-based conclusions supported by the scored answers, including uncertainty where applicable, and do not introduce stronger claims. -- For comment fields, describe the evidence and resulting answer without elevating it into an overall assessment unless the question explicitly asks for one. -- Do not add recommendations or stronger conclusions unless the question explicitly asks for them. -- For comment fields, do not leave answer empty even when all other answers are positive. -- Do not leave rationale empty when answer is populated. -- Final self-check before responding: every question ID in RESPONSE must have a non-empty "answer", non-empty "rationale", and "confidence". -- If any answer object is incomplete, regenerate the full JSON response before returning it. diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-scoring.system.txt b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-scoring.system.txt deleted file mode 100644 index aefebf06d5..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-scoring.system.txt +++ /dev/null @@ -1,11 +0,0 @@ -ROLE -You are a careful grant review assistant for human reviewers. Do not fill gaps, assume compliance, or treat relevance as proof. - -TASK -Using DATA, ATTACHMENTS, SECTION, RESPONSE, OUTPUT, and RULES: -1. Review each question in SECTION one at a time. -2. Identify the exact condition the question asks about. -3. Consider only the most relevant evidence in DATA and any provided ATTACHMENTS for that condition. -4. Choose the most conservative valid answer supported by that evidence. -5. If evidence is incomplete or indirect, explain the uncertainty in the rationale. -6. Repeat for every question in SECTION. diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-scoring.user.txt b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-scoring.user.txt deleted file mode 100644 index a8f64bc65f..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/application-scoring.user.txt +++ /dev/null @@ -1,15 +0,0 @@ -DATA -{{DATA}} - -ATTACHMENTS -{{ATTACHMENTS}} - -SECTION -{{SECTION}} - -RESPONSE -{{RESPONSE}} - -RULES -{{RULES}} -{{COMMON_RULES}} diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/attachment-summary.output.txt b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/attachment-summary.output.txt deleted file mode 100644 index b9d5a880a0..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/attachment-summary.output.txt +++ /dev/null @@ -1,3 +0,0 @@ -{ - "summary": "" -} diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/attachment-summary.rules.txt b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/attachment-summary.rules.txt deleted file mode 100644 index 2230e39228..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/attachment-summary.rules.txt +++ /dev/null @@ -1,13 +0,0 @@ -- Use only ATTACHMENT as evidence. -- Summarize actual content when ATTACHMENT.text is present; otherwise provide a conservative file-level summary. -- Describe the attachment itself rather than summarizing the overall project. -- Ensure the summary describes the attachment itself, not the overall project. -- If ATTACHMENT.text is primarily structured application, contact, organization, budget, or date fields, summarize it as a metadata-style attachment rather than rewriting it as a generic project summary. -- Begin with what the attachment contains or provides, not the file name or file type, unless that metadata is necessary to describe the evidence. -- Do not invent missing details. -- Do not calculate or restate totals, sums, or aggregates unless they are explicitly present in ATTACHMENT.text. -- Write reviewer-facing natural language. Do not refer to prompt section names, internal field keys, or schema labels such as ATTACHMENT or ATTACHMENT.text. -- Refer to evidence by its plain-language meaning, quoted text, or file name rather than internal key names. -- Write 1-2 complete sentences. -- Summary must be grounded in concrete ATTACHMENT evidence. -- Return exactly one object with only the key: summary. diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/attachment-summary.system.txt b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/attachment-summary.system.txt deleted file mode 100644 index 50f0d6a6f3..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/attachment-summary.system.txt +++ /dev/null @@ -1,8 +0,0 @@ -ROLE -You are a careful grant review assistant for human reviewers. Do not fill gaps, assume compliance, or treat relevance as proof. - -TASK -Using ATTACHMENT, OUTPUT, and RULES: -1. Review the attachment to identify what it contains. -2. Summarize the attachment itself, not the overall project. -3. Return a concise reviewer-facing summary. \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/attachment-summary.user.txt b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/attachment-summary.user.txt deleted file mode 100644 index 13cf1e6773..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/attachment-summary.user.txt +++ /dev/null @@ -1,9 +0,0 @@ -ATTACHMENTS -{{ATTACHMENTS}} - -RESPONSE -{{RESPONSE}} - -RULES -{{RULES}} -{{COMMON_RULES}} diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/common.rules.txt b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/common.rules.txt deleted file mode 100644 index 790f34fb73..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v1/common.rules.txt +++ /dev/null @@ -1,6 +0,0 @@ -- Any narrative text response must be at least 12 words. -- If ATTACHMENTS is empty, use DATA only and do not mention missing attachments unless their absence is material to the specific conclusion or question. -- Return values exactly as specified in OUTPUT. -- Do not return keys outside OUTPUT. -- Return valid JSON only. -- Return plain JSON only (no markdown). diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v2/form-worksheet.user.txt b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v2/form-worksheet.user.txt deleted file mode 100644 index 1cccb19f32..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v2/form-worksheet.user.txt +++ /dev/null @@ -1,37 +0,0 @@ -WORKSHEET CONTEXT: -{{DATA}} - -OUTPUT -{ - "Name": "", - "Title": "", - "Version": , - "Published": true, - "Sections": [ - { - "Name": "", - "Order": 1, - "Fields": [ - { - "Name": "", - "Key": "", - "Label": "", - "Type": , - "Definition": "" - } - ] - } - ], - "ReportColumns": "", - "ReportKeys": "", - "ReportViewName": "" -} - -Rules: -- Return one worksheet definition JSON object only. -- The context includes CHEFS fields, Unity core fields, and existing worksheet-derived custom fields. -- Use the provided form context to decide which custom fields are genuinely needed. -- Prefer existing Unity core fields when they already satisfy the need. -- Only create additional worksheet custom fields when the form genuinely needs them. -- Keep the worksheet structure valid for Flex. -- Return valid plain JSON only. diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v2/onboarding-mapping.system.txt b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v2/onboarding-mapping.system.txt deleted file mode 100644 index 3c791b606b..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v2/onboarding-mapping.system.txt +++ /dev/null @@ -1,4 +0,0 @@ -You are a careful mapping assistant for human reviewers. -Compare CHEFS fields, Unity core fields, and worksheet fields to suggest likely mappings. -Do not invent fields, persist changes, or assume a worksheet should exist if one is not clearly justified. -Return only valid JSON in the exact format requested. diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v2/onboarding-mapping.user.txt b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v2/onboarding-mapping.user.txt deleted file mode 100644 index 3237f3c5ca..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Runtime/Prompts/Versions/v2/onboarding-mapping.user.txt +++ /dev/null @@ -1,26 +0,0 @@ -FORM MAPPING CONTEXT: -{{DATA}} - -OUTPUT -{ - "": "", - "": "" -} - -Rules: -- Return one flat JSON object only. -- The context is grouped as `chefsData` and `unityData`. -- `chefsData.fields` contains the CHEFS source fields. -- `unityData.coreFields` contains Unity target fields. -- `unityData.customFields` contains worksheet-derived Unity target fields. -- Each property name must be a Unity core or worksheet-derived target field name from the provided context. -- Each property value must be the CHEFS source field name that best matches that Unity target field. -- Only include mappings that are clearly semantically equivalent or strongly related by label, name, type, and purpose. -- Do not force one-to-one coverage. Omit Unity fields when no CHEFS field is a sensible match. -- Omit CHEFS fields that do not clearly map to a Unity target field. -- Do not map platform/system identifiers such as SubmissionId, SubmissionDate, or ConfirmationId; they are managed by Unity and should be omitted if present. -- If no fields clearly match, return `{}`. -- The mapping is dynamic; do not hardcode or assume a fixed list of fields. -- Prefer existing Unity core intake fields when they already fit the source field. -- Only use worksheet custom field targets when the form genuinely needs them. -- Return valid plain JSON only. diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Unity.AI.Application.csproj b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Unity.AI.Application.csproj index 976f38baa3..e9f95639ec 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Unity.AI.Application.csproj +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Unity.AI.Application.csproj @@ -32,10 +32,4 @@ runtime; build; native; contentfiles; analyzers - - - PreserveNewest - PreserveNewest - - diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Unity.AI.Web.csproj b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Unity.AI.Web.csproj index 4b5d57bd20..dc792dbe61 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Unity.AI.Web.csproj +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Unity.AI.Web.csproj @@ -35,6 +35,7 @@ + diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/AIConfigurationViewComponent.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/AIConfigurationViewComponent.cs new file mode 100644 index 0000000000..a2de10dd7a --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/AIConfigurationViewComponent.cs @@ -0,0 +1,44 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Unity.AI.Settings; +using Unity.GrantManager.ApplicationForms; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; +using Volo.Abp.AspNetCore.Mvc.UI.Widgets; +using Volo.Abp.Settings; + +namespace Unity.AI.Web.Views.Shared.Components.AIConfiguration; + +[Widget( + ScriptTypes = new[] { typeof(AIConfigurationScriptBundleContributor) }, + AutoInitialize = true)] +public class AIConfigurationViewComponent( + IApplicationFormAppService applicationFormAppService, + ISettingProvider settingProvider) : AbpViewComponent +{ + public async Task InvokeAsync(Guid formId) + { + var applicationForm = await applicationFormAppService.GetAsync(formId); + + var model = new AIConfigurationViewModel + { + ApplicationFormId = formId, + ShowAutomatic = await settingProvider.GetAsync(AISettings.AutomaticGenerationEnabled, defaultValue: false), + ShowManual = await settingProvider.GetAsync(AISettings.ManualGenerationEnabled, defaultValue: false), + AutomaticallyGenerateAIAnalysis = applicationForm.AutomaticallyGenerateAIAnalysis, + ManuallyInitiateAIAnalysis = applicationForm.ManuallyInitiateAIAnalysis + }; + + return View(model); + } + + public class AIConfigurationScriptBundleContributor : BundleContributor + { + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files + .Add("/Views/Shared/Components/AIConfiguration/Default.js"); + } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/AIConfigurationViewModel.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/AIConfigurationViewModel.cs new file mode 100644 index 0000000000..03067f5d83 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/AIConfigurationViewModel.cs @@ -0,0 +1,16 @@ +using System; + +namespace Unity.AI.Web.Views.Shared.Components.AIConfiguration; + +public class AIConfigurationViewModel +{ + public Guid ApplicationFormId { get; set; } + + public bool ShowAutomatic { get; set; } + + public bool ShowManual { get; set; } + + public bool AutomaticallyGenerateAIAnalysis { get; set; } + + public bool ManuallyInitiateAIAnalysis { get; set; } +} diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/Default.cshtml b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/Default.cshtml new file mode 100644 index 0000000000..82acc15baa --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/Default.cshtml @@ -0,0 +1,71 @@ +@using Unity.AI.Web.Views.Shared.Components.AIConfiguration +@model AIConfigurationViewModel + +@{ + Layout = null; +} + + + +
+
+
+
AI Configuration
+
+
+ + + +
+
+
+
+ @if (Model.ShowAutomatic) + { +
+ + +
+ When enabled, all AI analysis features run automatically when a new application is submitted through this form. + Requires tenant-level Automatic AI Generation to also be enabled. +
+
+ } + + @if (Model.ShowManual) + { +
+ + +
+ When enabled, users with appropriate permissions can manually generate or regenerate AI analysis for applications submitted through this form. + Includes: AI Attachment Summary, Application Analysis, AI Scoring. +
+
+ } +
+
+
diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/Default.js b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/Default.js new file mode 100644 index 0000000000..8527f3422b --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/Default.js @@ -0,0 +1,61 @@ +$(function () { + const UIElements = { + btnSave: $('#btn-save-ai-config'), + btnCancel: $('#btn-cancel-ai-config'), + btnBack: $('#btn-back-ai-config'), + formId: $('#aiConfigFormId'), + automaticCheckbox: $('#AutomaticallyGenerateAIAnalysis'), + manualCheckbox: $('#ManuallyInitiateAIAnalysis') + }; + + let lastSavedAIValues = { + automaticallyGenerateAIAnalysis: UIElements.automaticCheckbox.is(':checked'), + manuallyInitiateAIAnalysis: UIElements.manualCheckbox.is(':checked') + }; + + init(); + + function init() { + bindUIEvents(); + } + + function bindUIEvents() { + UIElements.btnSave.on('click', handleSave); + UIElements.btnCancel.on('click', handleCancel); + UIElements.btnBack.on('click', function () { + location.href = '/ApplicationForms'; + }); + } + + function handleSave() { + UIElements.btnSave.prop('disabled', true); + + abp.ajax({ + url: `/api/app/application-form/${UIElements.formId.val()}/ai-config`, + type: 'PATCH', + data: JSON.stringify({ + automaticallyGenerateAIAnalysis: UIElements.automaticCheckbox.is(':checked'), + manuallyInitiateAIAnalysis: UIElements.manualCheckbox.is(':checked') + }), + contentType: 'application/json' + }) + .done(function () { + lastSavedAIValues = { + automaticallyGenerateAIAnalysis: UIElements.automaticCheckbox.is(':checked'), + manuallyInitiateAIAnalysis: UIElements.manualCheckbox.is(':checked') + }; + abp.notify.success('AI configuration saved successfully.'); + }) + .fail(function () { + abp.notify.error('Failed to save AI configuration.'); + }) + .always(function () { + UIElements.btnSave.prop('disabled', false); + }); + } + + function handleCancel() { + UIElements.automaticCheckbox.prop('checked', lastSavedAIValues.automaticallyGenerateAIAnalysis); + UIElements.manualCheckbox.prop('checked', lastSavedAIValues.manuallyInitiateAIAnalysis); + } +}); diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application.Contracts/Reporting/Configuration/WorksheetComponentMetaDataDto.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application.Contracts/Reporting/Configuration/WorksheetComponentMetaDataDto.cs index f8dfbd7206..274cca8159 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application.Contracts/Reporting/Configuration/WorksheetComponentMetaDataDto.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application.Contracts/Reporting/Configuration/WorksheetComponentMetaDataDto.cs @@ -46,5 +46,11 @@ public class WorksheetComponentMetaDataItemDto /// The path to reach the data, this is a datacentric version of the Path, and could be the same /// public string DataPath { get; set; } = string.Empty; + + /// + /// The name of the worksheet this component belongs to (includes the version suffix, e.g. "grant_application-v2"). + /// Used to group and default-sort reporting configuration fields by worksheet. + /// + public string WorksheetName { get; set; } = string.Empty; } } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Domain/Services/WorksheetsManager.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Domain/Services/WorksheetsManager.cs index 9fb9ec6ee4..307d7801fa 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Domain/Services/WorksheetsManager.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Domain/Services/WorksheetsManager.cs @@ -82,9 +82,16 @@ public async Task UpdateWorksheetInstanceValueAsync(WorksheetInstance instance) foreach (var field in instance.Values) { var fieldDefinition = fieldDefinitions.Find(s => s.Id == field.CustomFieldId); - if (fieldDefinition != null) - instanceCurrentValue.Values.Add(new FieldInstanceValue(fieldDefinition.Key, - JsonNode.Parse(field.CurrentValue)?["value"]?.ToString() ?? string.Empty)); + if (fieldDefinition == null) continue; + + // A missing key and an explicit JSON null both parse to a null JsonNode here; + // omit the entry rather than coercing it to "" so downstream reporting views + // (which treat a missing key as NULL) don't have to parse "" as JSON. + var value = JsonNode.Parse(field.CurrentValue)?["value"]; + if (value != null) + { + instanceCurrentValue.Values.Add(new FieldInstanceValue(fieldDefinition.Key, value.ToString())); + } } instance.SetValue(JsonSerializer.Serialize(instanceCurrentValue)); diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Domain/Services/WorksheetsManagerExtensions.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Domain/Services/WorksheetsManagerExtensions.cs index 2733995c25..0181a34098 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Domain/Services/WorksheetsManagerExtensions.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Domain/Services/WorksheetsManagerExtensions.cs @@ -38,11 +38,14 @@ public static List GroupAndTransformFieldSets(this List s.FieldId) .ToList(); - if (groups.Count == valueFields.Count) return valueFields; // no grouping required - var list = new List(); - // collect the value fields if known and store and transform to required values + // Always route through JoinFieldValues, even for a single posted value. A checkbox-group + // field with exactly one box checked still needs JSON-array encoding (JoinFieldValues + // dispatches to ConvertCheckboxGroupMultiValues for that type); skipping it here previously + // left the raw single checkbox value (e.g. "true") stored instead of a JSON array, which + // the reporting views can't jsonb_array_elements() over. JoinFieldValues already returns the + // raw single value unchanged for every other field type. foreach (var group in groups) { var fieldId = group.First().FieldId; @@ -56,11 +59,7 @@ public static List GroupAndTransformFieldSets(this List 1 ? - group.Select(s => s.Value) - .ToList() - .JoinFieldValues(fieldId, worksheet, additionalIdentifiers) - : values[0] + Value = values.JoinFieldValues(fieldId, worksheet, additionalIdentifiers) }); } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Reporting/Configuration/WorksheetFieldSchemaParser.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Reporting/Configuration/WorksheetFieldSchemaParser.cs index 011b97353d..2bcbbde2d9 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Reporting/Configuration/WorksheetFieldSchemaParser.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Reporting/Configuration/WorksheetFieldSchemaParser.cs @@ -83,8 +83,9 @@ public static List ParseWorksheet(Worksheet w return [..worksheet.Sections .Where(section => section.Fields != null) - .SelectMany(section => section.Fields) - .SelectMany(field => ParseField(field, worksheet, formSchema, submissionHeaderMapping))]; + .OrderBy(section => section.Order) + .SelectMany(section => section.Fields.OrderBy(field => field.Order)) + .SelectMany(field => ParseField(field, worksheet, formSchema, submissionHeaderMapping))]; } /// @@ -134,7 +135,8 @@ private static List ParseDataGridField(Custom if (dynamicColumns != null && dynamicColumns.Count > 0) { - // We found dynamic columns in the CHEFS form schema; emit them first. + // We found dynamic columns in the CHEFS form schema; emit them first, in the + // order CHEFS returns them (which mirrors the form builder's column order). // Any statically-defined columns on the DataGrid are merged in below. foreach (var column in dynamicColumns) { @@ -150,9 +152,10 @@ private static List ParseDataGridField(Custom Type = MapDataGridColumnType(column.Type), Path = $"{worksheetName}->{sectionName}->{dataGridName}->{columnKey}", TypePath = $"worksheet->section->datagrid->{MapDataGridColumnType(column.Type)}", - DataPath = $"({worksheetName}){dataGridName}->{columnKey}" + DataPath = $"({worksheetName}){dataGridName}->{columnKey}", + WorksheetName = worksheet.Name }; - + components.Add(component); } } @@ -167,7 +170,8 @@ private static List ParseDataGridField(Custom Type = "Dynamic", Path = $"{worksheetName}->{sectionName}->{dataGridName}->dynamic_columns", TypePath = $"worksheet->section->datagrid->Dynamic", - DataPath = $"({worksheetName}){dataGridName}->dynamic_columns" + DataPath = $"({worksheetName}){dataGridName}->dynamic_columns", + WorksheetName = worksheet.Name }; components.Add(dynamicComponent); @@ -183,6 +187,10 @@ private static List ParseDataGridField(Custom components.Select(c => c.Key ?? string.Empty), StringComparer.OrdinalIgnoreCase); + // DataGridDefinition.Columns is deserialized straight from the JSON array, so list + // order == author-defined column order. Combined with the dynamic columns above + // (which are always emitted first), this becomes the tie-break row order when the + // field expands into multiple rows. foreach (var column in dataGridDefinition.Columns) { // Skip columns that were already emitted from the CHEFS extraction @@ -201,7 +209,8 @@ private static List ParseDataGridField(Custom Type = MapDataGridColumnType(column.Type), Path = $"{worksheetName}->{sectionName}->{dataGridName}->{column.Name}", TypePath = $"worksheet->section->datagrid->{MapDataGridColumnType(column.Type)}", - DataPath = $"({worksheetName}){dataGridName}->{column.Name}" + DataPath = $"({worksheetName}){dataGridName}->{column.Name}", + WorksheetName = worksheet.Name }; components.Add(component); @@ -275,7 +284,10 @@ private static List ParseCheckboxGroupField(C var worksheetName = SanitizeName(worksheet.Name); var checkboxGroupName = SanitizeName(field.Key); - // Create a component for each option in the CheckboxGroup + // Create a component for each option in the CheckboxGroup, in the order the options + // are defined (CheckboxGroupDefinition.Options is deserialized straight from the JSON + // array, so list order == author-defined display order). Downstream sorting is stable, + // so this becomes the tie-break row order when the field expands into multiple rows. foreach (var option in checkboxGroupDefinition.Options) { var optionKey = SanitizeName(option.Key); @@ -288,7 +300,8 @@ private static List ParseCheckboxGroupField(C Type = "Checkbox", // Each option is essentially a checkbox Path = $"{worksheetName}->{sectionName}->{checkboxGroupName}->{option.Key}", TypePath = $"worksheet->section->checkboxgroup->Checkbox", - DataPath = $"({worksheetName}){checkboxGroupName}->{option.Key}" + DataPath = $"({worksheetName}){checkboxGroupName}->{option.Key}", + WorksheetName = worksheet.Name }; components.Add(component); @@ -355,7 +368,8 @@ private static WorksheetComponentMetaDataItemDto CreateSimpleComponent(CustomFie Type = field.Type.ToString(), Path = $"{worksheetName}->{sectionName}->{fieldName}", TypePath = $"worksheet->section->{field.Type.ToString().ToLowerInvariant()}", - DataPath = $"({worksheetName}){fieldName}" + DataPath = $"({worksheetName}){fieldName}", + WorksheetName = worksheet.Name }; } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/ValueConverter.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/ValueConverter.cs index 21356fec6f..e1ffa4c602 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/ValueConverter.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/ValueConverter.cs @@ -22,7 +22,7 @@ public static string Convert(string currentValue, CustomFieldType type, string? CustomFieldType.Email => JsonSerializer.Serialize(new EmailValue(currentValue)), CustomFieldType.Radio => JsonSerializer.Serialize(new RadioValue(currentValue)), CustomFieldType.Checkbox => JsonSerializer.Serialize(new CheckboxValue(ValueConverterHelpers.ConvertCheckbox(currentValue))), - CustomFieldType.CheckboxGroup => JsonSerializer.Serialize(new CheckboxGroupValue(currentValue)), + CustomFieldType.CheckboxGroup => JsonSerializer.Serialize(new CheckboxGroupValue(string.IsNullOrWhiteSpace(currentValue) ? "[]" : currentValue)), CustomFieldType.SelectList => JsonSerializer.Serialize(new SelectListValue(currentValue)), CustomFieldType.BCAddress => JsonSerializer.Serialize(new BCAddressValue(currentValue)), CustomFieldType.TextArea => JsonSerializer.Serialize(new TextAreaValue(currentValue)), diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.Application.Tests/Reporting/WorksheetFieldSchemaParserTests.cs b/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.Application.Tests/Reporting/WorksheetFieldSchemaParserTests.cs index c6a31f58d4..de01939db3 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.Application.Tests/Reporting/WorksheetFieldSchemaParserTests.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.Application.Tests/Reporting/WorksheetFieldSchemaParserTests.cs @@ -544,5 +544,220 @@ public async Task ParseDataGridField_DynamicWithFormSchemaKeyMismatch_ShouldFall result.ShouldNotBeNull(); result.ShouldContain(c => c.Key == "dynamic_columns"); } + + [Fact] + public async Task ParseDataGridField_WithMultipleStaticColumns_ShouldPreserveDefinitionOrder() + { + // Arrange + using var uow = _unitOfWorkManager.Begin(); + + var worksheet = new Worksheet(Guid.NewGuid(), "TestWorksheet", "Test Worksheet"); + var section = new WorksheetSection(Guid.NewGuid(), "TestSection"); + worksheet.Sections.Add(section); + + await _worksheetRepository.InsertAsync(worksheet, true); + await uow.SaveChangesAsync(); + + // Columns defined in a deliberately non-alphabetical order + var field = new CustomField(Guid.NewGuid(), "testDataGrid", "TestWorksheet", "Test DataGrid", + CustomFieldType.DataGrid, + @"{""dynamic"": false, ""columns"": [ + {""name"": ""zebra"", ""type"": ""Text""}, + {""name"": ""apple"", ""type"": ""Text""}, + {""name"": ""mango"", ""type"": ""Text""} + ], ""summaryOption"": ""None""}"); + section.AddField(field); + await uow.SaveChangesAsync(); + + worksheet = await _worksheetRepository.GetAsync(worksheet.Id); + + // Act + var result = WorksheetFieldSchemaParser.ParseField(field, worksheet); + + // Assert — rows must appear in the order the columns are defined on the grid, not sorted + result.ShouldNotBeNull(); + result.Select(c => c.Key).ShouldBe(["zebra", "apple", "mango"]); + } + + [Fact] + public async Task ParseDataGridField_WithDynamicAndStaticColumns_ShouldEmitDynamicColumnsBeforeStaticColumns() + { + // Arrange + using var uow = _unitOfWorkManager.Begin(); + + var worksheet = new Worksheet(Guid.NewGuid(), "TestWorksheet", "Test Worksheet"); + var section = new WorksheetSection(Guid.NewGuid(), "TestSection"); + worksheet.Sections.Add(section); + + await _worksheetRepository.InsertAsync(worksheet, true); + await uow.SaveChangesAsync(); + + var field = new CustomField(Guid.NewGuid(), "testDataGrid", "TestWorksheet", "Test DataGrid", + CustomFieldType.DataGrid, + @"{""dynamic"": true, ""columns"": [ + {""name"": ""staticB"", ""type"": ""Text""}, + {""name"": ""staticA"", ""type"": ""Text""} + ], ""summaryOption"": ""None""}"); + section.AddField(field); + await uow.SaveChangesAsync(); + + worksheet = await _worksheetRepository.GetAsync(worksheet.Id); + + var submissionHeaderMapping = $@"{{""{field.Name}.DataGrid"": ""chefsGrid""}}"; + + var formSchema = @"{ + ""components"": [ + { + ""key"": ""chefsGrid"", + ""type"": ""datagrid"", + ""components"": [ + { ""key"": ""dynamicB"", ""label"": ""Dynamic B"", ""type"": ""textfield"" }, + { ""key"": ""dynamicA"", ""label"": ""Dynamic A"", ""type"": ""textfield"" } + ] + } + ] + }"; + + // Act + var result = WorksheetFieldSchemaParser.ParseField(field, worksheet, formSchema, submissionHeaderMapping); + + // Assert — CHEFS-extracted dynamic columns keep their own order and come first, followed by + // the statically-defined columns (not covered by the dynamic extraction) in their own order + result.ShouldNotBeNull(); + result.Select(c => c.Key).ShouldBe(["dynamicB", "dynamicA", "staticB", "staticA"]); + } + + [Fact] + public async Task ParseCheckboxGroupField_WithMultipleOptions_ShouldPreserveDefinitionOrder() + { + // Arrange + using var uow = _unitOfWorkManager.Begin(); + + var worksheet = new Worksheet(Guid.NewGuid(), "TestWorksheet", "Test Worksheet"); + var section = new WorksheetSection(Guid.NewGuid(), "TestSection"); + worksheet.Sections.Add(section); + + await _worksheetRepository.InsertAsync(worksheet, true); + await uow.SaveChangesAsync(); + + // Options defined in a deliberately non-alphabetical order + var field = new CustomField(Guid.NewGuid(), "testCheckboxGroup", "TestWorksheet", "Test Checkbox Group", + CustomFieldType.CheckboxGroup, + @"{""options"": [ + {""key"": ""rural"", ""value"": false, ""label"": ""Rural""}, + {""key"": ""urban"", ""value"": false, ""label"": ""Urban""}, + {""key"": ""remote"", ""value"": false, ""label"": ""Remote""} + ]}"); + section.AddField(field); + await uow.SaveChangesAsync(); + + worksheet = await _worksheetRepository.GetAsync(worksheet.Id); + + // Act + var result = WorksheetFieldSchemaParser.ParseField(field, worksheet); + + // Assert — rows must appear in the order the options are defined on the field, not sorted + result.ShouldNotBeNull(); + result.Select(c => c.Key).ShouldBe(["rural", "urban", "remote"]); + } + + [Fact] + public async Task ParseCheckboxGroupField_WithNoOptions_ShouldReturnSimpleComponent() + { + // Arrange + using var uow = _unitOfWorkManager.Begin(); + + var worksheet = new Worksheet(Guid.NewGuid(), "TestWorksheet", "Test Worksheet"); + var section = new WorksheetSection(Guid.NewGuid(), "TestSection"); + worksheet.Sections.Add(section); + + await _worksheetRepository.InsertAsync(worksheet, true); + await uow.SaveChangesAsync(); + + var field = new CustomField(Guid.NewGuid(), "testCheckboxGroup", "TestWorksheet", "Test Checkbox Group", + CustomFieldType.CheckboxGroup, + @"{""options"": []}"); + section.AddField(field); + await uow.SaveChangesAsync(); + + worksheet = await _worksheetRepository.GetAsync(worksheet.Id); + + // Act + var result = WorksheetFieldSchemaParser.ParseField(field, worksheet); + + // Assert + result.ShouldNotBeNull(); + result.Count.ShouldBe(1); + + var component = result.First(); + component.Id.ShouldBe(field.Id.ToString()); + component.Key.ShouldBe("testCheckboxGroup"); + component.Type.ShouldBe("CheckboxGroup"); + } + + [Fact] + public async Task ParseWorksheet_WithFieldsOutOfInsertionOrder_ShouldEmitFieldsByOrderProperty() + { + // Arrange + using var uow = _unitOfWorkManager.Begin(); + + var worksheet = new Worksheet(Guid.NewGuid(), "TestWorksheet", "Test Worksheet"); + var section = new WorksheetSection(Guid.NewGuid(), "TestSection"); + worksheet.Sections.Add(section); + + await _worksheetRepository.InsertAsync(worksheet, true); + await uow.SaveChangesAsync(); + + // Added to the section in A, B, C order, but Order is deliberately assigned out of sequence + // (B=1, C=2, A=3) so the test fails if ParseWorksheet ever falls back to collection/insertion + // order instead of sorting by the field's Order property. + section.Fields.Add(new CustomField(Guid.NewGuid(), "fieldA", "TestWorksheet", "Field A", CustomFieldType.Text, (string?)null).SetOrder(3)); + section.Fields.Add(new CustomField(Guid.NewGuid(), "fieldB", "TestWorksheet", "Field B", CustomFieldType.Text, (string?)null).SetOrder(1)); + section.Fields.Add(new CustomField(Guid.NewGuid(), "fieldC", "TestWorksheet", "Field C", CustomFieldType.Text, (string?)null).SetOrder(2)); + await uow.SaveChangesAsync(); + + worksheet = await _worksheetRepository.GetAsync(worksheet.Id); + + // Act + var result = WorksheetFieldSchemaParser.ParseWorksheet(worksheet); + + // Assert — fields must appear in Order order (B, C, A), not the order they were added + result.ShouldNotBeNull(); + result.Select(c => c.Key).ShouldBe(["fieldB", "fieldC", "fieldA"]); + } + + [Fact] + public async Task ParseWorksheet_WithSectionsOutOfInsertionOrder_ShouldEmitSectionsByOrderProperty() + { + // Arrange + using var uow = _unitOfWorkManager.Begin(); + + var worksheet = new Worksheet(Guid.NewGuid(), "TestWorksheet", "Test Worksheet"); + + var sectionA = new WorksheetSection(Guid.NewGuid(), "SectionA"); + var sectionB = new WorksheetSection(Guid.NewGuid(), "SectionB"); + + // Added to the worksheet in A, B order, but Order is deliberately reversed (A=2, B=1) so the + // test fails if ParseWorksheet ever falls back to collection/insertion order instead of + // sorting sections by their Order property. + worksheet.Sections.Add(sectionA.SetOrder(2)); + worksheet.Sections.Add(sectionB.SetOrder(1)); + + sectionA.Fields.Add(new CustomField(Guid.NewGuid(), "fieldInA", "TestWorksheet", "Field In A", CustomFieldType.Text, (string?)null).SetOrder(1)); + sectionB.Fields.Add(new CustomField(Guid.NewGuid(), "fieldInB", "TestWorksheet", "Field In B", CustomFieldType.Text, (string?)null).SetOrder(1)); + + await _worksheetRepository.InsertAsync(worksheet, true); + await uow.SaveChangesAsync(); + + worksheet = await _worksheetRepository.GetAsync(worksheet.Id); + + // Act + var result = WorksheetFieldSchemaParser.ParseWorksheet(worksheet); + + // Assert — Section B's field (Order 1) must come before Section A's field (Order 2), even + // though Section A was added to the worksheet first + result.ShouldNotBeNull(); + result.Select(c => c.Key).ShouldBe(["fieldInB", "fieldInA"]); + } } } \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Integrations/Cas/SupplierService.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Integrations/Cas/SupplierService.cs index d9e7a06529..d70f466e54 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Integrations/Cas/SupplierService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Integrations/Cas/SupplierService.cs @@ -27,7 +27,7 @@ public class SupplierService : ApplicationService, ISupplierService { protected new ILogger Logger => LazyServiceProvider.LazyGetService(provider => LoggerFactory?.CreateLogger(GetType().FullName!) ?? NullLogger.Instance); private const string CFS_SUPPLIER = "cfs/supplier"; - private readonly Task casBaseApiTask; + private readonly Lazy> casBaseApiTask; private readonly ILocalEventBus localEventBus; private readonly IResilientHttpRequest resilientHttpRequest; private readonly ICasTokenService iTokenService; @@ -42,8 +42,8 @@ public SupplierService(ILocalEventBus localEventBus, this.resilientHttpRequest = resilientHttpRequest; this.iTokenService = iTokenService; - // Initialize the base API URL once during construction - casBaseApiTask = InitializeBaseApiAsync(endpointManagementAppService); + // Defer the database-backed lookup until the service is actually used. + casBaseApiTask = new(() => InitializeBaseApiAsync(endpointManagementAppService)); } private static async Task InitializeBaseApiAsync(IEndpointManagementAppService endpointManagementAppService) @@ -218,7 +218,7 @@ public async Task GetCasSupplierInformationAsync(string? supplierNumber { if (!string.IsNullOrEmpty(supplierNumber)) { - var casBaseApi = await casBaseApiTask; + var casBaseApi = await casBaseApiTask.Value; var resource = $"{casBaseApi}/{CFS_SUPPLIER}/{supplierNumber}"; return await GetCasSupplierInformationByResourceAsync(resource); } @@ -232,7 +232,7 @@ public async Task GetCasSupplierInformationByBn9Async(string? bn9) { if (!string.IsNullOrEmpty(bn9)) { - var casBaseApi = await casBaseApiTask; + var casBaseApi = await casBaseApiTask.Value; var resource = $"{casBaseApi}/{CFS_SUPPLIER}/{bn9}/businessnumber"; return await GetCasSupplierInformationByResourceAsync(resource); } diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application.Contracts/Configuration/FieldPathTypeDto.cs b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application.Contracts/Configuration/FieldPathTypeDto.cs index e50335bf1c..ddd73d90d1 100644 --- a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application.Contracts/Configuration/FieldPathTypeDto.cs +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application.Contracts/Configuration/FieldPathTypeDto.cs @@ -47,5 +47,18 @@ public class FieldPathTypeDto /// A comma-separated value (e.g., "v1, v2") means the field appears in those versions but not all. /// public string? VersionLabel { get; set; } = null; + + /// + /// The name of the worksheet this field belongs to (includes the version suffix, e.g. "grant_application-v2"). + /// Populated only by the worksheet-based providers; null for scoresheet/formversion providers. + /// + public string? WorksheetName { get; set; } = null; + + /// + /// The field's 1-based position in the provider's calculated overall field order (e.g., for worksheet + /// providers: Worksheet Name A-Z, then section order, then field layout order, then checkbox group + /// option / data grid column order). Assigned by the provider after all ordering is applied. + /// + public int SourceOrder { get; set; } } } diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application.Contracts/Configuration/ReportColumnsMapDto.cs b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application.Contracts/Configuration/ReportColumnsMapDto.cs index 6583e3cf69..93d44c2600 100644 --- a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application.Contracts/Configuration/ReportColumnsMapDto.cs +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application.Contracts/Configuration/ReportColumnsMapDto.cs @@ -140,5 +140,17 @@ public class MapRowDto /// a non-null value (e.g., "v1", "v2") means the column is specific to that form version. /// public string? VersionLabel { get; set; } = null; + + /// + /// Gets or sets the name of the worksheet this field belongs to (includes the version suffix, e.g. "grant_application-v2"). + /// Populated only for worksheet-based providers; null for scoresheet/formversion providers. + /// + public string? WorksheetName { get; set; } = null; + + /// + /// Gets or sets the field's 1-based position in the provider's calculated overall field order. + /// Drives the report-config-table's default sort (Source Order 1 -> n); not shown by default. + /// + public int SourceOrder { get; set; } } } diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/ConsolidatedFormVersionFieldsProvider.cs b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/ConsolidatedFormVersionFieldsProvider.cs index 72bad667f8..c9d2c578cc 100644 --- a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/ConsolidatedFormVersionFieldsProvider.cs +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/ConsolidatedFormVersionFieldsProvider.cs @@ -54,6 +54,11 @@ public async Task GetFieldsMetadataAsync(Guid correlationId } var mergedFields = MergeFields(versionsWithFields); + + // Stamp each field with its 1-based position in the merged order as SourceOrder, which drives + // the report-config-table's default sort. + FieldOrderingUtils.AssignSourceOrder(mergedFields); + var mapMetadata = new MapMetadataDto { Info = metadataInfo }; return new FieldPathMetaMapDto { Fields = [.. mergedFields], Metadata = mapMetadata }; diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/ConsolidatedWorksheetFieldsProvider.cs b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/ConsolidatedWorksheetFieldsProvider.cs index bb8d055188..80cd9df5c8 100644 --- a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/ConsolidatedWorksheetFieldsProvider.cs +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/ConsolidatedWorksheetFieldsProvider.cs @@ -61,16 +61,33 @@ public async Task GetFieldsMetadataAsync(Guid correlationId metadataInfo[$"ws_{version.Id}_{link.WorksheetId}"] = $"{worksheetTitle} ({worksheetName})"; } + // Default sort order within a version: Worksheet Name (A-Z), then section order, then field + // layout order within each section. Each worksheet's components are already emitted in + // section/field order by WorksheetFieldSchemaParser.ParseWorksheet, so a stable sort on + // WorksheetName alone preserves that ordering as the tie-breaker. + var orderedComponents = allComponents + .OrderBy(f => f.WorksheetName, StringComparer.OrdinalIgnoreCase) + .ToArray(); + // Stamp within-version duplicate DataPaths with (DK1), (DK2), … before merging, // so MergeFields() treats them as distinct paths and preserves both rather than // silently dropping the second occurrence. - var versionComponents = allComponents.ToArray(); + var versionComponents = orderedComponents; WorksheetFieldsUtils.UniqueifyDataPaths(versionComponents); versionsWithFields.Add((version.Id, versionLabel, versionComponents)); metadataInfo[$"formversion_{version.Id}"] = versionLabel; } - var mergedFields = MergeFields(versionsWithFields); + // Re-apply the Worksheet Name ordering across the merged result (stable sort preserves the + // section/field tie-break order already established per-version above). + var mergedFields = MergeFields(versionsWithFields) + .OrderBy(f => f.WorksheetName, StringComparer.OrdinalIgnoreCase) + .ToList(); + + // Stamp each field with its 1-based position in the final order as SourceOrder, which drives + // the report-config-table's default sort. + FieldOrderingUtils.AssignSourceOrder(mergedFields); + var mapMetadata = new MapMetadataDto { Info = metadataInfo }; return new FieldPathMetaMapDto { Fields = [.. mergedFields], Metadata = mapMetadata }; @@ -122,7 +139,8 @@ public async Task GetFieldsMetadataAsync(Guid correlationId Key = item.Key, Label = item.Label, TypePath = item.TypePath, - DataPath = item.DataPath + DataPath = item.DataPath, + WorksheetName = item.WorksheetName }; } @@ -197,7 +215,8 @@ private static List MergeFields( Type = field.Type ?? string.Empty, TypePath = field.TypePath, DataPath = field.DataPath, - VersionLabel = string.Join(", ", exactGroup.Select(e => e.VersionLabel)) + VersionLabel = string.Join(", ", exactGroup.Select(e => e.VersionLabel)), + WorksheetName = field.WorksheetName }); } else if (versionsWithFields.Count > 1 && versionsHavingThisExact.Count == versionsWithFields.Count) @@ -212,7 +231,8 @@ private static List MergeFields( Type = field.Type ?? string.Empty, TypePath = field.TypePath, DataPath = field.DataPath, - VersionLabel = null + VersionLabel = null, + WorksheetName = field.WorksheetName }); } else @@ -227,7 +247,8 @@ private static List MergeFields( Type = field.Type ?? string.Empty, TypePath = field.TypePath, DataPath = field.DataPath, - VersionLabel = string.Join(", ", exactGroup.Select(e => e.VersionLabel)) + VersionLabel = string.Join(", ", exactGroup.Select(e => e.VersionLabel)), + WorksheetName = field.WorksheetName }); } } diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/FieldOrderingUtils.cs b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/FieldOrderingUtils.cs new file mode 100644 index 0000000000..cb58dac675 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/FieldOrderingUtils.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using Unity.Reporting.Domain.Configuration; + +namespace Unity.Reporting.Configuration.FieldsProviders +{ + /// + /// Shared utility for stamping fields with their calculated overall Source Order. + /// + internal static class FieldOrderingUtils + { + /// + /// Stamps each field with its 1-based position in the list as . + /// Must be called after a provider has applied its final field ordering (e.g., worksheet name, + /// section, field layout, checkbox option, or data grid column order), since this simply reflects + /// list position. Mutates the fields in place. + /// + internal static void AssignSourceOrder(IReadOnlyList fields) + { + for (var i = 0; i < fields.Count; i++) + { + fields[i].SourceOrder = i + 1; + } + } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/FormVersionFieldsProvider.cs b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/FormVersionFieldsProvider.cs index 8b2e3ebd6d..58a01cda76 100644 --- a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/FormVersionFieldsProvider.cs +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/FormVersionFieldsProvider.cs @@ -37,6 +37,10 @@ public async Task GetFieldsMetadataAsync(Guid correlationId .Where(x => x != null) .Select(x => x!)]; + // Stamp each field with its 1-based position in the source order as SourceOrder, which drives + // the report-config-table's default sort. + FieldOrderingUtils.AssignSourceOrder(convertedMetadata); + return new FieldPathMetaMapDto() { Fields = convertedMetadata }; } diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/ScoresheetFieldsProvider.cs b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/ScoresheetFieldsProvider.cs index 7372d461f9..a8af6b276b 100644 --- a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/ScoresheetFieldsProvider.cs +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/ScoresheetFieldsProvider.cs @@ -52,7 +52,11 @@ public async Task GetFieldsMetadataAsync(Guid correlationId .Select(ConvertToFieldPathType) .Where(x => x != null) .Select(x => x!)]; - + + // Stamp each field with its 1-based position in the source order as SourceOrder, which drives + // the report-config-table's default sort. + FieldOrderingUtils.AssignSourceOrder(convertedMetadata); + // Create metadata information about the scoresheet used var mapMetadata = new MapMetadataDto(); var scoresheetKey = $"scoresheet_{scoresheetId.Value}"; diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/WorksheetFieldsProvider.cs b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/WorksheetFieldsProvider.cs index 4e984e35a7..7c72d16014 100644 --- a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/WorksheetFieldsProvider.cs +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/WorksheetFieldsProvider.cs @@ -46,15 +46,24 @@ public async Task GetFieldsMetadataAsync(Guid correlationId mapMetadata.Info[worksheetKey] = $"{worksheetTitle} ({worksheetName}) - ID: {link.WorksheetId}"; } + // Default sort order: Worksheet Name (A-Z), then section order, then field layout order within + // each section. Each worksheet's components are already emitted in section/field order by + // WorksheetFieldSchemaParser.ParseWorksheet, so a stable sort on WorksheetName alone preserves + // that ordering as the tie-breaker. FieldPathTypeDto[] convertedMetadata = [.. worksheetMetadata.SelectMany(s => s.Components) .Select(ConvertToFieldPathType) .Where(x => x != null) - .Select(x => x!)]; + .Select(x => x!) + .OrderBy(x => x.WorksheetName, StringComparer.OrdinalIgnoreCase)]; // Mirror submission behaviour: stamp within-version duplicate DataPaths with (DK1), (DK2), … // so that each row is distinguishable and the Duplicate Keys warning is triggered. WorksheetFieldsUtils.UniqueifyDataPaths(convertedMetadata); + // Stamp each field with its 1-based position in the final order as SourceOrder, which drives + // the report-config-table's default sort. + FieldOrderingUtils.AssignSourceOrder(convertedMetadata); + return new FieldPathMetaMapDto() { Fields = convertedMetadata, Metadata = mapMetadata }; } @@ -76,7 +85,8 @@ public async Task GetFieldsMetadataAsync(Guid correlationId Key = metadataItem.Key, Label = metadataItem.Label, TypePath = metadataItem.TypePath, - DataPath = metadataItem.DataPath + DataPath = metadataItem.DataPath, + WorksheetName = metadataItem.WorksheetName }; } diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/ReportMappingService.cs b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/ReportMappingService.cs index e5a8f62729..0b4675b16e 100644 --- a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/ReportMappingService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/ReportMappingService.cs @@ -136,6 +136,48 @@ public async Task GetByCorrelationAsync(Guid correlationId, ? throw new EntityNotFoundException(typeof(ReportColumnsMap), $"CorrelationId: {correlationId}, CorrelationProvider: {correlationProvider}") : ObjectMapper.Map(reportColumnsMap); + // Mappings saved before the Worksheet Name / Source Order columns existed have neither value in + // their persisted JSON rows, so those columns would otherwise render blank/zero for a saved view + // even though a brand-new (unsaved) load of the same source shows them correctly. Backfill both + // from the live provider metadata by matching on Path, without requiring the user to re-save + // the configuration. SourceOrder of 0 is otherwise never assigned (providers always number from 1), + // so it doubles as the "missing" sentinel here. + var needsWorksheetNameBackfill = (providerKey == Providers.Worksheet || providerKey == Providers.WorksheetConsolidated) && + map.Mapping.Rows.Any(row => string.IsNullOrEmpty(row.WorksheetName)); + var needsSourceOrderBackfill = map.Mapping.Rows.Any(row => row.SourceOrder == 0); + + if (needsWorksheetNameBackfill || needsSourceOrderBackfill) + { + var liveFields = await provider.GetFieldsMetadataAsync(correlationId); + var worksheetNameByPath = new Dictionary(StringComparer.OrdinalIgnoreCase); + var sourceOrderByPath = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var field in liveFields.Fields) + { + if (!string.IsNullOrEmpty(field.WorksheetName) && !worksheetNameByPath.ContainsKey(field.Path)) + { + worksheetNameByPath[field.Path] = field.WorksheetName; + } + if (!string.IsNullOrEmpty(field.Path) && !sourceOrderByPath.ContainsKey(field.Path)) + { + sourceOrderByPath[field.Path] = field.SourceOrder; + } + } + + foreach (var row in map.Mapping.Rows) + { + if (needsWorksheetNameBackfill && string.IsNullOrEmpty(row.WorksheetName) && + worksheetNameByPath.TryGetValue(row.Path, out var worksheetName)) + { + row.WorksheetName = worksheetName; + } + + if (needsSourceOrderBackfill && row.SourceOrder == 0 && + sourceOrderByPath.TryGetValue(row.Path, out var sourceOrder)) + { + row.SourceOrder = sourceOrder; + } + } + } // If we have an existing reportColumnsMap - let check if changes have occured that could effect the mapping map.DetectedChanges = await provider.DetectChangesAsync(correlationId, reportColumnsMap); diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/ReportMappingUtils.cs b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/ReportMappingUtils.cs index 956a7aff06..e8716bae4b 100644 --- a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/ReportMappingUtils.cs +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/ReportMappingUtils.cs @@ -344,7 +344,9 @@ internal static ReportColumnsMap CreateNewMap(UpsertReportColumnsMapDto upsertRe DataPath = field.DataPath, TypePath = field.TypePath, Id = field.Id, - VersionLabel = field.VersionLabel + VersionLabel = field.VersionLabel, + WorksheetName = field.WorksheetName, + SourceOrder = field.SourceOrder }; }).ToList(); @@ -453,7 +455,9 @@ internal static ReportColumnsMap UpdateExistingMap(UpsertReportColumnsMapDto upd DataPath = field.DataPath, TypePath = field.TypePath, Id = field.Id, - VersionLabel = field.VersionLabel + VersionLabel = field.VersionLabel, + WorksheetName = field.WorksheetName, + SourceOrder = field.SourceOrder }; }).ToList(); diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Domain/Configuration/ReportColumnsMap.cs b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Domain/Configuration/ReportColumnsMap.cs index 7bd059bb13..ed06422e82 100644 --- a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Domain/Configuration/ReportColumnsMap.cs +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Domain/Configuration/ReportColumnsMap.cs @@ -141,6 +141,18 @@ public class MapRow /// a comma-separated value (e.g., "v1, v2") means the column appears in those versions but not all. /// public string? VersionLabel { get; set; } = null; + + /// + /// Gets or sets the name of the worksheet this field belongs to (includes the version suffix, e.g. "grant_application-v2"). + /// Populated only for worksheet-based providers; null for scoresheet/formversion providers. + /// + public string? WorksheetName { get; set; } = null; + + /// + /// Gets or sets the field's 1-based position in the provider's calculated overall field order. + /// Drives the report-config-table's default sort (Source Order 1 -> n); not shown by default. + /// + public int SourceOrder { get; set; } } /// diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Views/Shared/Components/ReportingConfiguration/Default.js b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Views/Shared/Components/ReportingConfiguration/Default.js index 0b74fb8a41..e0507a28a2 100644 --- a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Views/Shared/Components/ReportingConfiguration/Default.js +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Views/Shared/Components/ReportingConfiguration/Default.js @@ -559,7 +559,9 @@ $(function () { dataPath: row.dataPath, columnName: row.columnName || '', typePath: row.typePath, - versionLabel: row.versionLabel || null + versionLabel: row.versionLabel || null, + worksheetName: row.worksheetName || null, + sourceOrder: row.sourceOrder || 0 })); return { @@ -593,7 +595,9 @@ $(function () { dataPath: field.dataPath, columnName: getDefaultColumnNameSource(field), typePath: field.typePath, - versionLabel: field.versionLabel || null + versionLabel: field.versionLabel || null, + worksheetName: field.worksheetName || null, + sourceOrder: field.sourceOrder || 0 })); return { @@ -656,12 +660,28 @@ $(function () { const providerConfig = getCurrentProviderConfig(); const listColumns = [ + { + title: 'Worksheet Name', + data: 'worksheetName', + name: 'worksheetName', + className: 'data-table-header', + width: '220px', + index: 0, + orderable: true, + visible: currentProvider === 'worksheet' || currentProvider === 'worksheet_consolidated', + render: function (data, type, _) { + if (type === 'display') { + return data || ''; + } + return data || ''; + } + }, { title: providerConfig.columns.label, data: 'label', name: 'label', className: 'data-table-header', - index: 0, + index: 1, orderable: true }, { @@ -669,7 +689,7 @@ $(function () { data: 'key', name: 'key', className: 'data-table-header', - index: 1, + index: 2, orderable: true }, { @@ -677,7 +697,7 @@ $(function () { data: 'type', name: 'type', className: 'data-table-header', - index: 2, + index: 3, orderable: true }, { @@ -685,7 +705,7 @@ $(function () { data: 'dataPath', // We use the dataPath explicitly here name: 'path', className: 'data-table-header', - index: 3, + index: 4, orderable: true, render: function (data, type, row) { if (type === 'display') { @@ -699,7 +719,7 @@ $(function () { data: 'columnName', name: 'columnName', className: 'data-table-header', - index: 4, + index: 5, orderable: false, render: function (data, type, row) { if (type === 'display') { @@ -714,7 +734,7 @@ $(function () { data: 'typePath', name: 'typePath', className: 'data-table-header', - index: 5, + index: 6, orderable: false, render: function (data, type, _) { if (type === 'display') { @@ -729,7 +749,7 @@ $(function () { name: 'versionLabel', className: 'data-table-header', width: '90px', - index: 6, + index: 7, orderable: true, visible: currentProvider === 'worksheet_consolidated' || currentProvider === 'formversion_consolidated', render: function (data, type, _) { @@ -738,6 +758,24 @@ $(function () { } return data || ''; } + }, + { + title: 'Source Order', + data: 'sourceOrder', + name: 'sourceOrder', + className: 'data-table-header', + width: '90px', + index: 8, + orderable: true, + // Hidden by default (not part of defaultVisibleColumns below) — it exists so users can + // opt in via the column picker and re-sort back to the calculated natural order after + // sorting by another column. + render: function (data, type, _) { + if (type === 'display') { + return data || ''; + } + return data; + } } ]; @@ -790,12 +828,26 @@ $(function () { if (currentProvider === 'worksheet_consolidated' || currentProvider === 'formversion_consolidated') { defaultVisibleColumns.push('versionLabel'); } + if (currentProvider === 'worksheet' || currentProvider === 'worksheet_consolidated') { + defaultVisibleColumns.push('worksheetName'); + } + + // Default sort: for worksheet-based providers, sort by the hidden Source Order column (1 -> n), + // the server's calculated overall field order (Worksheet Name A-Z, then section order, then field + // layout order, then checkbox group option / data grid column order). Other providers default-sort + // by Label, unchanged. Resolved here to the column's actual numeric index — DataTables' initial + // `order` option only accepts column indices, not names, so passing a name straight through would + // silently fail to apply any sort. + const defaultSortColumnName = (currentProvider === 'worksheet' || currentProvider === 'worksheet_consolidated') + ? 'sourceOrder' + : 'label'; + const defaultSortColumn = listColumns.find(c => c.name === defaultSortColumnName)?.index ?? 1; dataTable = initializeDataTable({ dt, defaultVisibleColumns, listColumns, - defaultSortColumn: 0, + defaultSortColumn, dataEndpoint: dataEndpoint, data: {}, responseCallback: responseCallback, @@ -811,6 +863,16 @@ $(function () { fixedHeaders: true }); + // table-utils' getVisibleColumnIndexes() always forces the column at position 0 to be + // visible (so the table never renders with an empty leftmost column), regardless of + // defaultVisibleColumns. Worksheet Name now occupies position 0 so it can be the leftmost + // column, which means that force-visible rule would otherwise show it even for providers + // where it should stay hidden. Re-apply the correct provider-based visibility here so it's + // not stuck "on" before any saved preference (below) has a chance to override it. + _suppressColvisSave = true; + dataTable.column('worksheetName:name').visible(currentProvider === 'worksheet' || currentProvider === 'worksheet_consolidated'); + _suppressColvisSave = false; + // Persist column visibility per provider dataTable.on('column-visibility.dt', function () { if (!_suppressColvisSave) saveColvisState(currentProvider); diff --git a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/unity-styles.css b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/unity-styles.css index 93e869148d..1567fe83c5 100644 --- a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/unity-styles.css +++ b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/unity-styles.css @@ -914,4 +914,11 @@ div.dt-container div.dt-search { .filter-search-action-bar_search-wrapper { flex: 1; +} + +table.dataTable th.dt-type-numeric div.dt-column-header, +table.dataTable th.dt-type-date div.dt-column-header, +table.dataTable td.dt-type-numeric div.dt-column-header, +table.dataTable td.dt-type-date div.dt-column-header { + flex-direction: row !important; } \ No newline at end of file diff --git a/applications/Unity.GrantManager/ocUnityDbConnect.ps1 b/applications/Unity.GrantManager/ocUnityDbConnect.ps1 index a575ec6c4b..68097f1f84 100644 --- a/applications/Unity.GrantManager/ocUnityDbConnect.ps1 +++ b/applications/Unity.GrantManager/ocUnityDbConnect.ps1 @@ -1,9 +1,30 @@ +# Prompt user for environment selection +$validEnvironments = @("dev", "test", "prod") +do { + Write-Host "Enter environment (dev, test, prod)" -ForegroundColor Green + $environment = Read-Host +} while (-not ($validEnvironments -contains $environment)) + +# Prompt user for cluster selection +$validPlatforms = @("gold", "silver") +do { + Write-Host "Enter OpenShift cluster (gold, silver)" -ForegroundColor Green + $platform = Read-Host +} while (-not ($validPlatforms -contains $platform.ToLowerInvariant())) + +$platform = $platform.ToLowerInvariant() +$server = if ($platform -eq "gold") { + "https://api.gold.devops.gov.bc.ca:6443" +} else { + "https://api.silver.devops.gov.bc.ca:6443" +} + # Prompt the user to optionally login to OpenShift Write-Host "Do you want to log in to OpenShift now? (y/n)" -ForegroundColor Green $loginResponse = Read-Host if ($loginResponse -match '^(y|yes)$') { try { - oc login --web --server=https://api.silver.devops.gov.bc.ca:6443 + oc login --web --server=$server } catch { Write-Host "Login failed. Please check your connection and credentials." -ForegroundColor Red @@ -11,13 +32,6 @@ if ($loginResponse -match '^(y|yes)$') { } } -# Prompt user for environment selection -$validEnvironments = @("dev", "test", "prod") -do { - Write-Host "Enter environment (dev, test, prod)" -ForegroundColor Green - $environment = Read-Host -} while (-not ($validEnvironments -contains $environment)) - # Define cluster mappings $clusterMappings = @{ @@ -37,8 +51,9 @@ if ($environment -eq 'prod') { } -# Configuration parameters (dynamically updated based on environment) -$NameSpace = "d18498-$environment" # OpenShift project namespace +# Configuration parameters (dynamically updated based on environment and cluster) +$namespacePrefix = if ($platform -eq "gold") { "ce395f" } else { "d18498" } +$NameSpace = "$namespacePrefix-$environment" # OpenShift project namespace $ClusterName = "$cluster-crunchy-postgres" $LocalPort = 5436 $RemotePort = 5432 diff --git a/applications/Unity.GrantManager/scripts/Get-SonarIssues.ps1 b/applications/Unity.GrantManager/scripts/Get-SonarIssues.ps1 index 736c658310..994a1ece4a 100644 --- a/applications/Unity.GrantManager/scripts/Get-SonarIssues.ps1 +++ b/applications/Unity.GrantManager/scripts/Get-SonarIssues.ps1 @@ -3,12 +3,16 @@ Pulls open SonarCloud issues for a branch via the public API and writes them to a Markdown report. .DESCRIPTION - Calls the SonarCloud /api/issues/search endpoint (paginating past its 500-per-page limit), + Calls the SonarQube /api/issues/search endpoint (paginating past its 500-per-page limit), then groups the results by severity into a Markdown file - handy for pasting into Copilot/Claude - or attaching to a PR instead of screen-scraping the SonarCloud UI. + or attaching to a PR instead of screen-scraping the SonarQube UI. + +.PARAMETER ServerUrl + SonarQube server URL. Default: https://sonarqube.econ.gov.bc.ca/sonar. Set this to + https://sonarcloud.io when querying SonarCloud. .PARAMETER ProjectKey - SonarCloud project (component) key. Default: bcgov_Unity. + SonarQube project (component) key. Default: UnityScanKey. .PARAMETER Branch Branch name to query. If omitted, you'll be prompted to pick the current git branch, one of @@ -28,6 +32,10 @@ .PARAMETER Types Optional filter, e.g. -Types BUG,VULNERABILITY. Valid values: BUG, VULNERABILITY, CODE_SMELL. +.PARAMETER FileType + Source file type to include. Valid values: js, css, cshtml, cs, all. If omitted, you'll be + prompted to choose one. + .PARAMETER IncludeResolved Include resolved/closed issues too. By default only unresolved (open) issues are fetched. @@ -41,7 +49,10 @@ this for unattended/CI runs where nothing should launch afterwards. .EXAMPLE - .\Get-SonarIssues.ps1 -Branch main -Token $env:SONAR_TOKEN + .\Get-SonarIssues.ps1 -Branch dev -Token $env:SONAR_TOKEN + +.EXAMPLE + .\Get-SonarIssues.ps1 -ServerUrl https://sonarcloud.io -ProjectKey bcgov_Unity -Branch main -Token $env:SONAR_TOKEN .EXAMPLE .\Get-SonarIssues.ps1 -ProjectKey bcgov_Unity -Branch feature/AB-12345 -Severities BLOCKER,CRITICAL -OutputPath .\sonar-report.md @@ -59,7 +70,9 @@ .\Get-SonarIssues.ps1 -Branch main -FixLevel Quick #> param( - [string]$ProjectKey = "bcgov_Unity", + [string]$ServerUrl = "https://sonarqube.econ.gov.bc.ca/sonar", + + [string]$ProjectKey = "UnityScanKey", [string]$Branch = "", @@ -73,6 +86,9 @@ param( [ValidateSet("BUG", "VULNERABILITY", "CODE_SMELL")] [string[]]$Types = @(), + [ValidateSet("js", "css", "cshtml", "cs", "all")] + [string]$FileType = "", + [switch]$IncludeResolved, [ValidateSet("None", "Quick", "QuickModerate", "All")] @@ -83,7 +99,7 @@ param( $ErrorActionPreference = "Stop" -$ApiBase = "https://sonarcloud.io/api/issues/search" +$ApiBase = "$($ServerUrl.TrimEnd('/'))/api/issues/search" $PageSize = 500 $SeverityOrder = @("BLOCKER", "CRITICAL", "MAJOR", "MINOR", "INFO") $WellKnownBranches = @("dev", "test", "main") @@ -151,6 +167,32 @@ function Read-BranchSelection { } } +function Read-FileTypeSelection { + $menu = [ordered]@{ + "1" = @{ Label = "JavaScript (.js)"; Extension = "js" } + "2" = @{ Label = "Stylesheet (.css)"; Extension = "css" } + "3" = @{ Label = "Razor (.cshtml)"; Extension = "cshtml" } + "4" = @{ Label = "C# (.cs)"; Extension = "cs" } + "5" = @{ Label = "All source file types"; Extension = "all" } + } + + Write-Host "" + Write-Host "Which source file type should be included?" -ForegroundColor Cyan + foreach ($key in $menu.Keys) { + Write-Host " [$key] $($menu[$key].Label)" + } + + while ($true) { + $choice = Read-Host "Enter choice (default: 5 - All source file types)" + if ([string]::IsNullOrWhiteSpace($choice)) { $choice = "5" } + + if ($menu.Contains($choice)) { + return $menu[$choice].Extension + } + Write-Host "Invalid choice '$choice' - try again." -ForegroundColor Yellow + } +} + # Maps a -FixLevel value (or the equivalent interactive menu choice) to the FixComplexity tiers # it covers. "None" intentionally maps to an empty array - nothing gets fixed. $FixLevelTierMap = [ordered]@{ @@ -190,6 +232,10 @@ if (-not $Branch) { $Branch = Read-BranchSelection -CurrentBranch (Get-CurrentGitBranch) } +if (-not $FileType) { + $FileType = Read-FileTypeSelection +} + if (-not $OutputPath) { $branchSlug = if ($Branch) { ($Branch -replace '[\\/:*?"<>|]', '-') } else { "default-branch" } $OutputPath = "sonar-issues-$branchSlug.md" @@ -287,6 +333,14 @@ function Get-IssueFilePath { return ($Issue.component -replace "^$([regex]::Escape($ProjectKey)):", "") } +if ($FileType -ne "all") { + $extension = ".$FileType" + $allIssues = [System.Collections.Generic.List[object]]@( + $allIssues | Where-Object { (Get-IssueFilePath $_).EndsWith($extension, [System.StringComparison]::OrdinalIgnoreCase) } + ) + Write-Host "Issues after file type filter '$FileType': $($allIssues.Count)" +} + # --- Fix-complexity classification --- # Goal: separate mechanical, low-risk fixes (rename, swap one API for another, drop an unused var) # from ones that ripple across every call site or need an actual design change, so the report can @@ -366,6 +420,7 @@ $md = New-Object System.Text.StringBuilder [void]$md.AppendLine("") [void]$md.AppendLine("- **Project:** $ProjectKey") [void]$md.AppendLine("- **Branch:** $(if ($Branch) { $Branch } else { '(default)' })") +[void]$md.AppendLine("- **Source file type:** $FileType") [void]$md.AppendLine("- **Generated:** $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')") [void]$md.AppendLine("- **Total open issues:** $($allIssues.Count)") [void]$md.AppendLine("") @@ -404,7 +459,7 @@ if ($quickWins.Count -gt 0) { $file = Get-IssueFilePath $issue $line = if ($issue.textRange -and $issue.textRange.startLine) { $issue.textRange.startLine } else { "-" } $message = ($issue.message -replace '\|', '\|') - $link = "https://sonarcloud.io/project/issues?id=$ProjectKey&issues=$($issue.key)&open=$($issue.key)$(if ($Branch) { "&branch=$Branch" })" + $link = "$($ServerUrl.TrimEnd('/'))/project/issues?id=$ProjectKey&issues=$($issue.key)&open=$($issue.key)$(if ($Branch) { "&branch=$Branch" })" [void]$md.AppendLine("| $($issue.severity) | ``$file`` | $line | [$($issue.rule)]($link) | $message |") } [void]$md.AppendLine("") @@ -425,7 +480,7 @@ foreach ($sev in $SeverityOrder) { $line = if ($issue.textRange -and $issue.textRange.startLine) { $issue.textRange.startLine } else { "-" } $message = ($issue.message -replace '\|', '\|') $effort = if ($issue.effort) { $issue.effort } else { "-" } - $link = "https://sonarcloud.io/project/issues?id=$ProjectKey&issues=$($issue.key)&open=$($issue.key)$(if ($Branch) { "&branch=$Branch" })" + $link = "$($ServerUrl.TrimEnd('/'))/project/issues?id=$ProjectKey&issues=$($issue.key)&open=$($issue.key)$(if ($Branch) { "&branch=$Branch" })" [void]$md.AppendLine("| ``$file`` | $line | $($issue.type) | $($issue.FixComplexity.Badge) | $effort | [$($issue.rule)]($link) | $message |") } [void]$md.AppendLine("") diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Applicants/ApplicantListDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Applicants/ApplicantListDto.cs index b8ab20408b..7b9eb63212 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Applicants/ApplicantListDto.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Applicants/ApplicantListDto.cs @@ -28,4 +28,5 @@ public class ApplicantListDto : AuditedEntityDto public int? FiscalDay { get; set; } public DateTime? StartedOperatingDate { get; set; } public bool IsDuplicated { get; set; } + public DateOnly? FiscalYearEnd { get; set; } } \ No newline at end of file diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/ApplicantSummaryDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/ApplicantSummaryDto.cs index 332d0f7986..375cdd7b45 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/ApplicantSummaryDto.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/ApplicantSummaryDto.cs @@ -21,6 +21,7 @@ public class ApplicantSummaryDto public string? UnityApplicantId { get; set; } public string? FiscalDay { get; set; } public string? FiscalMonth { get; set; } + public DateOnly? FiscalYearEnd { get; set; } public string? ElectoralDistrict { get; set; } public bool IsDuplicated { get; set; } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/GrantApplicationDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/GrantApplicationDto.cs index bcd98265cf..1e634c4df5 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/GrantApplicationDto.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/GrantApplicationDto.cs @@ -40,6 +40,8 @@ public class GrantApplicationDto : AuditedEntityDto public string? Notes { get; set; } = string.Empty; public string? AssessmentResultStatus { get; set; } = string.Empty; public bool ExternalStatusVisibility { get; set; } = false; + public string ExternalStatus { get; set; } = string.Empty; + public string? PublishedStatus { get; set; } public DateTime? AssessmentResultDate { get; set; } public GrantApplicationState StatusCode { get; set; } public DateTime? FinalDecisionDate { get; set; } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantManagerFeaturesDefinitionProvider.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantManagerFeaturesDefinitionProvider.cs index f302199750..7f7bf9fb0a 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantManagerFeaturesDefinitionProvider.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantManagerFeaturesDefinitionProvider.cs @@ -13,7 +13,7 @@ public class GrantManagerFeaturesDefinitionProvider : FeatureDefinitionProvider { public override void Define(IFeatureDefinitionContext context) { - var myGroup = context.AddGroup("GrantManager"); + var myGroup = context.AddGroup("GrantManager", displayName: LocalizableString.Create("Grant Manager")); var defaultValue = "false"; myGroup.AddFeature("Unity.Payments", diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Applicants/ApplicantAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Applicants/ApplicantAppService.cs index 15c346d508..8203ff5e4d 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Applicants/ApplicantAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Applicants/ApplicantAppService.cs @@ -5,23 +5,23 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using System.Text.Json; +using System.Threading.Tasks; using Unity.GrantManager.Applications; using Unity.GrantManager.GrantApplications; using Unity.GrantManager.Intakes; using Unity.GrantManager.Intakes.Mapping; -using Unity.Payments.Events; -using Volo.Abp; using Unity.GrantManager.Integrations.Orgbook; +using Unity.GrantManager.Permissions; using Unity.Modules.Shared; using Unity.Modules.Shared.Utils; using Unity.Payments.Domain.Suppliers; -using Unity.GrantManager.Permissions; +using Unity.Payments.Events; using Unity.Payments.Integrations.Cas; using Unity.Payments.Suppliers; -using Volo.Abp.DependencyInjection; +using Volo.Abp; using Volo.Abp.Application.Dtos; +using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Repositories; namespace Unity.GrantManager.Applicants; @@ -798,8 +798,9 @@ public async Task> GetListAsync(ApplicantListRe : null, IsDuplicated = applicant.IsDuplicated, CreationTime = applicant.CreationTime, - LastModificationTime = applicant.LastModificationTime - }).ToList(); + LastModificationTime = applicant.LastModificationTime, + FiscalYearEnd = applicant.FiscalYearEnd, + }).ToList(); // Use items.Count while client side datatables are used. When going to server // side actually query the correct amount with paging enabled. return new PagedResultDto(items.Count, items); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/GrantApplicationAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/GrantApplicationAppService.cs index f1ce5f881a..536b503fce 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/GrantApplicationAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/GrantApplicationAppService.cs @@ -13,7 +13,6 @@ using System.Text.Json; using System.Threading.Tasks; using Unity.AI.Models; -using Unity.AI.Permissions; using Unity.AI.Responses; using Unity.Flex.WorksheetInstances; using Unity.Flex.Worksheets; @@ -25,7 +24,6 @@ using Unity.GrantManager.GlobalTag; using Unity.GrantManager.Identity; using Unity.GrantManager.Payments; -using Unity.GrantManager.GrantApplications.Automation; using Unity.Modules.Shared; using Unity.Modules.Shared.Correlation; using Unity.Modules.Shared.Specializations; @@ -159,9 +157,11 @@ public async Task> GetListAsync(GrantApplica RiskRanking = rec.RiskRanking, UnityApplicationId = rec.UnityApplicationId, ExternalStatusVisibility = rec.ExternalStatusVisibility, - + // From ApplicationStatus Status = rec.Status, + ExternalStatus = rec.ExternalStatus, + PublishedStatus = rec.PublishedStatus, // From ApplicationForm Category = rec.Category, diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Endpoints/EndpointManagementAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Endpoints/EndpointManagementAppService.cs index 5c03430b2e..c369d76dc6 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Endpoints/EndpointManagementAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Endpoints/EndpointManagementAppService.cs @@ -15,7 +15,8 @@ namespace Unity.GrantManager.Integrations.Endpoints { public class EndpointManagementAppService( IRepository repository, - IDistributedCache cache) : + IDistributedCache cache, + IUnitOfWorkManager unitOfWorkManager) : CrudAppService< DynamicUrl, DynamicUrlDto, @@ -25,6 +26,7 @@ public class EndpointManagementAppService( IEndpointManagementAppService { private readonly IDistributedCache _cache = cache; + private readonly IUnitOfWorkManager _unitOfWorkManager = unitOfWorkManager; private const string CACHE_KEY_SET_PREFIX = "DynamicUrl:KeySet"; private static string BuildCacheKey(string keyName, bool tenantSpecific, Guid? tenantId) @@ -156,13 +158,17 @@ await _cache.SetStringAsync( private async Task GetUrlValueAsync(string keyName, Guid? tenantId) { + using var uow = _unitOfWorkManager.Begin(requiresNew: true, isTransactional: false); var queryable = await Repository.GetQueryableAsync(); - return await AsyncExecuter.FirstOrDefaultAsync( + var url = await AsyncExecuter.FirstOrDefaultAsync( queryable .AsNoTracking() .Where(x => x.KeyName == keyName && x.TenantId == tenantId) .Select(x => x.Url)); + + await uow.CompleteAsync(); + return url; } // ------------------------------ diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json index 3f0b387506..10945ccd1a 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json @@ -67,7 +67,9 @@ "Assignee": "Assignee", "Probability": "Probability", "GrantApplicationStatus": "Status", - "ExternalStatusVisibility": "Status Visibility", + "ExternalStatusVisibility": "External Status Visibility", + "ExternalStatus": "External Status", + "PublishedStatus": "Published Status", "ProposalDate": "Proposal Date", "SubmissionDate": "Submission Date", "GrantApplications": "Grant Applications", diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/Applicant.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/Applicant.cs index 12645d2962..94d6158dd4 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/Applicant.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/Applicant.cs @@ -24,6 +24,7 @@ public class Applicant : AuditedAggregateRoot, IMultiTenant public string? FiscalMonth { get; set; } public string? BusinessNumber { get; set; } public int? FiscalDay { get; set; } + public DateOnly? FiscalYearEnd { get; set; } public DateOnly? StartedOperatingDate { get; set; } public Guid? TenantId { get; set; } public Guid? SupplierId { get; set; } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicantListRecord.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicantListRecord.cs index 72bccbb077..9ce7b8704e 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicantListRecord.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicantListRecord.cs @@ -27,4 +27,5 @@ public class ApplicantListRecord public bool IsDuplicated { get; set; } public DateTime CreationTime { get; set; } public DateTime? LastModificationTime { get; set; } + public DateOnly? FiscalYearEnd { get; set; } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationListRecord.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationListRecord.cs index 7b4f4e11ca..8026f005c6 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationListRecord.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationListRecord.cs @@ -61,6 +61,8 @@ public class ApplicationListRecord // ApplicationStatus (always joined) public string Status { get; init; } = string.Empty; + public string ExternalStatus { get; init; } = string.Empty; + public string? PublishedStatus { get; init; } // ApplicationForm (always joined) public string Category { get; init; } = string.Empty; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/GrantTenantDbContext.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/GrantTenantDbContext.cs index c77da4df0b..fefe53b707 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/GrantTenantDbContext.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/GrantTenantDbContext.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using System.Linq; using Unity.GrantManager.Applications; @@ -96,6 +97,12 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .WithOne(s => s.Applicant) .HasForeignKey(x => x.ApplicantId) .OnDelete(DeleteBehavior.NoAction); + + // FYE is updated through the compute_applicants_fiscal_year_end() trigger + var fyeProp = b.Property(x => x.FiscalYearEnd) + .HasColumnType("date") + .ValueGeneratedOnAddOrUpdate(); + fyeProp.Metadata.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore); }); modelBuilder.Entity(b => diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/20260805185000_RebuildAIModels.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/20260805185000_RebuildAIModels.cs new file mode 100644 index 0000000000..28d7d6c9a7 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/20260805185000_RebuildAIModels.cs @@ -0,0 +1,181 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Unity.GrantManager.EntityFrameworkCore; + +#nullable disable + +namespace Unity.GrantManager.Migrations.HostMigrations; + +[DbContext(typeof(GrantManagerDbContext))] +[Migration("20260805185000_RebuildAIModels")] +public partial class RebuildAIModels : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql( + """ + ALTER TABLE "AI"."AIOperations" + DROP CONSTRAINT IF EXISTS "FK_AIOperations_AIModels_AIModelId"; + + DROP INDEX IF EXISTS "AI"."IX_AIOperations_AIModelId"; + DROP INDEX IF EXISTS "AI"."IX_AIModels_Name"; + + ALTER TABLE IF EXISTS "AI"."AIModels" + RENAME TO "AIModels_Legacy"; + + DO $$ + BEGIN + IF EXISTS ( + SELECT 1 + FROM pg_constraint c + JOIN pg_class t ON t.oid = c.conrelid + JOIN pg_namespace n ON n.oid = t.relnamespace + WHERE n.nspname = 'AI' + AND t.relname = 'AIModels_Legacy' + AND c.conname = 'PK_AIModels' + ) THEN + ALTER TABLE "AI"."AIModels_Legacy" + RENAME CONSTRAINT "PK_AIModels" TO "PK_AIModels_Legacy"; + END IF; + END $$; + + CREATE TABLE IF NOT EXISTS "AI"."AIModels" + ( + "Id" uuid NOT NULL, + "Name" character varying(200) NOT NULL, + "Provider" character varying(100) NOT NULL, + "IsActive" boolean NOT NULL, + "SettingsJson" jsonb NOT NULL, + "ExtraProperties" text NOT NULL, + "ConcurrencyStamp" character varying(40) NOT NULL, + "CreationTime" timestamp without time zone NOT NULL, + "CreatorId" uuid NULL, + "LastModificationTime" timestamp without time zone NULL, + "LastModifierId" uuid NULL, + CONSTRAINT "PK_AIModels" PRIMARY KEY ("Id") + ); + + DO $$ + BEGIN + IF to_regclass('"AI"."AIModels_Legacy"') IS NOT NULL THEN + INSERT INTO "AI"."AIModels" + ( + "Id", "Name", "Provider", "IsActive", "SettingsJson", + "ExtraProperties", "ConcurrencyStamp", "CreationTime", "CreatorId", + "LastModificationTime", "LastModifierId" + ) + SELECT + "Id", + CASE "Name" + WHEN 'Gpt4oMini' THEN 'gpt-4o-mini' + WHEN 'Gpt5Mini' THEN 'gpt-5-mini' + WHEN 'Gpt5Nano' THEN 'gpt-5-nano' + ELSE "Name" + END, + 'OpenAI', "IsActive", "SettingsJson", "ExtraProperties", + "ConcurrencyStamp", "CreationTime", "CreatorId", + "LastModificationTime", "LastModifierId" + FROM "AI"."AIModels_Legacy"; + END IF; + END $$; + + DROP TABLE IF EXISTS "AI"."AIModels_Legacy"; + + CREATE UNIQUE INDEX "IX_AIModels_Name" + ON "AI"."AIModels" ("Name"); + + CREATE INDEX "IX_AIOperations_AIModelId" + ON "AI"."AIOperations" ("AIModelId"); + + ALTER TABLE "AI"."AIOperations" + ADD CONSTRAINT "FK_AIOperations_AIModels_AIModelId" + FOREIGN KEY ("AIModelId") + REFERENCES "AI"."AIModels" ("Id") + ON DELETE RESTRICT; + """); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql( + """ + ALTER TABLE "AI"."AIOperations" + DROP CONSTRAINT IF EXISTS "FK_AIOperations_AIModels_AIModelId"; + + DROP INDEX IF EXISTS "AI"."IX_AIOperations_AIModelId"; + DROP INDEX IF EXISTS "AI"."IX_AIModels_Name"; + + ALTER TABLE IF EXISTS "AI"."AIModels" + RENAME TO "AIModels_Current"; + + DO $$ + BEGIN + IF EXISTS ( + SELECT 1 + FROM pg_constraint c + JOIN pg_class t ON t.oid = c.conrelid + JOIN pg_namespace n ON n.oid = t.relnamespace + WHERE n.nspname = 'AI' + AND t.relname = 'AIModels_Current' + AND c.conname = 'PK_AIModels' + ) THEN + ALTER TABLE "AI"."AIModels_Current" + RENAME CONSTRAINT "PK_AIModels" TO "PK_AIModels_Current"; + END IF; + END $$; + + CREATE TABLE IF NOT EXISTS "AI"."AIModels" + ( + "Id" uuid NOT NULL, + "Name" character varying(200) NOT NULL, + "IsActive" boolean NOT NULL, + "SettingsJson" jsonb NOT NULL, + "ExtraProperties" text NOT NULL, + "ConcurrencyStamp" character varying(40) NOT NULL, + "CreationTime" timestamp without time zone NOT NULL, + "CreatorId" uuid NULL, + "LastModificationTime" timestamp without time zone NULL, + "LastModifierId" uuid NULL, + CONSTRAINT "PK_AIModels" PRIMARY KEY ("Id") + ); + + DO $$ + BEGIN + IF to_regclass('"AI"."AIModels_Current"') IS NOT NULL THEN + INSERT INTO "AI"."AIModels" + ( + "Id", "Name", "IsActive", "SettingsJson", "ExtraProperties", + "ConcurrencyStamp", "CreationTime", "CreatorId", "LastModificationTime", + "LastModifierId" + ) + SELECT + "Id", + CASE "Name" + WHEN 'gpt-4o-mini' THEN 'Gpt4oMini' + WHEN 'gpt-5-mini' THEN 'Gpt5Mini' + WHEN 'gpt-5-nano' THEN 'Gpt5Nano' + ELSE "Name" + END, + "IsActive", "SettingsJson", "ExtraProperties", "ConcurrencyStamp", + "CreationTime", "CreatorId", "LastModificationTime", "LastModifierId" + FROM "AI"."AIModels_Current"; + END IF; + END $$; + + DROP TABLE IF EXISTS "AI"."AIModels_Current"; + + CREATE UNIQUE INDEX "IX_AIModels_Name" + ON "AI"."AIModels" ("Name"); + + CREATE INDEX "IX_AIOperations_AIModelId" + ON "AI"."AIOperations" ("AIModelId"); + + ALTER TABLE "AI"."AIOperations" + ADD CONSTRAINT "FK_AIOperations_AIModels_AIModelId" + FOREIGN KEY ("AIModelId") + REFERENCES "AI"."AIModels" ("Id") + ON DELETE RESTRICT; + """); + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/GrantManagerDbContextModelSnapshot.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/GrantManagerDbContextModelSnapshot.cs index 0596cc6c64..af2ce821bf 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/GrantManagerDbContextModelSnapshot.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/GrantManagerDbContextModelSnapshot.cs @@ -508,6 +508,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(200) .HasColumnType("character varying(200)"); + b.Property("Provider") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + b.Property("SettingsJson") .IsRequired() .HasColumnType("jsonb"); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260806201536_AB33799_HardenCheckboxGroupReportingViews.Designer.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260806201536_AB33799_HardenCheckboxGroupReportingViews.Designer.cs new file mode 100644 index 0000000000..837f6ef919 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260806201536_AB33799_HardenCheckboxGroupReportingViews.Designer.cs @@ -0,0 +1,5273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Unity.GrantManager.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +#nullable disable + +namespace Unity.GrantManager.Migrations.TenantMigrations +{ + [DbContext(typeof(GrantTenantDbContext))] + [Migration("20260806201536_AB33799_HardenCheckboxGroupReportingViews")] + partial class AB33799_HardenCheckboxGroupReportingViews + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.PostgreSql) + .HasAnnotation("ProductVersion", "10.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ScoresheetId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ScoresheetId"); + + b.ToTable("ScoresheetInstances", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Answer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("CurrentValue") + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("ScoresheetInstanceId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("QuestionId"); + + b.HasIndex("ScoresheetInstanceId"); + + b.ToTable("Answers", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Definition") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("Label") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("SectionId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SectionId"); + + b.ToTable("Questions", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Scoresheet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsArchived") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("Published") + .HasColumnType("boolean"); + + b.Property("ReportColumns") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportKeys") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("Scoresheets", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("ScoresheetId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ScoresheetId"); + + b.ToTable("ScoresheetSections", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.CustomFieldValue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("CurrentValue") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CustomFieldId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("WorksheetInstanceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorksheetInstanceId"); + + b.ToTable("CustomFieldValues", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("CurrentValue") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UiAnchor") + .IsRequired() + .HasColumnType("text"); + + b.Property("WorksheetCorrelationId") + .HasColumnType("uuid"); + + b.Property("WorksheetCorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("WorksheetId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("WorksheetInstances", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetLinks.WorksheetLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UiAnchor") + .IsRequired() + .HasColumnType("text"); + + b.Property("WorksheetId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorksheetId"); + + b.ToTable("WorksheetLinks", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.CustomField", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Definition") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("Key") + .IsRequired() + .HasColumnType("text"); + + b.Property("Label") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("SectionId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SectionId"); + + b.ToTable("CustomFields", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.Worksheet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsArchived") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Published") + .HasColumnType("boolean"); + + b.Property("ReportColumns") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportKeys") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("Worksheets", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Definition") + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("WorksheetId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorksheetId"); + + b.ToTable("WorksheetSections", "Flex"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Applicant", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantName") + .IsRequired() + .HasMaxLength(600) + .HasColumnType("character varying(600)"); + + b.Property("ApproxNumberOfEmployees") + .HasColumnType("text"); + + b.Property("AuditComments") + .HasColumnType("text"); + + b.Property("BusinessNumber") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FiscalDay") + .HasColumnType("integer"); + + b.Property("FiscalMonth") + .HasColumnType("text"); + + b.Property("FundingHistoryComments") + .HasColumnType("text"); + + b.Property("IndigenousOrgInd") + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsDuplicated") + .HasColumnType("boolean"); + + b.Property("IssueTrackingComments") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MatchPercentage") + .HasColumnType("numeric"); + + b.Property("NonRegOrgName") + .HasColumnType("text"); + + b.Property("NonRegisteredBusinessName") + .HasColumnType("text"); + + b.Property("OrgName") + .HasColumnType("text"); + + b.Property("OrgNumber") + .HasColumnType("text"); + + b.Property("OrgStatus") + .HasColumnType("text"); + + b.Property("OrganizationType") + .HasColumnType("text"); + + b.Property("RedStop") + .HasColumnType("boolean"); + + b.Property("ReportsComments") + .HasColumnType("text"); + + b.Property("Sector") + .HasColumnType("text"); + + b.Property("SectorSubSectorIndustryDesc") + .HasColumnType("text"); + + b.Property("StartedOperatingDate") + .HasColumnType("date"); + + b.Property("Status") + .HasColumnType("text"); + + b.Property("SubSector") + .HasColumnType("text"); + + b.Property("SupplierId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UnityApplicantId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantName"); + + b.HasIndex("OrgName"); + + b.HasIndex("OrgNumber"); + + b.HasIndex("Status"); + + b.HasIndex("SupplierId"); + + b.HasIndex("TenantId"); + + b.HasIndex("UnityApplicantId"); + + b.HasIndex("TenantId", "IsDeleted", "CreationTime") + .HasFilter("\"IsDeleted\" = false"); + + b.ToTable("Applicants", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAddress", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AddressType") + .HasColumnType("integer"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("Country") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Postal") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("Street") + .HasColumnType("text"); + + b.Property("Street2") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Unit") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicantAddresses", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAgent", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("BceidBusinessGuid") + .HasColumnType("uuid"); + + b.Property("BceidBusinessName") + .HasColumnType("text"); + + b.Property("BceidUserGuid") + .HasColumnType("uuid"); + + b.Property("BceidUserName") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContactOrder") + .HasColumnType("integer"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IdentityEmail") + .HasColumnType("text"); + + b.Property("IdentityName") + .HasColumnType("text"); + + b.Property("IdentityProvider") + .HasColumnType("text"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsConfirmed") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OidcSubUser") + .HasColumnType("text"); + + b.Property("Phone") + .HasColumnType("text"); + + b.Property("Phone2") + .HasColumnType("text"); + + b.Property("Phone2Extension") + .HasColumnType("text"); + + b.Property("PhoneExtension") + .HasColumnType("text"); + + b.Property("RoleForApplicant") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationId") + .IsUnique(); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicantAgents", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("ApplicantAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AIAnalysis") + .HasColumnType("text"); + + b.Property("AIScoresheetAnswers") + .HasColumnType("jsonb"); + + b.Property("Acquisition") + .HasColumnType("text"); + + b.Property("ApplicantElectoralDistrict") + .HasColumnType("text"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("ApplicationStatusId") + .HasColumnType("uuid"); + + b.Property("ApprovedAmount") + .HasColumnType("numeric"); + + b.Property("AssessmentResultDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AssessmentResultStatus") + .HasColumnType("text"); + + b.Property("AssessmentStartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("Community") + .HasColumnType("text"); + + b.Property("CommunityPopulation") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContractExecutionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ContractNumber") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeclineRational") + .HasColumnType("text"); + + b.Property("DefaultSiteId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("DueDate") + .HasColumnType("timestamp without time zone"); + + b.Property("DueDiligenceStatus") + .HasColumnType("text"); + + b.Property("EconomicRegion") + .HasColumnType("text"); + + b.Property("ElectoralDistrict") + .HasColumnType("text"); + + b.Property("ExternalStatusVisibility") + .HasColumnType("boolean"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FinalDecisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Forestry") + .HasColumnType("text"); + + b.Property("ForestryFocus") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LikelihoodOfFunding") + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("NotificationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property("Payload") + .HasColumnType("jsonb"); + + b.Property("PercentageTotalProjectBudget") + .HasColumnType("double precision"); + + b.Property("Place") + .HasColumnType("text"); + + b.Property("ProjectEndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ProjectFundingTotal") + .HasColumnType("numeric"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProjectStartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ProjectSummary") + .HasColumnType("text"); + + b.Property("ProposalDate") + .HasColumnType("timestamp without time zone"); + + b.Property("RecommendedAmount") + .HasColumnType("numeric"); + + b.Property("ReferenceNo") + .IsRequired() + .HasColumnType("text"); + + b.Property("RegionalDistrict") + .HasColumnType("text"); + + b.Property("RequestedAmount") + .HasColumnType("numeric"); + + b.Property("RiskRanking") + .HasColumnType("text"); + + b.Property("SigningAuthorityBusinessPhone") + .HasColumnType("text"); + + b.Property("SigningAuthorityCellPhone") + .HasColumnType("text"); + + b.Property("SigningAuthorityEmail") + .HasColumnType("text"); + + b.Property("SigningAuthorityFullName") + .HasColumnType("text"); + + b.Property("SigningAuthorityTitle") + .HasColumnType("text"); + + b.Property("SubStatus") + .HasColumnType("text"); + + b.Property("SubmissionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TotalProjectBudget") + .HasColumnType("numeric"); + + b.Property("TotalScore") + .HasColumnType("integer"); + + b.Property("UnityApplicationId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationFormId"); + + b.HasIndex("ApplicationStatusId"); + + b.HasIndex("OwnerId"); + + b.HasIndex("ReferenceNo"); + + b.HasIndex("TenantId", "SubmissionDate") + .HasFilter("\"IsDeleted\" = false"); + + b.ToTable("Applications", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAssignment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("AssigneeId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Duty") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("AssigneeId"); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicationAssignments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationChefsFileAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AISummary") + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ChefsFileId") + .HasColumnType("text"); + + b.Property("ChefsSubmissionId") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationChefsFileAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationContact", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContactEmail") + .HasColumnType("text"); + + b.Property("ContactFullName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContactMobilePhone") + .HasColumnType("text"); + + b.Property("ContactTitle") + .HasColumnType("text"); + + b.Property("ContactType") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContactWorkPhone") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationContact", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationForm", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountCodingId") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .HasColumnType("text"); + + b.Property("ApplicationFormDescription") + .HasColumnType("text"); + + b.Property("ApplicationFormName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AttemptedConnectionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AutomaticallyGenerateAIAnalysis") + .HasColumnType("boolean"); + + b.Property("AvailableChefsFields") + .HasColumnType("text"); + + b.Property("Category") + .HasColumnType("text"); + + b.Property("ChefsApplicationFormGuid") + .HasColumnType("text"); + + b.Property("ChefsCriteriaFormGuid") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ConnectionHttpStatus") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DefaultPaymentGroup") + .HasColumnType("integer"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ElectoralDistrictAddressType") + .HasColumnType("integer"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormHierarchy") + .HasColumnType("integer"); + + b.Property("IntakeId") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("IsDirectApproval") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ManuallyInitiateAIAnalysis") + .HasColumnType("boolean"); + + b.Property("ParentFormId") + .HasColumnType("uuid"); + + b.Property("Payable") + .HasColumnType("boolean"); + + b.Property("PaymentApprovalThreshold") + .HasColumnType("numeric"); + + b.Property("Prefix") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("PreventPayment") + .HasColumnType("boolean"); + + b.Property("ScoresheetId") + .HasColumnType("uuid"); + + b.Property("SuffixType") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IntakeId"); + + b.HasIndex("ParentFormId"); + + b.HasIndex("TenantId", "IsDeleted") + .HasFilter("\"IsDeleted\" = false"); + + b.ToTable("ApplicationForms", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormSubmission", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormVersionId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ChefsSubmissionGuid") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormVersionId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OidcSub") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Submission") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationFormId"); + + b.ToTable("ApplicationFormSubmissions", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormVersion", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("AvailableChefsFields") + .HasColumnType("text"); + + b.Property("ChefsApplicationFormGuid") + .HasColumnType("text"); + + b.Property("ChefsFormVersionGuid") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormSchema") + .HasColumnType("jsonb"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Published") + .HasColumnType("boolean"); + + b.Property("ReportColumns") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportKeys") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("SubmissionHeaderMapping") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationFormId"); + + b.ToTable("ApplicationFormVersion", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationLink", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LinkType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Related"); + + b.Property("LinkedApplicationId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicationLinks", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationStatus", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExternalStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("InternalStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("NotifiedStatus") + .HasColumnType("text"); + + b.Property("StatusCode") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("StatusCode") + .IsUnique(); + + b.ToTable("ApplicationStatuses", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationTags", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TagId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("TagId"); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicationTags", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AssessmentAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AssessmentId"); + + b.ToTable("AssessmentAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AuditHistory", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("AuditDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AuditNote") + .HasColumnType("text"); + + b.Property("AuditStatus") + .HasColumnType("text"); + + b.Property("AuditTrackingNumber") + .HasColumnType("text"); + + b.Property("AuditorName") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("AuditHistories", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.FundingHistory", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApprovedAmount") + .HasColumnType("numeric"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FundingNotes") + .HasColumnType("text"); + + b.Property("FundingYear") + .HasColumnType("text"); + + b.Property("GrantCategory") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OneTimeConsideration") + .HasColumnType("numeric"); + + b.Property("PaidDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ReconsiderationAmount") + .HasColumnType("numeric"); + + b.Property("RenewedFunding") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TotalGrantAmount") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("FundingHistories", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.IssueTracking", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IssueDescription") + .HasColumnType("text"); + + b.Property("IssueHeading") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ResolutionNote") + .HasColumnType("text"); + + b.Property("Resolved") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Year") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("IssueTrackings", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ReportsHistory", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FiscalYear") + .HasColumnType("text"); + + b.Property("IncompleteReport") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("Outstanding") + .HasColumnType("boolean"); + + b.Property("ReportDate") + .HasColumnType("timestamp without time zone"); + + b.Property("SignedOff") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("ReportsHistories", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Assessments.Assessment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ApprovalRecommended") + .HasColumnType("boolean"); + + b.Property("AssessorId") + .HasColumnType("uuid"); + + b.Property("CleanGrowth") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("EconomicImpact") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FinancialAnalysis") + .HasColumnType("integer"); + + b.Property("InclusiveGrowth") + .HasColumnType("integer"); + + b.Property("IsAiAssessment") + .HasColumnType("boolean"); + + b.Property("IsComplete") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("AssessorId"); + + b.ToTable("Assessments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicantComment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CommenterId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PinDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("CommenterId"); + + b.ToTable("ApplicantComments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicationComment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CommenterId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PinDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("CommenterId"); + + b.ToTable("ApplicationComments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.AssessmentComment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CommenterId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PinDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("AssessmentId"); + + b.HasIndex("CommenterId"); + + b.ToTable("AssessmentComments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Contacts.Contact", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Email") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("HomePhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MobilePhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("WorkPhoneExtension") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("WorkPhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.ToTable("Contacts", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Contacts.ContactLink", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContactId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsPrimary") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("RelatedEntityId") + .HasColumnType("uuid"); + + b.Property("RelatedEntityType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Role") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("RelatedEntityType", "RelatedEntityId"); + + b.HasIndex("ContactId", "RelatedEntityType", "RelatedEntityId"); + + b.ToTable("ContactLinks", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.GlobalTag.Tag", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Tags", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Identity.Person", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Badge") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FullName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OidcDisplayName") + .IsRequired() + .HasColumnType("text"); + + b.Property("OidcSub") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("OidcSub"); + + b.HasIndex("TenantId"); + + b.ToTable("Persons", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Intakes.Intake", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Budget") + .HasColumnType("double precision"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IntakeName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("StartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Intakes", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotification", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationStatus") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ApplicationStatusId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DateField") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EmailTemplateId") + .HasColumnType("uuid"); + + b.Property("EventType") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("RecipientCategory") + .HasColumnType("text"); + + b.Property("RecipientIdentifier") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TriggerDetail") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("TriggerType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("ScheduledNotifications", "Notifications"); + }); + + modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotificationTracking", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatorId") + .HasColumnType("uuid"); + + b.Property("DateField") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("NotificationSentDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ScheduledNotificationId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("CreationTime"); + + b.HasIndex("ScheduledNotificationId"); + + b.HasIndex("ApplicationId", "ScheduledNotificationId", "DateField") + .IsUnique(); + + b.ToTable("ScheduledNotificationTracking", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("EmailGroups", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroupUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.ToTable("EmailGroupUsers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Emails.EmailLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("BCC") + .IsRequired() + .HasColumnType("text"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("BodyType") + .IsRequired() + .HasColumnType("text"); + + b.Property("CC") + .IsRequired() + .HasColumnType("text"); + + b.Property("ChesHttpStatusCode") + .HasColumnType("text"); + + b.Property("ChesMsgId") + .HasColumnType("uuid"); + + b.Property("ChesResponse") + .IsRequired() + .HasColumnType("text"); + + b.Property("ChesStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("EmailType") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FromAddress") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestIds") + .IsRequired() + .HasColumnType("text"); + + b.Property("Priority") + .IsRequired() + .HasColumnType("text"); + + b.Property("Recipient") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("RetryAttempts") + .HasColumnType("integer"); + + b.Property("ScheduledNotificationId") + .HasColumnType("uuid"); + + b.Property("SendOnDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("SentDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tag") + .IsRequired() + .HasColumnType("text"); + + b.Property("TemplateName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("ToAddress") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("EmailLogs", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Emails.EmailLogAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContentType") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("EmailLogId") + .HasColumnType("uuid"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("FileSize") + .HasColumnType("bigint"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OriginTemplateId") + .HasColumnType("uuid"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TemplateId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EmailLogId"); + + b.HasIndex("S3ObjectKey"); + + b.HasIndex("TemplateId"); + + b.ToTable("EmailLogAttachments", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.EmailTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BodyHTML") + .IsRequired() + .HasColumnType("text"); + + b.Property("BodyText") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("RecipientCategory") + .HasColumnType("text"); + + b.Property("RecipientIdentifier") + .HasColumnType("text"); + + b.Property("SendFrom") + .IsRequired() + .HasColumnType("text"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("EmailTemplates", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.Subscriber", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Subscribers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("SubscriptionGroups", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroupSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("SubscriberId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.HasIndex("SubscriberId"); + + b.ToTable("SubscriptionGroupSubscribers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TemplateVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MapTo") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Token") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("TemplateVariables", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.Trigger", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("InternalName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Triggers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TriggerSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("SubscriptionGroupId") + .HasColumnType("uuid"); + + b.Property("TemplateId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TriggerId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SubscriptionGroupId"); + + b.HasIndex("TemplateId"); + + b.HasIndex("TriggerId"); + + b.ToTable("TriggerSubscriptions", "Notifications"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.AccountCodings.AccountCoding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .HasMaxLength(35) + .HasColumnType("character varying(35)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MinistryClient") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("Responsibility") + .IsRequired() + .HasColumnType("text"); + + b.Property("ServiceLine") + .IsRequired() + .HasColumnType("text"); + + b.Property("Stob") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AccountCodings", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentConfigurations.PaymentConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DefaultAccountCodingId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentIdPrefix") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("PaymentConfigurations", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.ExpenseApproval", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DecisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("DecisionUserId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("PaymentRequestId"); + + b.ToTable("ExpenseApprovals", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountCodingId") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("BatchName") + .IsRequired() + .HasColumnType("text"); + + b.Property("BatchNumber") + .HasColumnType("numeric"); + + b.Property("CancelledBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("CancelledBy"); + + b.Property("CancelledById") + .HasColumnType("uuid") + .HasColumnName("CancelledById"); + + b.Property("CancelledOn") + .HasColumnType("timestamp without time zone") + .HasColumnName("CancelledOn"); + + b.Property("CasHttpStatusCode") + .HasColumnType("integer"); + + b.Property("CasResponse") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContractNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FsbApNotified") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("FsbNotificationEmailLogId") + .HasColumnType("uuid"); + + b.Property("FsbNotificationSentDate") + .HasColumnType("timestamp without time zone"); + + b.Property("InvoiceNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("InvoiceStatus") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("IsRecon") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("PayeeName") + .IsRequired() + .HasColumnType("text"); + + b.Property("PaymentDate") + .HasColumnType("text"); + + b.Property("PaymentNumber") + .HasColumnType("text"); + + b.Property("PaymentStatus") + .HasColumnType("text"); + + b.Property("ReferenceNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("RequesterName") + .IsRequired() + .HasColumnType("text"); + + b.Property("SiteId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("SubmissionConfirmationCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("SupplierName") + .HasColumnType("text"); + + b.Property("SupplierNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("AccountCodingId"); + + b.HasIndex("CorrelationId"); + + b.HasIndex("CreationTime"); + + b.HasIndex("FsbNotificationEmailLogId"); + + b.HasIndex("ReferenceNumber") + .IsUnique(); + + b.HasIndex("SiteId"); + + b.HasIndex("Status"); + + b.HasIndex("TenantId", "CreationTime") + .HasFilter("\"IsDeleted\" = false"); + + b.ToTable("PaymentRequests", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentTags.PaymentTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestId") + .HasColumnType("uuid"); + + b.Property("TagId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("PaymentRequestId"); + + b.HasIndex("TagId"); + + b.ToTable("PaymentTags", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentThresholds.PaymentThreshold", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Threshold") + .HasColumnType("numeric"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("PaymentThresholds", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Site", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddressLine1") + .HasColumnType("text"); + + b.Property("AddressLine2") + .HasColumnType("text"); + + b.Property("AddressLine3") + .HasColumnType("text"); + + b.Property("BankAccount") + .HasColumnType("text"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("Country") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EFTAdvicePref") + .HasColumnType("text"); + + b.Property("EmailAddress") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastUpdatedInCas") + .HasColumnType("timestamp without time zone"); + + b.Property("MarkDeletedInUse") + .HasColumnType("boolean"); + + b.Property("Number") + .IsRequired() + .HasColumnType("text"); + + b.Property("PaymentGroup") + .HasColumnType("integer"); + + b.Property("PostalCode") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("SiteProtected") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("text"); + + b.Property("SupplierId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("SupplierId"); + + b.ToTable("Sites", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Supplier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BusinessNumber") + .HasColumnType("text"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastUpdatedInCAS") + .HasColumnType("timestamp without time zone"); + + b.Property("MailingAddress") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Number") + .HasColumnType("text"); + + b.Property("PostalCode") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("SIN") + .HasColumnType("text"); + + b.Property("StandardIndustryClassification") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("text"); + + b.Property("Subcategory") + .HasColumnType("text"); + + b.Property("SupplierProtected") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Suppliers", "Payments"); + }); + + modelBuilder.Entity("Unity.Reporting.Domain.Configuration.ReportColumnsMap", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Mapping") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RoleStatus") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("ViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ViewStatus") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("ReportColumnsMaps", "Reporting"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.Scoresheet", "Scoresheet") + .WithMany("Instances") + .HasForeignKey("ScoresheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scoresheet"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Answer", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.Question", "Question") + .WithMany("Answers") + .HasForeignKey("QuestionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", null) + .WithMany("Answers") + .HasForeignKey("ScoresheetInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Question"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.ScoresheetSection", "Section") + .WithMany("Fields") + .HasForeignKey("SectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Section"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.Scoresheet", "Scoresheet") + .WithMany("Sections") + .HasForeignKey("ScoresheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scoresheet"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.CustomFieldValue", b => + { + b.HasOne("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", null) + .WithMany("Values") + .HasForeignKey("WorksheetInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetLinks.WorksheetLink", b => + { + b.HasOne("Unity.Flex.Domain.Worksheets.Worksheet", "Worksheet") + .WithMany("Links") + .HasForeignKey("WorksheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Worksheet"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.CustomField", b => + { + b.HasOne("Unity.Flex.Domain.Worksheets.WorksheetSection", "Section") + .WithMany("Fields") + .HasForeignKey("SectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Section"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => + { + b.HasOne("Unity.Flex.Domain.Worksheets.Worksheet", "Worksheet") + .WithMany("Sections") + .HasForeignKey("WorksheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Worksheet"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAddress", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", "Applicant") + .WithMany("ApplicantAddresses") + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("ApplicantAddresses") + .HasForeignKey("ApplicationId"); + + b.Navigation("Applicant"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAgent", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithOne("ApplicantAgent") + .HasForeignKey("Unity.GrantManager.Applications.ApplicantAgent", "ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAttachment", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", "Applicant") + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", "ApplicationForm") + .WithMany() + .HasForeignKey("ApplicationFormId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationStatus", "ApplicationStatus") + .WithMany("Applications") + .HasForeignKey("ApplicationStatusId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Applicant"); + + b.Navigation("ApplicationForm"); + + b.Navigation("ApplicationStatus"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAssignment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("ApplicationAssignments") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", "Assignee") + .WithMany() + .HasForeignKey("AssigneeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Application"); + + b.Navigation("Assignee"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAttachment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationChefsFileAttachment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationContact", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationForm", b => + { + b.HasOne("Unity.GrantManager.Intakes.Intake", null) + .WithMany() + .HasForeignKey("IntakeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) + .WithMany() + .HasForeignKey("ParentFormId") + .OnDelete(DeleteBehavior.NoAction); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormSubmission", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) + .WithMany() + .HasForeignKey("ApplicationFormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormVersion", b => + { + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) + .WithMany() + .HasForeignKey("ApplicationFormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationLink", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany("ApplicationLinks") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationTags", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("ApplicationTags") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.GlobalTag.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Application"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AssessmentAttachment", b => + { + b.HasOne("Unity.GrantManager.Assessments.Assessment", null) + .WithMany() + .HasForeignKey("AssessmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AuditHistory", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.FundingHistory", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.IssueTracking", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ReportsHistory", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Assessments.Assessment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("Assessments") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("AssessorId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicantComment", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("CommenterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicationComment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("CommenterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.AssessmentComment", b => + { + b.HasOne("Unity.GrantManager.Assessments.Assessment", null) + .WithMany() + .HasForeignKey("AssessmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("CommenterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Contacts.ContactLink", b => + { + b.HasOne("Unity.GrantManager.Contacts.Contact", null) + .WithMany() + .HasForeignKey("ContactId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotificationTracking", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Notifications.ScheduledNotification", null) + .WithMany() + .HasForeignKey("ScheduledNotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroupUser", b => + { + b.HasOne("Unity.Notifications.EmailGroups.EmailGroup", null) + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Notifications.Emails.EmailLogAttachment", b => + { + b.HasOne("Unity.Notifications.Emails.EmailLog", null) + .WithMany() + .HasForeignKey("EmailLogId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Unity.Notifications.Templates.EmailTemplate", null) + .WithMany() + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroupSubscription", b => + { + b.HasOne("Unity.Notifications.Templates.SubscriptionGroup", "SubscriptionGroup") + .WithMany() + .HasForeignKey("GroupId"); + + b.HasOne("Unity.Notifications.Templates.Subscriber", "Subscriber") + .WithMany() + .HasForeignKey("SubscriberId"); + + b.Navigation("Subscriber"); + + b.Navigation("SubscriptionGroup"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TriggerSubscription", b => + { + b.HasOne("Unity.Notifications.Templates.SubscriptionGroup", "SubscriptionGroup") + .WithMany() + .HasForeignKey("SubscriptionGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Notifications.Templates.EmailTemplate", "EmailTemplate") + .WithMany() + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Notifications.Templates.Trigger", "Trigger") + .WithMany() + .HasForeignKey("TriggerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EmailTemplate"); + + b.Navigation("SubscriptionGroup"); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.ExpenseApproval", b => + { + b.HasOne("Unity.Payments.Domain.PaymentRequests.PaymentRequest", "PaymentRequest") + .WithMany("ExpenseApprovals") + .HasForeignKey("PaymentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PaymentRequest"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.HasOne("Unity.Payments.Domain.AccountCodings.AccountCoding", "AccountCoding") + .WithMany() + .HasForeignKey("AccountCodingId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("Unity.Payments.Domain.Suppliers.Site", "Site") + .WithMany() + .HasForeignKey("SiteId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("AccountCoding"); + + b.Navigation("Site"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentTags.PaymentTag", b => + { + b.HasOne("Unity.Payments.Domain.PaymentRequests.PaymentRequest", null) + .WithMany("PaymentTags") + .HasForeignKey("PaymentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.GlobalTag.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Site", b => + { + b.HasOne("Unity.Payments.Domain.Suppliers.Supplier", "Supplier") + .WithMany("Sites") + .HasForeignKey("SupplierId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Scoresheet", b => + { + b.Navigation("Instances"); + + b.Navigation("Sections"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => + { + b.Navigation("Fields"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", b => + { + b.Navigation("Values"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.Worksheet", b => + { + b.Navigation("Links"); + + b.Navigation("Sections"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => + { + b.Navigation("Fields"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Applicant", b => + { + b.Navigation("ApplicantAddresses"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.Navigation("ApplicantAddresses"); + + b.Navigation("ApplicantAgent"); + + b.Navigation("ApplicationAssignments"); + + b.Navigation("ApplicationLinks"); + + b.Navigation("ApplicationTags"); + + b.Navigation("Assessments"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationStatus", b => + { + b.Navigation("Applications"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.Navigation("ExpenseApprovals"); + + b.Navigation("PaymentTags"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Supplier", b => + { + b.Navigation("Sites"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260806201536_AB33799_HardenCheckboxGroupReportingViews.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260806201536_AB33799_HardenCheckboxGroupReportingViews.cs new file mode 100644 index 0000000000..766ddd60ff --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260806201536_AB33799_HardenCheckboxGroupReportingViews.cs @@ -0,0 +1,43 @@ +using System; +using System.IO; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Unity.GrantManager.Migrations.TenantMigrations +{ + /// + public partial class AB33799_HardenCheckboxGroupReportingViews : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + // Re-deploy get_worksheet_data / get_consolidated_worksheet_data with a guard around the + // checkbox-group ::jsonb cast: a stored value that is null, '', or otherwise not a JSON + // array (e.g. from a checkbox-group field saved with zero selections) now resolves to + // NULL for that column instead of raising "invalid input syntax for type json" and + // breaking the whole generated view. CREATE OR REPLACE is idempotent. + RunEmbeddedScript(migrationBuilder, "get_worksheet_data.sql"); + RunEmbeddedScript(migrationBuilder, "get_consolidated_worksheet_data.sql"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + // No-op: the prior function bodies are not preserved: refer to source control history + // for the pre-hardening SQL if a rollback is ever required. + } + + private static void RunEmbeddedScript(MigrationBuilder migrationBuilder, string scriptFileName) + { + var assembly = Assembly.GetExecutingAssembly(); + var resourceName = $"Unity.GrantManager.Scripts.{scriptFileName}"; + + using var stream = assembly.GetManifestResourceStream(resourceName) + ?? throw new InvalidOperationException($"Could not find embedded resource: {resourceName}"); + using var reader = new StreamReader(stream); + migrationBuilder.Sql(reader.ReadToEnd()); + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260806202244_AB33799_FixCheckboxGroupEmptyValues.Designer.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260806202244_AB33799_FixCheckboxGroupEmptyValues.Designer.cs new file mode 100644 index 0000000000..3ad2b6d6eb --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260806202244_AB33799_FixCheckboxGroupEmptyValues.Designer.cs @@ -0,0 +1,5273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Unity.GrantManager.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +#nullable disable + +namespace Unity.GrantManager.Migrations.TenantMigrations +{ + [DbContext(typeof(GrantTenantDbContext))] + [Migration("20260806202244_AB33799_FixCheckboxGroupEmptyValues")] + partial class AB33799_FixCheckboxGroupEmptyValues + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.PostgreSql) + .HasAnnotation("ProductVersion", "10.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ScoresheetId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ScoresheetId"); + + b.ToTable("ScoresheetInstances", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Answer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("CurrentValue") + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("ScoresheetInstanceId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("QuestionId"); + + b.HasIndex("ScoresheetInstanceId"); + + b.ToTable("Answers", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Definition") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("Label") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("SectionId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SectionId"); + + b.ToTable("Questions", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Scoresheet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsArchived") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("Published") + .HasColumnType("boolean"); + + b.Property("ReportColumns") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportKeys") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("Scoresheets", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("ScoresheetId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ScoresheetId"); + + b.ToTable("ScoresheetSections", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.CustomFieldValue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("CurrentValue") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CustomFieldId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("WorksheetInstanceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorksheetInstanceId"); + + b.ToTable("CustomFieldValues", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("CurrentValue") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UiAnchor") + .IsRequired() + .HasColumnType("text"); + + b.Property("WorksheetCorrelationId") + .HasColumnType("uuid"); + + b.Property("WorksheetCorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("WorksheetId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("WorksheetInstances", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetLinks.WorksheetLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UiAnchor") + .IsRequired() + .HasColumnType("text"); + + b.Property("WorksheetId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorksheetId"); + + b.ToTable("WorksheetLinks", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.CustomField", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Definition") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("Key") + .IsRequired() + .HasColumnType("text"); + + b.Property("Label") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("SectionId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SectionId"); + + b.ToTable("CustomFields", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.Worksheet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsArchived") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Published") + .HasColumnType("boolean"); + + b.Property("ReportColumns") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportKeys") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("Worksheets", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Definition") + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("WorksheetId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorksheetId"); + + b.ToTable("WorksheetSections", "Flex"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Applicant", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantName") + .IsRequired() + .HasMaxLength(600) + .HasColumnType("character varying(600)"); + + b.Property("ApproxNumberOfEmployees") + .HasColumnType("text"); + + b.Property("AuditComments") + .HasColumnType("text"); + + b.Property("BusinessNumber") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FiscalDay") + .HasColumnType("integer"); + + b.Property("FiscalMonth") + .HasColumnType("text"); + + b.Property("FundingHistoryComments") + .HasColumnType("text"); + + b.Property("IndigenousOrgInd") + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsDuplicated") + .HasColumnType("boolean"); + + b.Property("IssueTrackingComments") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MatchPercentage") + .HasColumnType("numeric"); + + b.Property("NonRegOrgName") + .HasColumnType("text"); + + b.Property("NonRegisteredBusinessName") + .HasColumnType("text"); + + b.Property("OrgName") + .HasColumnType("text"); + + b.Property("OrgNumber") + .HasColumnType("text"); + + b.Property("OrgStatus") + .HasColumnType("text"); + + b.Property("OrganizationType") + .HasColumnType("text"); + + b.Property("RedStop") + .HasColumnType("boolean"); + + b.Property("ReportsComments") + .HasColumnType("text"); + + b.Property("Sector") + .HasColumnType("text"); + + b.Property("SectorSubSectorIndustryDesc") + .HasColumnType("text"); + + b.Property("StartedOperatingDate") + .HasColumnType("date"); + + b.Property("Status") + .HasColumnType("text"); + + b.Property("SubSector") + .HasColumnType("text"); + + b.Property("SupplierId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UnityApplicantId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantName"); + + b.HasIndex("OrgName"); + + b.HasIndex("OrgNumber"); + + b.HasIndex("Status"); + + b.HasIndex("SupplierId"); + + b.HasIndex("TenantId"); + + b.HasIndex("UnityApplicantId"); + + b.HasIndex("TenantId", "IsDeleted", "CreationTime") + .HasFilter("\"IsDeleted\" = false"); + + b.ToTable("Applicants", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAddress", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AddressType") + .HasColumnType("integer"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("Country") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Postal") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("Street") + .HasColumnType("text"); + + b.Property("Street2") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Unit") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicantAddresses", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAgent", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("BceidBusinessGuid") + .HasColumnType("uuid"); + + b.Property("BceidBusinessName") + .HasColumnType("text"); + + b.Property("BceidUserGuid") + .HasColumnType("uuid"); + + b.Property("BceidUserName") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContactOrder") + .HasColumnType("integer"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IdentityEmail") + .HasColumnType("text"); + + b.Property("IdentityName") + .HasColumnType("text"); + + b.Property("IdentityProvider") + .HasColumnType("text"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsConfirmed") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OidcSubUser") + .HasColumnType("text"); + + b.Property("Phone") + .HasColumnType("text"); + + b.Property("Phone2") + .HasColumnType("text"); + + b.Property("Phone2Extension") + .HasColumnType("text"); + + b.Property("PhoneExtension") + .HasColumnType("text"); + + b.Property("RoleForApplicant") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationId") + .IsUnique(); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicantAgents", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("ApplicantAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AIAnalysis") + .HasColumnType("text"); + + b.Property("AIScoresheetAnswers") + .HasColumnType("jsonb"); + + b.Property("Acquisition") + .HasColumnType("text"); + + b.Property("ApplicantElectoralDistrict") + .HasColumnType("text"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("ApplicationStatusId") + .HasColumnType("uuid"); + + b.Property("ApprovedAmount") + .HasColumnType("numeric"); + + b.Property("AssessmentResultDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AssessmentResultStatus") + .HasColumnType("text"); + + b.Property("AssessmentStartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("Community") + .HasColumnType("text"); + + b.Property("CommunityPopulation") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContractExecutionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ContractNumber") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeclineRational") + .HasColumnType("text"); + + b.Property("DefaultSiteId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("DueDate") + .HasColumnType("timestamp without time zone"); + + b.Property("DueDiligenceStatus") + .HasColumnType("text"); + + b.Property("EconomicRegion") + .HasColumnType("text"); + + b.Property("ElectoralDistrict") + .HasColumnType("text"); + + b.Property("ExternalStatusVisibility") + .HasColumnType("boolean"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FinalDecisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Forestry") + .HasColumnType("text"); + + b.Property("ForestryFocus") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LikelihoodOfFunding") + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("NotificationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property("Payload") + .HasColumnType("jsonb"); + + b.Property("PercentageTotalProjectBudget") + .HasColumnType("double precision"); + + b.Property("Place") + .HasColumnType("text"); + + b.Property("ProjectEndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ProjectFundingTotal") + .HasColumnType("numeric"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProjectStartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ProjectSummary") + .HasColumnType("text"); + + b.Property("ProposalDate") + .HasColumnType("timestamp without time zone"); + + b.Property("RecommendedAmount") + .HasColumnType("numeric"); + + b.Property("ReferenceNo") + .IsRequired() + .HasColumnType("text"); + + b.Property("RegionalDistrict") + .HasColumnType("text"); + + b.Property("RequestedAmount") + .HasColumnType("numeric"); + + b.Property("RiskRanking") + .HasColumnType("text"); + + b.Property("SigningAuthorityBusinessPhone") + .HasColumnType("text"); + + b.Property("SigningAuthorityCellPhone") + .HasColumnType("text"); + + b.Property("SigningAuthorityEmail") + .HasColumnType("text"); + + b.Property("SigningAuthorityFullName") + .HasColumnType("text"); + + b.Property("SigningAuthorityTitle") + .HasColumnType("text"); + + b.Property("SubStatus") + .HasColumnType("text"); + + b.Property("SubmissionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TotalProjectBudget") + .HasColumnType("numeric"); + + b.Property("TotalScore") + .HasColumnType("integer"); + + b.Property("UnityApplicationId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationFormId"); + + b.HasIndex("ApplicationStatusId"); + + b.HasIndex("OwnerId"); + + b.HasIndex("ReferenceNo"); + + b.HasIndex("TenantId", "SubmissionDate") + .HasFilter("\"IsDeleted\" = false"); + + b.ToTable("Applications", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAssignment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("AssigneeId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Duty") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("AssigneeId"); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicationAssignments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationChefsFileAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AISummary") + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ChefsFileId") + .HasColumnType("text"); + + b.Property("ChefsSubmissionId") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationChefsFileAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationContact", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContactEmail") + .HasColumnType("text"); + + b.Property("ContactFullName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContactMobilePhone") + .HasColumnType("text"); + + b.Property("ContactTitle") + .HasColumnType("text"); + + b.Property("ContactType") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContactWorkPhone") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationContact", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationForm", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountCodingId") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .HasColumnType("text"); + + b.Property("ApplicationFormDescription") + .HasColumnType("text"); + + b.Property("ApplicationFormName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AttemptedConnectionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AutomaticallyGenerateAIAnalysis") + .HasColumnType("boolean"); + + b.Property("AvailableChefsFields") + .HasColumnType("text"); + + b.Property("Category") + .HasColumnType("text"); + + b.Property("ChefsApplicationFormGuid") + .HasColumnType("text"); + + b.Property("ChefsCriteriaFormGuid") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ConnectionHttpStatus") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DefaultPaymentGroup") + .HasColumnType("integer"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ElectoralDistrictAddressType") + .HasColumnType("integer"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormHierarchy") + .HasColumnType("integer"); + + b.Property("IntakeId") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("IsDirectApproval") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ManuallyInitiateAIAnalysis") + .HasColumnType("boolean"); + + b.Property("ParentFormId") + .HasColumnType("uuid"); + + b.Property("Payable") + .HasColumnType("boolean"); + + b.Property("PaymentApprovalThreshold") + .HasColumnType("numeric"); + + b.Property("Prefix") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("PreventPayment") + .HasColumnType("boolean"); + + b.Property("ScoresheetId") + .HasColumnType("uuid"); + + b.Property("SuffixType") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IntakeId"); + + b.HasIndex("ParentFormId"); + + b.HasIndex("TenantId", "IsDeleted") + .HasFilter("\"IsDeleted\" = false"); + + b.ToTable("ApplicationForms", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormSubmission", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormVersionId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ChefsSubmissionGuid") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormVersionId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OidcSub") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Submission") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationFormId"); + + b.ToTable("ApplicationFormSubmissions", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormVersion", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("AvailableChefsFields") + .HasColumnType("text"); + + b.Property("ChefsApplicationFormGuid") + .HasColumnType("text"); + + b.Property("ChefsFormVersionGuid") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormSchema") + .HasColumnType("jsonb"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Published") + .HasColumnType("boolean"); + + b.Property("ReportColumns") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportKeys") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("SubmissionHeaderMapping") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationFormId"); + + b.ToTable("ApplicationFormVersion", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationLink", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LinkType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Related"); + + b.Property("LinkedApplicationId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicationLinks", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationStatus", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExternalStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("InternalStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("NotifiedStatus") + .HasColumnType("text"); + + b.Property("StatusCode") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("StatusCode") + .IsUnique(); + + b.ToTable("ApplicationStatuses", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationTags", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TagId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("TagId"); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicationTags", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AssessmentAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AssessmentId"); + + b.ToTable("AssessmentAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AuditHistory", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("AuditDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AuditNote") + .HasColumnType("text"); + + b.Property("AuditStatus") + .HasColumnType("text"); + + b.Property("AuditTrackingNumber") + .HasColumnType("text"); + + b.Property("AuditorName") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("AuditHistories", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.FundingHistory", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApprovedAmount") + .HasColumnType("numeric"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FundingNotes") + .HasColumnType("text"); + + b.Property("FundingYear") + .HasColumnType("text"); + + b.Property("GrantCategory") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OneTimeConsideration") + .HasColumnType("numeric"); + + b.Property("PaidDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ReconsiderationAmount") + .HasColumnType("numeric"); + + b.Property("RenewedFunding") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TotalGrantAmount") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("FundingHistories", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.IssueTracking", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IssueDescription") + .HasColumnType("text"); + + b.Property("IssueHeading") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ResolutionNote") + .HasColumnType("text"); + + b.Property("Resolved") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Year") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("IssueTrackings", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ReportsHistory", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FiscalYear") + .HasColumnType("text"); + + b.Property("IncompleteReport") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("Outstanding") + .HasColumnType("boolean"); + + b.Property("ReportDate") + .HasColumnType("timestamp without time zone"); + + b.Property("SignedOff") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("ReportsHistories", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Assessments.Assessment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ApprovalRecommended") + .HasColumnType("boolean"); + + b.Property("AssessorId") + .HasColumnType("uuid"); + + b.Property("CleanGrowth") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("EconomicImpact") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FinancialAnalysis") + .HasColumnType("integer"); + + b.Property("InclusiveGrowth") + .HasColumnType("integer"); + + b.Property("IsAiAssessment") + .HasColumnType("boolean"); + + b.Property("IsComplete") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("AssessorId"); + + b.ToTable("Assessments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicantComment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CommenterId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PinDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("CommenterId"); + + b.ToTable("ApplicantComments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicationComment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CommenterId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PinDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("CommenterId"); + + b.ToTable("ApplicationComments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.AssessmentComment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CommenterId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PinDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("AssessmentId"); + + b.HasIndex("CommenterId"); + + b.ToTable("AssessmentComments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Contacts.Contact", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Email") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("HomePhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MobilePhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("WorkPhoneExtension") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("WorkPhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.ToTable("Contacts", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Contacts.ContactLink", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContactId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsPrimary") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("RelatedEntityId") + .HasColumnType("uuid"); + + b.Property("RelatedEntityType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Role") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("RelatedEntityType", "RelatedEntityId"); + + b.HasIndex("ContactId", "RelatedEntityType", "RelatedEntityId"); + + b.ToTable("ContactLinks", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.GlobalTag.Tag", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Tags", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Identity.Person", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Badge") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FullName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OidcDisplayName") + .IsRequired() + .HasColumnType("text"); + + b.Property("OidcSub") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("OidcSub"); + + b.HasIndex("TenantId"); + + b.ToTable("Persons", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Intakes.Intake", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Budget") + .HasColumnType("double precision"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IntakeName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("StartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Intakes", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotification", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationStatus") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ApplicationStatusId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DateField") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EmailTemplateId") + .HasColumnType("uuid"); + + b.Property("EventType") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("RecipientCategory") + .HasColumnType("text"); + + b.Property("RecipientIdentifier") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TriggerDetail") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("TriggerType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("ScheduledNotifications", "Notifications"); + }); + + modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotificationTracking", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatorId") + .HasColumnType("uuid"); + + b.Property("DateField") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("NotificationSentDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ScheduledNotificationId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("CreationTime"); + + b.HasIndex("ScheduledNotificationId"); + + b.HasIndex("ApplicationId", "ScheduledNotificationId", "DateField") + .IsUnique(); + + b.ToTable("ScheduledNotificationTracking", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("EmailGroups", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroupUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.ToTable("EmailGroupUsers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Emails.EmailLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("BCC") + .IsRequired() + .HasColumnType("text"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("BodyType") + .IsRequired() + .HasColumnType("text"); + + b.Property("CC") + .IsRequired() + .HasColumnType("text"); + + b.Property("ChesHttpStatusCode") + .HasColumnType("text"); + + b.Property("ChesMsgId") + .HasColumnType("uuid"); + + b.Property("ChesResponse") + .IsRequired() + .HasColumnType("text"); + + b.Property("ChesStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("EmailType") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FromAddress") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestIds") + .IsRequired() + .HasColumnType("text"); + + b.Property("Priority") + .IsRequired() + .HasColumnType("text"); + + b.Property("Recipient") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("RetryAttempts") + .HasColumnType("integer"); + + b.Property("ScheduledNotificationId") + .HasColumnType("uuid"); + + b.Property("SendOnDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("SentDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tag") + .IsRequired() + .HasColumnType("text"); + + b.Property("TemplateName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("ToAddress") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("EmailLogs", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Emails.EmailLogAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContentType") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("EmailLogId") + .HasColumnType("uuid"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("FileSize") + .HasColumnType("bigint"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OriginTemplateId") + .HasColumnType("uuid"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TemplateId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EmailLogId"); + + b.HasIndex("S3ObjectKey"); + + b.HasIndex("TemplateId"); + + b.ToTable("EmailLogAttachments", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.EmailTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BodyHTML") + .IsRequired() + .HasColumnType("text"); + + b.Property("BodyText") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("RecipientCategory") + .HasColumnType("text"); + + b.Property("RecipientIdentifier") + .HasColumnType("text"); + + b.Property("SendFrom") + .IsRequired() + .HasColumnType("text"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("EmailTemplates", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.Subscriber", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Subscribers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("SubscriptionGroups", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroupSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("SubscriberId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.HasIndex("SubscriberId"); + + b.ToTable("SubscriptionGroupSubscribers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TemplateVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MapTo") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Token") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("TemplateVariables", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.Trigger", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("InternalName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Triggers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TriggerSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("SubscriptionGroupId") + .HasColumnType("uuid"); + + b.Property("TemplateId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TriggerId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SubscriptionGroupId"); + + b.HasIndex("TemplateId"); + + b.HasIndex("TriggerId"); + + b.ToTable("TriggerSubscriptions", "Notifications"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.AccountCodings.AccountCoding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .HasMaxLength(35) + .HasColumnType("character varying(35)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MinistryClient") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("Responsibility") + .IsRequired() + .HasColumnType("text"); + + b.Property("ServiceLine") + .IsRequired() + .HasColumnType("text"); + + b.Property("Stob") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AccountCodings", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentConfigurations.PaymentConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DefaultAccountCodingId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentIdPrefix") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("PaymentConfigurations", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.ExpenseApproval", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DecisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("DecisionUserId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("PaymentRequestId"); + + b.ToTable("ExpenseApprovals", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountCodingId") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("BatchName") + .IsRequired() + .HasColumnType("text"); + + b.Property("BatchNumber") + .HasColumnType("numeric"); + + b.Property("CancelledBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("CancelledBy"); + + b.Property("CancelledById") + .HasColumnType("uuid") + .HasColumnName("CancelledById"); + + b.Property("CancelledOn") + .HasColumnType("timestamp without time zone") + .HasColumnName("CancelledOn"); + + b.Property("CasHttpStatusCode") + .HasColumnType("integer"); + + b.Property("CasResponse") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContractNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FsbApNotified") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("FsbNotificationEmailLogId") + .HasColumnType("uuid"); + + b.Property("FsbNotificationSentDate") + .HasColumnType("timestamp without time zone"); + + b.Property("InvoiceNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("InvoiceStatus") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("IsRecon") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("PayeeName") + .IsRequired() + .HasColumnType("text"); + + b.Property("PaymentDate") + .HasColumnType("text"); + + b.Property("PaymentNumber") + .HasColumnType("text"); + + b.Property("PaymentStatus") + .HasColumnType("text"); + + b.Property("ReferenceNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("RequesterName") + .IsRequired() + .HasColumnType("text"); + + b.Property("SiteId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("SubmissionConfirmationCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("SupplierName") + .HasColumnType("text"); + + b.Property("SupplierNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("AccountCodingId"); + + b.HasIndex("CorrelationId"); + + b.HasIndex("CreationTime"); + + b.HasIndex("FsbNotificationEmailLogId"); + + b.HasIndex("ReferenceNumber") + .IsUnique(); + + b.HasIndex("SiteId"); + + b.HasIndex("Status"); + + b.HasIndex("TenantId", "CreationTime") + .HasFilter("\"IsDeleted\" = false"); + + b.ToTable("PaymentRequests", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentTags.PaymentTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestId") + .HasColumnType("uuid"); + + b.Property("TagId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("PaymentRequestId"); + + b.HasIndex("TagId"); + + b.ToTable("PaymentTags", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentThresholds.PaymentThreshold", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Threshold") + .HasColumnType("numeric"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("PaymentThresholds", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Site", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddressLine1") + .HasColumnType("text"); + + b.Property("AddressLine2") + .HasColumnType("text"); + + b.Property("AddressLine3") + .HasColumnType("text"); + + b.Property("BankAccount") + .HasColumnType("text"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("Country") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EFTAdvicePref") + .HasColumnType("text"); + + b.Property("EmailAddress") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastUpdatedInCas") + .HasColumnType("timestamp without time zone"); + + b.Property("MarkDeletedInUse") + .HasColumnType("boolean"); + + b.Property("Number") + .IsRequired() + .HasColumnType("text"); + + b.Property("PaymentGroup") + .HasColumnType("integer"); + + b.Property("PostalCode") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("SiteProtected") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("text"); + + b.Property("SupplierId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("SupplierId"); + + b.ToTable("Sites", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Supplier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BusinessNumber") + .HasColumnType("text"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastUpdatedInCAS") + .HasColumnType("timestamp without time zone"); + + b.Property("MailingAddress") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Number") + .HasColumnType("text"); + + b.Property("PostalCode") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("SIN") + .HasColumnType("text"); + + b.Property("StandardIndustryClassification") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("text"); + + b.Property("Subcategory") + .HasColumnType("text"); + + b.Property("SupplierProtected") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Suppliers", "Payments"); + }); + + modelBuilder.Entity("Unity.Reporting.Domain.Configuration.ReportColumnsMap", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Mapping") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RoleStatus") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("ViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ViewStatus") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("ReportColumnsMaps", "Reporting"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.Scoresheet", "Scoresheet") + .WithMany("Instances") + .HasForeignKey("ScoresheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scoresheet"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Answer", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.Question", "Question") + .WithMany("Answers") + .HasForeignKey("QuestionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", null) + .WithMany("Answers") + .HasForeignKey("ScoresheetInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Question"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.ScoresheetSection", "Section") + .WithMany("Fields") + .HasForeignKey("SectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Section"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.Scoresheet", "Scoresheet") + .WithMany("Sections") + .HasForeignKey("ScoresheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scoresheet"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.CustomFieldValue", b => + { + b.HasOne("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", null) + .WithMany("Values") + .HasForeignKey("WorksheetInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetLinks.WorksheetLink", b => + { + b.HasOne("Unity.Flex.Domain.Worksheets.Worksheet", "Worksheet") + .WithMany("Links") + .HasForeignKey("WorksheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Worksheet"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.CustomField", b => + { + b.HasOne("Unity.Flex.Domain.Worksheets.WorksheetSection", "Section") + .WithMany("Fields") + .HasForeignKey("SectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Section"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => + { + b.HasOne("Unity.Flex.Domain.Worksheets.Worksheet", "Worksheet") + .WithMany("Sections") + .HasForeignKey("WorksheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Worksheet"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAddress", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", "Applicant") + .WithMany("ApplicantAddresses") + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("ApplicantAddresses") + .HasForeignKey("ApplicationId"); + + b.Navigation("Applicant"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAgent", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithOne("ApplicantAgent") + .HasForeignKey("Unity.GrantManager.Applications.ApplicantAgent", "ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAttachment", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", "Applicant") + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", "ApplicationForm") + .WithMany() + .HasForeignKey("ApplicationFormId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationStatus", "ApplicationStatus") + .WithMany("Applications") + .HasForeignKey("ApplicationStatusId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Applicant"); + + b.Navigation("ApplicationForm"); + + b.Navigation("ApplicationStatus"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAssignment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("ApplicationAssignments") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", "Assignee") + .WithMany() + .HasForeignKey("AssigneeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Application"); + + b.Navigation("Assignee"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAttachment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationChefsFileAttachment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationContact", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationForm", b => + { + b.HasOne("Unity.GrantManager.Intakes.Intake", null) + .WithMany() + .HasForeignKey("IntakeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) + .WithMany() + .HasForeignKey("ParentFormId") + .OnDelete(DeleteBehavior.NoAction); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormSubmission", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) + .WithMany() + .HasForeignKey("ApplicationFormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormVersion", b => + { + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) + .WithMany() + .HasForeignKey("ApplicationFormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationLink", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany("ApplicationLinks") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationTags", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("ApplicationTags") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.GlobalTag.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Application"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AssessmentAttachment", b => + { + b.HasOne("Unity.GrantManager.Assessments.Assessment", null) + .WithMany() + .HasForeignKey("AssessmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AuditHistory", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.FundingHistory", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.IssueTracking", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ReportsHistory", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Assessments.Assessment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("Assessments") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("AssessorId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicantComment", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("CommenterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicationComment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("CommenterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.AssessmentComment", b => + { + b.HasOne("Unity.GrantManager.Assessments.Assessment", null) + .WithMany() + .HasForeignKey("AssessmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("CommenterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Contacts.ContactLink", b => + { + b.HasOne("Unity.GrantManager.Contacts.Contact", null) + .WithMany() + .HasForeignKey("ContactId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotificationTracking", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Notifications.ScheduledNotification", null) + .WithMany() + .HasForeignKey("ScheduledNotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroupUser", b => + { + b.HasOne("Unity.Notifications.EmailGroups.EmailGroup", null) + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Notifications.Emails.EmailLogAttachment", b => + { + b.HasOne("Unity.Notifications.Emails.EmailLog", null) + .WithMany() + .HasForeignKey("EmailLogId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Unity.Notifications.Templates.EmailTemplate", null) + .WithMany() + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroupSubscription", b => + { + b.HasOne("Unity.Notifications.Templates.SubscriptionGroup", "SubscriptionGroup") + .WithMany() + .HasForeignKey("GroupId"); + + b.HasOne("Unity.Notifications.Templates.Subscriber", "Subscriber") + .WithMany() + .HasForeignKey("SubscriberId"); + + b.Navigation("Subscriber"); + + b.Navigation("SubscriptionGroup"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TriggerSubscription", b => + { + b.HasOne("Unity.Notifications.Templates.SubscriptionGroup", "SubscriptionGroup") + .WithMany() + .HasForeignKey("SubscriptionGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Notifications.Templates.EmailTemplate", "EmailTemplate") + .WithMany() + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Notifications.Templates.Trigger", "Trigger") + .WithMany() + .HasForeignKey("TriggerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EmailTemplate"); + + b.Navigation("SubscriptionGroup"); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.ExpenseApproval", b => + { + b.HasOne("Unity.Payments.Domain.PaymentRequests.PaymentRequest", "PaymentRequest") + .WithMany("ExpenseApprovals") + .HasForeignKey("PaymentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PaymentRequest"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.HasOne("Unity.Payments.Domain.AccountCodings.AccountCoding", "AccountCoding") + .WithMany() + .HasForeignKey("AccountCodingId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("Unity.Payments.Domain.Suppliers.Site", "Site") + .WithMany() + .HasForeignKey("SiteId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("AccountCoding"); + + b.Navigation("Site"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentTags.PaymentTag", b => + { + b.HasOne("Unity.Payments.Domain.PaymentRequests.PaymentRequest", null) + .WithMany("PaymentTags") + .HasForeignKey("PaymentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.GlobalTag.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Site", b => + { + b.HasOne("Unity.Payments.Domain.Suppliers.Supplier", "Supplier") + .WithMany("Sites") + .HasForeignKey("SupplierId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Scoresheet", b => + { + b.Navigation("Instances"); + + b.Navigation("Sections"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => + { + b.Navigation("Fields"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", b => + { + b.Navigation("Values"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.Worksheet", b => + { + b.Navigation("Links"); + + b.Navigation("Sections"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => + { + b.Navigation("Fields"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Applicant", b => + { + b.Navigation("ApplicantAddresses"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.Navigation("ApplicantAddresses"); + + b.Navigation("ApplicantAgent"); + + b.Navigation("ApplicationAssignments"); + + b.Navigation("ApplicationLinks"); + + b.Navigation("ApplicationTags"); + + b.Navigation("Assessments"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationStatus", b => + { + b.Navigation("Applications"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.Navigation("ExpenseApprovals"); + + b.Navigation("PaymentTags"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Supplier", b => + { + b.Navigation("Sites"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260806202244_AB33799_FixCheckboxGroupEmptyValues.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260806202244_AB33799_FixCheckboxGroupEmptyValues.cs new file mode 100644 index 0000000000..e518f36760 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260806202244_AB33799_FixCheckboxGroupEmptyValues.cs @@ -0,0 +1,71 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Unity.GrantManager.Migrations.TenantMigrations +{ + /// + public partial class AB33799_FixCheckboxGroupEmptyValues : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + // One-off backfill for AB#33799: root-level checkbox-group fields (e.g. HowDidYouHearAboutUs, + // TrainingEducation, BeneficiaryGroups) that were saved with an empty string / non-JSON-array + // value instead of null. That shape breaks the reporting views' ::jsonb cast with + // "invalid input syntax for type json". Reuses the checkbox-group field-key detection from + // "Reporting"."ReportColumnsMaps" to find every worksheet/field affected, not just the ones + // already found manually on UAT. Idempotent: once fixed, the value is JSON null and no + // longer matches the WHERE clause, so re-running this migration is a no-op. + migrationBuilder.Sql(@" + WITH checkboxgroup_fields AS ( + SELECT DISTINCT + split_part( + COALESCE( + CASE + WHEN row_data->>'DataPath' ~ '^\(' + THEN regexp_replace(row_data->>'DataPath', '^\([^)]+\)', '') + ELSE row_data->>'DataPath' + END, + row_data->>'PropertyName' + ), '->', 1 + ) AS field_key + FROM ""Reporting"".""ReportColumnsMaps"" rcm, + jsonb_array_elements(rcm.""Mapping""->'Rows') AS row_data + WHERE row_data->>'TypePath' ILIKE '%checkboxgroup%' + AND row_data->>'TypePath' NOT ILIKE '%datagrid%' + ) + UPDATE ""Flex"".""WorksheetInstances"" wi + SET ""CurrentValue"" = jsonb_set( + wi.""CurrentValue"", + '{values}', + ( + SELECT jsonb_agg( + CASE + WHEN elem->>'key' IN (SELECT field_key FROM checkboxgroup_fields) + AND elem->>'value' IS NOT NULL + AND jsonb_typeof(""Reporting"".safe_to_jsonb(elem->>'value')) IS DISTINCT FROM 'array' + THEN jsonb_set(elem, '{value}', 'null'::jsonb) + ELSE elem + END + ) + FROM jsonb_array_elements(wi.""CurrentValue""->'values') AS elem + ) + ) + WHERE EXISTS ( + SELECT 1 + FROM jsonb_array_elements(wi.""CurrentValue""->'values') AS elem + WHERE elem->>'key' IN (SELECT field_key FROM checkboxgroup_fields) + AND elem->>'value' IS NOT NULL + AND jsonb_typeof(""Reporting"".safe_to_jsonb(elem->>'value')) IS DISTINCT FROM 'array' + );"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + // No-op: the original (invalid) empty-string values are not preserved, and re-introducing + // them would just reproduce the bug this migration fixes. + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260807224046_AB33996_AddApplicantFiscalYearEndRestricted.Designer.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260807224046_AB33996_AddApplicantFiscalYearEndRestricted.Designer.cs new file mode 100644 index 0000000000..5994040904 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260807224046_AB33996_AddApplicantFiscalYearEndRestricted.Designer.cs @@ -0,0 +1,5277 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Unity.GrantManager.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +#nullable disable + +namespace Unity.GrantManager.Migrations.TenantMigrations +{ + [DbContext(typeof(GrantTenantDbContext))] + [Migration("20260807224046_AB33996_AddApplicantFiscalYearEndRestricted")] + partial class AB33996_AddApplicantFiscalYearEndRestricted + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.PostgreSql) + .HasAnnotation("ProductVersion", "10.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ScoresheetId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ScoresheetId"); + + b.ToTable("ScoresheetInstances", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Answer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("CurrentValue") + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("ScoresheetInstanceId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("QuestionId"); + + b.HasIndex("ScoresheetInstanceId"); + + b.ToTable("Answers", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Definition") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("Label") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("SectionId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SectionId"); + + b.ToTable("Questions", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Scoresheet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsArchived") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("Published") + .HasColumnType("boolean"); + + b.Property("ReportColumns") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportKeys") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("Scoresheets", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("ScoresheetId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ScoresheetId"); + + b.ToTable("ScoresheetSections", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.CustomFieldValue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("CurrentValue") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CustomFieldId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("WorksheetInstanceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorksheetInstanceId"); + + b.ToTable("CustomFieldValues", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("CurrentValue") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UiAnchor") + .IsRequired() + .HasColumnType("text"); + + b.Property("WorksheetCorrelationId") + .HasColumnType("uuid"); + + b.Property("WorksheetCorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("WorksheetId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("WorksheetInstances", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetLinks.WorksheetLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UiAnchor") + .IsRequired() + .HasColumnType("text"); + + b.Property("WorksheetId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorksheetId"); + + b.ToTable("WorksheetLinks", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.CustomField", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Definition") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("Key") + .IsRequired() + .HasColumnType("text"); + + b.Property("Label") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("SectionId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SectionId"); + + b.ToTable("CustomFields", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.Worksheet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsArchived") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Published") + .HasColumnType("boolean"); + + b.Property("ReportColumns") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportKeys") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("Worksheets", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Definition") + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("WorksheetId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorksheetId"); + + b.ToTable("WorksheetSections", "Flex"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Applicant", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantName") + .IsRequired() + .HasMaxLength(600) + .HasColumnType("character varying(600)"); + + b.Property("ApproxNumberOfEmployees") + .HasColumnType("text"); + + b.Property("AuditComments") + .HasColumnType("text"); + + b.Property("BusinessNumber") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FiscalDay") + .HasColumnType("integer"); + + b.Property("FiscalMonth") + .HasColumnType("text"); + + b.Property("FiscalYearEnd") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("date"); + + b.Property("FundingHistoryComments") + .HasColumnType("text"); + + b.Property("IndigenousOrgInd") + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsDuplicated") + .HasColumnType("boolean"); + + b.Property("IssueTrackingComments") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MatchPercentage") + .HasColumnType("numeric"); + + b.Property("NonRegOrgName") + .HasColumnType("text"); + + b.Property("NonRegisteredBusinessName") + .HasColumnType("text"); + + b.Property("OrgName") + .HasColumnType("text"); + + b.Property("OrgNumber") + .HasColumnType("text"); + + b.Property("OrgStatus") + .HasColumnType("text"); + + b.Property("OrganizationType") + .HasColumnType("text"); + + b.Property("RedStop") + .HasColumnType("boolean"); + + b.Property("ReportsComments") + .HasColumnType("text"); + + b.Property("Sector") + .HasColumnType("text"); + + b.Property("SectorSubSectorIndustryDesc") + .HasColumnType("text"); + + b.Property("StartedOperatingDate") + .HasColumnType("date"); + + b.Property("Status") + .HasColumnType("text"); + + b.Property("SubSector") + .HasColumnType("text"); + + b.Property("SupplierId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UnityApplicantId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantName"); + + b.HasIndex("OrgName"); + + b.HasIndex("OrgNumber"); + + b.HasIndex("Status"); + + b.HasIndex("SupplierId"); + + b.HasIndex("TenantId"); + + b.HasIndex("UnityApplicantId"); + + b.HasIndex("TenantId", "IsDeleted", "CreationTime") + .HasFilter("\"IsDeleted\" = false"); + + b.ToTable("Applicants", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAddress", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AddressType") + .HasColumnType("integer"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("Country") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Postal") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("Street") + .HasColumnType("text"); + + b.Property("Street2") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Unit") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicantAddresses", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAgent", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("BceidBusinessGuid") + .HasColumnType("uuid"); + + b.Property("BceidBusinessName") + .HasColumnType("text"); + + b.Property("BceidUserGuid") + .HasColumnType("uuid"); + + b.Property("BceidUserName") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContactOrder") + .HasColumnType("integer"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IdentityEmail") + .HasColumnType("text"); + + b.Property("IdentityName") + .HasColumnType("text"); + + b.Property("IdentityProvider") + .HasColumnType("text"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsConfirmed") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OidcSubUser") + .HasColumnType("text"); + + b.Property("Phone") + .HasColumnType("text"); + + b.Property("Phone2") + .HasColumnType("text"); + + b.Property("Phone2Extension") + .HasColumnType("text"); + + b.Property("PhoneExtension") + .HasColumnType("text"); + + b.Property("RoleForApplicant") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationId") + .IsUnique(); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicantAgents", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("ApplicantAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AIAnalysis") + .HasColumnType("text"); + + b.Property("AIScoresheetAnswers") + .HasColumnType("jsonb"); + + b.Property("Acquisition") + .HasColumnType("text"); + + b.Property("ApplicantElectoralDistrict") + .HasColumnType("text"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("ApplicationStatusId") + .HasColumnType("uuid"); + + b.Property("ApprovedAmount") + .HasColumnType("numeric"); + + b.Property("AssessmentResultDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AssessmentResultStatus") + .HasColumnType("text"); + + b.Property("AssessmentStartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("Community") + .HasColumnType("text"); + + b.Property("CommunityPopulation") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContractExecutionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ContractNumber") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeclineRational") + .HasColumnType("text"); + + b.Property("DefaultSiteId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("DueDate") + .HasColumnType("timestamp without time zone"); + + b.Property("DueDiligenceStatus") + .HasColumnType("text"); + + b.Property("EconomicRegion") + .HasColumnType("text"); + + b.Property("ElectoralDistrict") + .HasColumnType("text"); + + b.Property("ExternalStatusVisibility") + .HasColumnType("boolean"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FinalDecisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Forestry") + .HasColumnType("text"); + + b.Property("ForestryFocus") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LikelihoodOfFunding") + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("NotificationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property("Payload") + .HasColumnType("jsonb"); + + b.Property("PercentageTotalProjectBudget") + .HasColumnType("double precision"); + + b.Property("Place") + .HasColumnType("text"); + + b.Property("ProjectEndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ProjectFundingTotal") + .HasColumnType("numeric"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProjectStartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ProjectSummary") + .HasColumnType("text"); + + b.Property("ProposalDate") + .HasColumnType("timestamp without time zone"); + + b.Property("RecommendedAmount") + .HasColumnType("numeric"); + + b.Property("ReferenceNo") + .IsRequired() + .HasColumnType("text"); + + b.Property("RegionalDistrict") + .HasColumnType("text"); + + b.Property("RequestedAmount") + .HasColumnType("numeric"); + + b.Property("RiskRanking") + .HasColumnType("text"); + + b.Property("SigningAuthorityBusinessPhone") + .HasColumnType("text"); + + b.Property("SigningAuthorityCellPhone") + .HasColumnType("text"); + + b.Property("SigningAuthorityEmail") + .HasColumnType("text"); + + b.Property("SigningAuthorityFullName") + .HasColumnType("text"); + + b.Property("SigningAuthorityTitle") + .HasColumnType("text"); + + b.Property("SubStatus") + .HasColumnType("text"); + + b.Property("SubmissionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TotalProjectBudget") + .HasColumnType("numeric"); + + b.Property("TotalScore") + .HasColumnType("integer"); + + b.Property("UnityApplicationId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationFormId"); + + b.HasIndex("ApplicationStatusId"); + + b.HasIndex("OwnerId"); + + b.HasIndex("ReferenceNo"); + + b.HasIndex("TenantId", "SubmissionDate") + .HasFilter("\"IsDeleted\" = false"); + + b.ToTable("Applications", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAssignment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("AssigneeId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Duty") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("AssigneeId"); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicationAssignments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationChefsFileAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AISummary") + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ChefsFileId") + .HasColumnType("text"); + + b.Property("ChefsSubmissionId") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationChefsFileAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationContact", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContactEmail") + .HasColumnType("text"); + + b.Property("ContactFullName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContactMobilePhone") + .HasColumnType("text"); + + b.Property("ContactTitle") + .HasColumnType("text"); + + b.Property("ContactType") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContactWorkPhone") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationContact", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationForm", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountCodingId") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .HasColumnType("text"); + + b.Property("ApplicationFormDescription") + .HasColumnType("text"); + + b.Property("ApplicationFormName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AttemptedConnectionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AutomaticallyGenerateAIAnalysis") + .HasColumnType("boolean"); + + b.Property("AvailableChefsFields") + .HasColumnType("text"); + + b.Property("Category") + .HasColumnType("text"); + + b.Property("ChefsApplicationFormGuid") + .HasColumnType("text"); + + b.Property("ChefsCriteriaFormGuid") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ConnectionHttpStatus") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DefaultPaymentGroup") + .HasColumnType("integer"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ElectoralDistrictAddressType") + .HasColumnType("integer"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormHierarchy") + .HasColumnType("integer"); + + b.Property("IntakeId") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("IsDirectApproval") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ManuallyInitiateAIAnalysis") + .HasColumnType("boolean"); + + b.Property("ParentFormId") + .HasColumnType("uuid"); + + b.Property("Payable") + .HasColumnType("boolean"); + + b.Property("PaymentApprovalThreshold") + .HasColumnType("numeric"); + + b.Property("Prefix") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("PreventPayment") + .HasColumnType("boolean"); + + b.Property("ScoresheetId") + .HasColumnType("uuid"); + + b.Property("SuffixType") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IntakeId"); + + b.HasIndex("ParentFormId"); + + b.HasIndex("TenantId", "IsDeleted") + .HasFilter("\"IsDeleted\" = false"); + + b.ToTable("ApplicationForms", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormSubmission", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormVersionId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ChefsSubmissionGuid") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormVersionId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OidcSub") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Submission") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationFormId"); + + b.ToTable("ApplicationFormSubmissions", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormVersion", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("AvailableChefsFields") + .HasColumnType("text"); + + b.Property("ChefsApplicationFormGuid") + .HasColumnType("text"); + + b.Property("ChefsFormVersionGuid") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormSchema") + .HasColumnType("jsonb"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Published") + .HasColumnType("boolean"); + + b.Property("ReportColumns") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportKeys") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("SubmissionHeaderMapping") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationFormId"); + + b.ToTable("ApplicationFormVersion", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationLink", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LinkType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Related"); + + b.Property("LinkedApplicationId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicationLinks", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationStatus", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExternalStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("InternalStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("NotifiedStatus") + .HasColumnType("text"); + + b.Property("StatusCode") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("StatusCode") + .IsUnique(); + + b.ToTable("ApplicationStatuses", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationTags", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TagId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("TagId"); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicationTags", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AssessmentAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AssessmentId"); + + b.ToTable("AssessmentAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AuditHistory", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("AuditDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AuditNote") + .HasColumnType("text"); + + b.Property("AuditStatus") + .HasColumnType("text"); + + b.Property("AuditTrackingNumber") + .HasColumnType("text"); + + b.Property("AuditorName") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("AuditHistories", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.FundingHistory", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApprovedAmount") + .HasColumnType("numeric"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FundingNotes") + .HasColumnType("text"); + + b.Property("FundingYear") + .HasColumnType("text"); + + b.Property("GrantCategory") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OneTimeConsideration") + .HasColumnType("numeric"); + + b.Property("PaidDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ReconsiderationAmount") + .HasColumnType("numeric"); + + b.Property("RenewedFunding") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TotalGrantAmount") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("FundingHistories", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.IssueTracking", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IssueDescription") + .HasColumnType("text"); + + b.Property("IssueHeading") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ResolutionNote") + .HasColumnType("text"); + + b.Property("Resolved") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Year") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("IssueTrackings", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ReportsHistory", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FiscalYear") + .HasColumnType("text"); + + b.Property("IncompleteReport") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("Outstanding") + .HasColumnType("boolean"); + + b.Property("ReportDate") + .HasColumnType("timestamp without time zone"); + + b.Property("SignedOff") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("ReportsHistories", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Assessments.Assessment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ApprovalRecommended") + .HasColumnType("boolean"); + + b.Property("AssessorId") + .HasColumnType("uuid"); + + b.Property("CleanGrowth") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("EconomicImpact") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FinancialAnalysis") + .HasColumnType("integer"); + + b.Property("InclusiveGrowth") + .HasColumnType("integer"); + + b.Property("IsAiAssessment") + .HasColumnType("boolean"); + + b.Property("IsComplete") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("AssessorId"); + + b.ToTable("Assessments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicantComment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CommenterId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PinDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("CommenterId"); + + b.ToTable("ApplicantComments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicationComment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CommenterId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PinDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("CommenterId"); + + b.ToTable("ApplicationComments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.AssessmentComment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CommenterId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PinDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("AssessmentId"); + + b.HasIndex("CommenterId"); + + b.ToTable("AssessmentComments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Contacts.Contact", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Email") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("HomePhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MobilePhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("WorkPhoneExtension") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("WorkPhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.ToTable("Contacts", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Contacts.ContactLink", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContactId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsPrimary") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("RelatedEntityId") + .HasColumnType("uuid"); + + b.Property("RelatedEntityType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Role") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("RelatedEntityType", "RelatedEntityId"); + + b.HasIndex("ContactId", "RelatedEntityType", "RelatedEntityId"); + + b.ToTable("ContactLinks", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.GlobalTag.Tag", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Tags", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Identity.Person", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Badge") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FullName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OidcDisplayName") + .IsRequired() + .HasColumnType("text"); + + b.Property("OidcSub") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("OidcSub"); + + b.HasIndex("TenantId"); + + b.ToTable("Persons", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Intakes.Intake", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Budget") + .HasColumnType("double precision"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IntakeName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("StartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Intakes", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotification", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationStatus") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ApplicationStatusId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DateField") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EmailTemplateId") + .HasColumnType("uuid"); + + b.Property("EventType") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("RecipientCategory") + .HasColumnType("text"); + + b.Property("RecipientIdentifier") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TriggerDetail") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("TriggerType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("ScheduledNotifications", "Notifications"); + }); + + modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotificationTracking", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatorId") + .HasColumnType("uuid"); + + b.Property("DateField") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("NotificationSentDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ScheduledNotificationId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("CreationTime"); + + b.HasIndex("ScheduledNotificationId"); + + b.HasIndex("ApplicationId", "ScheduledNotificationId", "DateField") + .IsUnique(); + + b.ToTable("ScheduledNotificationTracking", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("EmailGroups", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroupUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.ToTable("EmailGroupUsers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Emails.EmailLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("BCC") + .IsRequired() + .HasColumnType("text"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("BodyType") + .IsRequired() + .HasColumnType("text"); + + b.Property("CC") + .IsRequired() + .HasColumnType("text"); + + b.Property("ChesHttpStatusCode") + .HasColumnType("text"); + + b.Property("ChesMsgId") + .HasColumnType("uuid"); + + b.Property("ChesResponse") + .IsRequired() + .HasColumnType("text"); + + b.Property("ChesStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("EmailType") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FromAddress") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestIds") + .IsRequired() + .HasColumnType("text"); + + b.Property("Priority") + .IsRequired() + .HasColumnType("text"); + + b.Property("Recipient") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("RetryAttempts") + .HasColumnType("integer"); + + b.Property("ScheduledNotificationId") + .HasColumnType("uuid"); + + b.Property("SendOnDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("SentDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tag") + .IsRequired() + .HasColumnType("text"); + + b.Property("TemplateName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("ToAddress") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("EmailLogs", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Emails.EmailLogAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContentType") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("EmailLogId") + .HasColumnType("uuid"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("FileSize") + .HasColumnType("bigint"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OriginTemplateId") + .HasColumnType("uuid"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TemplateId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EmailLogId"); + + b.HasIndex("S3ObjectKey"); + + b.HasIndex("TemplateId"); + + b.ToTable("EmailLogAttachments", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.EmailTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BodyHTML") + .IsRequired() + .HasColumnType("text"); + + b.Property("BodyText") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("RecipientCategory") + .HasColumnType("text"); + + b.Property("RecipientIdentifier") + .HasColumnType("text"); + + b.Property("SendFrom") + .IsRequired() + .HasColumnType("text"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("EmailTemplates", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.Subscriber", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Subscribers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("SubscriptionGroups", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroupSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("SubscriberId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.HasIndex("SubscriberId"); + + b.ToTable("SubscriptionGroupSubscribers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TemplateVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MapTo") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Token") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("TemplateVariables", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.Trigger", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("InternalName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Triggers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TriggerSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("SubscriptionGroupId") + .HasColumnType("uuid"); + + b.Property("TemplateId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TriggerId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SubscriptionGroupId"); + + b.HasIndex("TemplateId"); + + b.HasIndex("TriggerId"); + + b.ToTable("TriggerSubscriptions", "Notifications"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.AccountCodings.AccountCoding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .HasMaxLength(35) + .HasColumnType("character varying(35)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MinistryClient") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("Responsibility") + .IsRequired() + .HasColumnType("text"); + + b.Property("ServiceLine") + .IsRequired() + .HasColumnType("text"); + + b.Property("Stob") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AccountCodings", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentConfigurations.PaymentConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DefaultAccountCodingId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentIdPrefix") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("PaymentConfigurations", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.ExpenseApproval", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DecisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("DecisionUserId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("PaymentRequestId"); + + b.ToTable("ExpenseApprovals", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountCodingId") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("BatchName") + .IsRequired() + .HasColumnType("text"); + + b.Property("BatchNumber") + .HasColumnType("numeric"); + + b.Property("CancelledBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("CancelledBy"); + + b.Property("CancelledById") + .HasColumnType("uuid") + .HasColumnName("CancelledById"); + + b.Property("CancelledOn") + .HasColumnType("timestamp without time zone") + .HasColumnName("CancelledOn"); + + b.Property("CasHttpStatusCode") + .HasColumnType("integer"); + + b.Property("CasResponse") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContractNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FsbApNotified") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("FsbNotificationEmailLogId") + .HasColumnType("uuid"); + + b.Property("FsbNotificationSentDate") + .HasColumnType("timestamp without time zone"); + + b.Property("InvoiceNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("InvoiceStatus") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("IsRecon") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("PayeeName") + .IsRequired() + .HasColumnType("text"); + + b.Property("PaymentDate") + .HasColumnType("text"); + + b.Property("PaymentNumber") + .HasColumnType("text"); + + b.Property("PaymentStatus") + .HasColumnType("text"); + + b.Property("ReferenceNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("RequesterName") + .IsRequired() + .HasColumnType("text"); + + b.Property("SiteId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("SubmissionConfirmationCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("SupplierName") + .HasColumnType("text"); + + b.Property("SupplierNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("AccountCodingId"); + + b.HasIndex("CorrelationId"); + + b.HasIndex("CreationTime"); + + b.HasIndex("FsbNotificationEmailLogId"); + + b.HasIndex("ReferenceNumber") + .IsUnique(); + + b.HasIndex("SiteId"); + + b.HasIndex("Status"); + + b.HasIndex("TenantId", "CreationTime") + .HasFilter("\"IsDeleted\" = false"); + + b.ToTable("PaymentRequests", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentTags.PaymentTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestId") + .HasColumnType("uuid"); + + b.Property("TagId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("PaymentRequestId"); + + b.HasIndex("TagId"); + + b.ToTable("PaymentTags", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentThresholds.PaymentThreshold", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Threshold") + .HasColumnType("numeric"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("PaymentThresholds", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Site", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddressLine1") + .HasColumnType("text"); + + b.Property("AddressLine2") + .HasColumnType("text"); + + b.Property("AddressLine3") + .HasColumnType("text"); + + b.Property("BankAccount") + .HasColumnType("text"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("Country") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EFTAdvicePref") + .HasColumnType("text"); + + b.Property("EmailAddress") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastUpdatedInCas") + .HasColumnType("timestamp without time zone"); + + b.Property("MarkDeletedInUse") + .HasColumnType("boolean"); + + b.Property("Number") + .IsRequired() + .HasColumnType("text"); + + b.Property("PaymentGroup") + .HasColumnType("integer"); + + b.Property("PostalCode") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("SiteProtected") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("text"); + + b.Property("SupplierId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("SupplierId"); + + b.ToTable("Sites", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Supplier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BusinessNumber") + .HasColumnType("text"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastUpdatedInCAS") + .HasColumnType("timestamp without time zone"); + + b.Property("MailingAddress") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Number") + .HasColumnType("text"); + + b.Property("PostalCode") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("SIN") + .HasColumnType("text"); + + b.Property("StandardIndustryClassification") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("text"); + + b.Property("Subcategory") + .HasColumnType("text"); + + b.Property("SupplierProtected") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Suppliers", "Payments"); + }); + + modelBuilder.Entity("Unity.Reporting.Domain.Configuration.ReportColumnsMap", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Mapping") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RoleStatus") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("ViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ViewStatus") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("ReportColumnsMaps", "Reporting"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.Scoresheet", "Scoresheet") + .WithMany("Instances") + .HasForeignKey("ScoresheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scoresheet"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Answer", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.Question", "Question") + .WithMany("Answers") + .HasForeignKey("QuestionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", null) + .WithMany("Answers") + .HasForeignKey("ScoresheetInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Question"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.ScoresheetSection", "Section") + .WithMany("Fields") + .HasForeignKey("SectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Section"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.Scoresheet", "Scoresheet") + .WithMany("Sections") + .HasForeignKey("ScoresheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scoresheet"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.CustomFieldValue", b => + { + b.HasOne("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", null) + .WithMany("Values") + .HasForeignKey("WorksheetInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetLinks.WorksheetLink", b => + { + b.HasOne("Unity.Flex.Domain.Worksheets.Worksheet", "Worksheet") + .WithMany("Links") + .HasForeignKey("WorksheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Worksheet"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.CustomField", b => + { + b.HasOne("Unity.Flex.Domain.Worksheets.WorksheetSection", "Section") + .WithMany("Fields") + .HasForeignKey("SectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Section"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => + { + b.HasOne("Unity.Flex.Domain.Worksheets.Worksheet", "Worksheet") + .WithMany("Sections") + .HasForeignKey("WorksheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Worksheet"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAddress", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", "Applicant") + .WithMany("ApplicantAddresses") + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("ApplicantAddresses") + .HasForeignKey("ApplicationId"); + + b.Navigation("Applicant"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAgent", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithOne("ApplicantAgent") + .HasForeignKey("Unity.GrantManager.Applications.ApplicantAgent", "ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAttachment", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", "Applicant") + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", "ApplicationForm") + .WithMany() + .HasForeignKey("ApplicationFormId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationStatus", "ApplicationStatus") + .WithMany("Applications") + .HasForeignKey("ApplicationStatusId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Applicant"); + + b.Navigation("ApplicationForm"); + + b.Navigation("ApplicationStatus"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAssignment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("ApplicationAssignments") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", "Assignee") + .WithMany() + .HasForeignKey("AssigneeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Application"); + + b.Navigation("Assignee"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAttachment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationChefsFileAttachment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationContact", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationForm", b => + { + b.HasOne("Unity.GrantManager.Intakes.Intake", null) + .WithMany() + .HasForeignKey("IntakeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) + .WithMany() + .HasForeignKey("ParentFormId") + .OnDelete(DeleteBehavior.NoAction); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormSubmission", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) + .WithMany() + .HasForeignKey("ApplicationFormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormVersion", b => + { + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) + .WithMany() + .HasForeignKey("ApplicationFormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationLink", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany("ApplicationLinks") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationTags", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("ApplicationTags") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.GlobalTag.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Application"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AssessmentAttachment", b => + { + b.HasOne("Unity.GrantManager.Assessments.Assessment", null) + .WithMany() + .HasForeignKey("AssessmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AuditHistory", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.FundingHistory", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.IssueTracking", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ReportsHistory", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Assessments.Assessment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("Assessments") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("AssessorId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicantComment", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("CommenterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicationComment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("CommenterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.AssessmentComment", b => + { + b.HasOne("Unity.GrantManager.Assessments.Assessment", null) + .WithMany() + .HasForeignKey("AssessmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("CommenterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Contacts.ContactLink", b => + { + b.HasOne("Unity.GrantManager.Contacts.Contact", null) + .WithMany() + .HasForeignKey("ContactId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotificationTracking", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Notifications.ScheduledNotification", null) + .WithMany() + .HasForeignKey("ScheduledNotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroupUser", b => + { + b.HasOne("Unity.Notifications.EmailGroups.EmailGroup", null) + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Notifications.Emails.EmailLogAttachment", b => + { + b.HasOne("Unity.Notifications.Emails.EmailLog", null) + .WithMany() + .HasForeignKey("EmailLogId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Unity.Notifications.Templates.EmailTemplate", null) + .WithMany() + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroupSubscription", b => + { + b.HasOne("Unity.Notifications.Templates.SubscriptionGroup", "SubscriptionGroup") + .WithMany() + .HasForeignKey("GroupId"); + + b.HasOne("Unity.Notifications.Templates.Subscriber", "Subscriber") + .WithMany() + .HasForeignKey("SubscriberId"); + + b.Navigation("Subscriber"); + + b.Navigation("SubscriptionGroup"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TriggerSubscription", b => + { + b.HasOne("Unity.Notifications.Templates.SubscriptionGroup", "SubscriptionGroup") + .WithMany() + .HasForeignKey("SubscriptionGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Notifications.Templates.EmailTemplate", "EmailTemplate") + .WithMany() + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Notifications.Templates.Trigger", "Trigger") + .WithMany() + .HasForeignKey("TriggerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EmailTemplate"); + + b.Navigation("SubscriptionGroup"); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.ExpenseApproval", b => + { + b.HasOne("Unity.Payments.Domain.PaymentRequests.PaymentRequest", "PaymentRequest") + .WithMany("ExpenseApprovals") + .HasForeignKey("PaymentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PaymentRequest"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.HasOne("Unity.Payments.Domain.AccountCodings.AccountCoding", "AccountCoding") + .WithMany() + .HasForeignKey("AccountCodingId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("Unity.Payments.Domain.Suppliers.Site", "Site") + .WithMany() + .HasForeignKey("SiteId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("AccountCoding"); + + b.Navigation("Site"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentTags.PaymentTag", b => + { + b.HasOne("Unity.Payments.Domain.PaymentRequests.PaymentRequest", null) + .WithMany("PaymentTags") + .HasForeignKey("PaymentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.GlobalTag.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Site", b => + { + b.HasOne("Unity.Payments.Domain.Suppliers.Supplier", "Supplier") + .WithMany("Sites") + .HasForeignKey("SupplierId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Scoresheet", b => + { + b.Navigation("Instances"); + + b.Navigation("Sections"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => + { + b.Navigation("Fields"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", b => + { + b.Navigation("Values"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.Worksheet", b => + { + b.Navigation("Links"); + + b.Navigation("Sections"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => + { + b.Navigation("Fields"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Applicant", b => + { + b.Navigation("ApplicantAddresses"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.Navigation("ApplicantAddresses"); + + b.Navigation("ApplicantAgent"); + + b.Navigation("ApplicationAssignments"); + + b.Navigation("ApplicationLinks"); + + b.Navigation("ApplicationTags"); + + b.Navigation("Assessments"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationStatus", b => + { + b.Navigation("Applications"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.Navigation("ExpenseApprovals"); + + b.Navigation("PaymentTags"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Supplier", b => + { + b.Navigation("Sites"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260807224046_AB33996_AddApplicantFiscalYearEndRestricted.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260807224046_AB33996_AddApplicantFiscalYearEndRestricted.cs new file mode 100644 index 0000000000..16cad7af90 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260807224046_AB33996_AddApplicantFiscalYearEndRestricted.cs @@ -0,0 +1,82 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; + +#nullable disable + +namespace Unity.GrantManager.Migrations.TenantMigrations +{ + /// + public partial class AB33996_AddApplicantFiscalYearEndRestricted : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "FiscalYearEnd", + table: "Applicants", + type: "date", + nullable: true); + + // Trigger keeps FiscalYearEnd in sync whenever FiscalMonth or FiscalDay change. + // PostgreSQL generated columns cannot reference volatile functions (e.g. CURRENT_DATE), + // so a BEFORE trigger is used instead of a GENERATED ALWAYS AS column. + migrationBuilder.Sql(@" +CREATE OR REPLACE FUNCTION compute_applicants_fiscal_year_end() +RETURNS TRIGGER AS $$ +DECLARE + month_num integer; +BEGIN + month_num := CASE LEFT(NEW.""FiscalMonth"", 3) + WHEN 'Jan' THEN 1 WHEN 'Feb' THEN 2 WHEN 'Mar' THEN 3 + WHEN 'Apr' THEN 4 WHEN 'May' THEN 5 WHEN 'Jun' THEN 6 + WHEN 'Jul' THEN 7 WHEN 'Aug' THEN 8 WHEN 'Sep' THEN 9 + WHEN 'Oct' THEN 10 WHEN 'Nov' THEN 11 WHEN 'Dec' THEN 12 + ELSE NULL + END; + + IF month_num IS NOT NULL AND NEW.""FiscalDay"" IS NOT NULL THEN + BEGIN + NEW.""FiscalYearEnd"" := MAKE_DATE( + EXTRACT(YEAR FROM CURRENT_DATE)::int, + month_num, + NEW.""FiscalDay"" + ); + EXCEPTION WHEN others THEN + NEW.""FiscalYearEnd"" := NULL; + END; + ELSE + NEW.""FiscalYearEnd"" := NULL; + END IF; + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trg_applicants_fiscal_year_end +BEFORE INSERT OR UPDATE OF ""FiscalMonth"", ""FiscalDay"" +ON ""Applicants"" +FOR EACH ROW EXECUTE FUNCTION compute_applicants_fiscal_year_end(); +"); + + // Backfill existing rows by touching FiscalDay, which fires the trigger above. + migrationBuilder.Sql(@" +UPDATE ""Applicants"" +SET ""FiscalDay"" = ""FiscalDay"" +WHERE ""FiscalMonth"" IS NOT NULL AND ""FiscalDay"" IS NOT NULL; +"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql(@" +DROP TRIGGER IF EXISTS trg_applicants_fiscal_year_end ON ""Applicants""; +DROP FUNCTION IF EXISTS compute_applicants_fiscal_year_end(); +"); + + migrationBuilder.DropColumn( + name: "FiscalYearEnd", + table: "Applicants"); + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs index 3df92980ba..1d8fd17a8c 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs @@ -19,7 +19,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.PostgreSql) - .HasAnnotation("ProductVersion", "10.0.3") + .HasAnnotation("ProductVersion", "10.0.10") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -845,6 +845,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("FiscalMonth") .HasColumnType("text"); + b.Property("FiscalYearEnd") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("date"); + b.Property("FundingHistoryComments") .HasColumnType("text"); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicantRepository.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicantRepository.cs index fe9dd193a3..d90e39d505 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicantRepository.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicantRepository.cs @@ -1,9 +1,9 @@ -using System; +using Microsoft.EntityFrameworkCore; +using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; using Unity.GrantManager.Applications; using Unity.GrantManager.EntityFrameworkCore; using Unity.Payments.Domain.Suppliers; @@ -165,7 +165,8 @@ public async Task> GetApplicantListRecordsAsync(IReadO StartedOperatingDate = a.StartedOperatingDate, IsDuplicated = a.IsDuplicated, CreationTime = a.CreationTime, - LastModificationTime = a.LastModificationTime + LastModificationTime = a.LastModificationTime, + FiscalYearEnd = a.FiscalYearEnd }) .ToListAsync(); } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicationRepository.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicationRepository.cs index 80432f764a..08219b0dfe 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicationRepository.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicationRepository.cs @@ -323,6 +323,8 @@ public async Task> GetApplicationListRecordsAsync( a.UnityApplicationId, Status = a.ApplicationStatus.InternalStatus, // ApplicationStatus (always joined) a.ExternalStatusVisibility, + a.ApplicationStatus.ExternalStatus, + a.ApplicationStatus.NotifiedStatus, Category = a.ApplicationForm.Category ?? string.Empty, // ApplicationForm (always joined) a.ApplicantId, ApplicantName = a.Applicant.ApplicantName, // Applicant (always joined) @@ -508,6 +510,10 @@ public async Task> GetApplicationListRecordsAsync( UnityApplicationId = a.UnityApplicationId, Status = a.Status, ExternalStatusVisibility = a.ExternalStatusVisibility, + ExternalStatus = a.ExternalStatus, + PublishedStatus = a.ExternalStatusVisibility + ? a.NotifiedStatus ?? a.ExternalStatus + : a.ExternalStatus, Category = a.Category, ApplicantId = a.ApplicantId, ApplicantName = a.ApplicantName, diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/get_consolidated_worksheet_data.sql b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/get_consolidated_worksheet_data.sql index cc1fcc39cc..467f0ae94b 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/get_consolidated_worksheet_data.sql +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/get_consolidated_worksheet_data.sql @@ -124,7 +124,9 @@ BEGIN WHEN 'checkbox' THEN CASE WHEN um.type_path LIKE '%checkboxgroup%' THEN - format('(CASE WHEN ((SELECT cell_elem->>''value'' FROM jsonb_array_elements(dg_tbl.dg_data->''cells'') AS cell_elem WHERE cell_elem->>''key'' = %L)) IS NULL THEN NULL ELSE (SELECT (checkbox_elem->>''value'')::BOOLEAN FROM jsonb_array_elements(((SELECT cell_elem->>''value'' FROM jsonb_array_elements(dg_tbl.dg_data->''cells'') AS cell_elem WHERE cell_elem->>''key'' = %L))::jsonb) AS checkbox_elem WHERE checkbox_elem->>''key'' = %L) END) AS %I', + -- Validate via safe_to_jsonb + jsonb_typeof = 'array' rather than a bracket-shape regex, so a + -- null/empty/malformed stored value (e.g. '' or '[invalid]') never reaches the ::jsonb cast + format('(CASE WHEN jsonb_typeof("Reporting".safe_to_jsonb((SELECT cell_elem->>''value'' FROM jsonb_array_elements(dg_tbl.dg_data->''cells'') AS cell_elem WHERE cell_elem->>''key'' = %L))) = ''array'' THEN (SELECT (checkbox_elem->>''value'')::BOOLEAN FROM jsonb_array_elements("Reporting".safe_to_jsonb((SELECT cell_elem->>''value'' FROM jsonb_array_elements(dg_tbl.dg_data->''cells'') AS cell_elem WHERE cell_elem->>''key'' = %L))) AS checkbox_elem WHERE checkbox_elem->>''key'' = %L) ELSE NULL END) AS %I', split_part(um.clean_data_path, '->', 1), split_part(um.clean_data_path, '->', 1), split_part(um.clean_data_path, '->', 2), @@ -218,7 +220,9 @@ BEGIN WHEN 'checkbox' THEN CASE WHEN um.type_path LIKE '%checkboxgroup%' THEN - format('(CASE WHEN ((SELECT v_elem->>''value'' FROM jsonb_array_elements(wi."CurrentValue"->''values'') AS v_elem WHERE v_elem->>''key'' = %L)) IS NULL THEN NULL ELSE (SELECT (checkbox_elem->>''value'')::BOOLEAN FROM jsonb_array_elements(((SELECT v_elem->>''value'' FROM jsonb_array_elements(wi."CurrentValue"->''values'') AS v_elem WHERE v_elem->>''key'' = %L))::jsonb) AS checkbox_elem WHERE checkbox_elem->>''key'' = %L) END) AS %I', + -- Validate via safe_to_jsonb + jsonb_typeof = 'array' rather than a bracket-shape regex, so a + -- null/empty/malformed stored value (e.g. '' or '[invalid]') never reaches the ::jsonb cast + format('(CASE WHEN jsonb_typeof("Reporting".safe_to_jsonb((SELECT v_elem->>''value'' FROM jsonb_array_elements(wi."CurrentValue"->''values'') AS v_elem WHERE v_elem->>''key'' = %L))) = ''array'' THEN (SELECT (checkbox_elem->>''value'')::BOOLEAN FROM jsonb_array_elements("Reporting".safe_to_jsonb((SELECT v_elem->>''value'' FROM jsonb_array_elements(wi."CurrentValue"->''values'') AS v_elem WHERE v_elem->>''key'' = %L))) AS checkbox_elem WHERE checkbox_elem->>''key'' = %L) ELSE NULL END) AS %I', split_part(COALESCE(um.clean_data_path, um.property_name), '->', 1), split_part(COALESCE(um.clean_data_path, um.property_name), '->', 1), split_part(COALESCE(um.clean_data_path, um.property_name), '->', 2), diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/get_worksheet_data.sql b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/get_worksheet_data.sql index 21e58fbed9..8ca3b51108 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/get_worksheet_data.sql +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/get_worksheet_data.sql @@ -111,7 +111,9 @@ BEGIN CASE WHEN um.type_path LIKE '%checkboxgroup%' THEN -- For checkbox group, parse the JSON array and extract the specific checkbox value - format('(CASE WHEN ((SELECT cell_elem->>''value'' FROM jsonb_array_elements(dg_tbl.dg_data->''cells'') AS cell_elem WHERE cell_elem->>''key'' = %L)) IS NULL THEN NULL ELSE (SELECT (checkbox_elem->>''value'')::BOOLEAN FROM jsonb_array_elements(((SELECT cell_elem->>''value'' FROM jsonb_array_elements(dg_tbl.dg_data->''cells'') AS cell_elem WHERE cell_elem->>''key'' = %L))::jsonb) AS checkbox_elem WHERE checkbox_elem->>''key'' = %L) END) AS %I', + -- Validate via safe_to_jsonb + jsonb_typeof = 'array' rather than a bracket-shape regex, so a + -- null/empty/malformed stored value (e.g. '' or '[invalid]') never reaches the ::jsonb cast + format('(CASE WHEN jsonb_typeof("Reporting".safe_to_jsonb((SELECT cell_elem->>''value'' FROM jsonb_array_elements(dg_tbl.dg_data->''cells'') AS cell_elem WHERE cell_elem->>''key'' = %L))) = ''array'' THEN (SELECT (checkbox_elem->>''value'')::BOOLEAN FROM jsonb_array_elements("Reporting".safe_to_jsonb((SELECT cell_elem->>''value'' FROM jsonb_array_elements(dg_tbl.dg_data->''cells'') AS cell_elem WHERE cell_elem->>''key'' = %L))) AS checkbox_elem WHERE checkbox_elem->>''key'' = %L) ELSE NULL END) AS %I', split_part(um.clean_data_path, '->', 1), -- Field10 equivalent in datagrid split_part(um.clean_data_path, '->', 1), -- Field10 equivalent in datagrid split_part(um.clean_data_path, '->', 2), -- check1/check2/etc @@ -203,9 +205,11 @@ BEGIN CASE WHEN um.type_path LIKE '%checkboxgroup%' THEN -- For checkbox group, parse the JSON array and extract the specific checkbox value - format('(CASE WHEN ((SELECT v_elem->>''value'' FROM jsonb_array_elements(wi."CurrentValue"->''values'') AS v_elem WHERE v_elem->>''key'' = %L)) IS NULL THEN NULL ELSE (SELECT (checkbox_elem->>''value'')::BOOLEAN FROM jsonb_array_elements(((SELECT v_elem->>''value'' FROM jsonb_array_elements(wi."CurrentValue"->''values'') AS v_elem WHERE v_elem->>''key'' = %L))::jsonb) AS checkbox_elem WHERE checkbox_elem->>''key'' = %L) END) AS %I', + -- Validate via safe_to_jsonb + jsonb_typeof = 'array' rather than a bracket-shape regex, so a + -- null/empty/malformed stored value (e.g. '' or '[invalid]') never reaches the ::jsonb cast + format('(CASE WHEN jsonb_typeof("Reporting".safe_to_jsonb((SELECT v_elem->>''value'' FROM jsonb_array_elements(wi."CurrentValue"->''values'') AS v_elem WHERE v_elem->>''key'' = %L))) = ''array'' THEN (SELECT (checkbox_elem->>''value'')::BOOLEAN FROM jsonb_array_elements("Reporting".safe_to_jsonb((SELECT v_elem->>''value'' FROM jsonb_array_elements(wi."CurrentValue"->''values'') AS v_elem WHERE v_elem->>''key'' = %L))) AS checkbox_elem WHERE checkbox_elem->>''key'' = %L) ELSE NULL END) AS %I', + split_part(COALESCE(um.clean_data_path, um.property_name), '->', 1), -- Field10 split_part(COALESCE(um.clean_data_path, um.property_name), '->', 1), -- Field10 - split_part(COALESCE(um.clean_data_path, um.property_name), '->', 1), -- Field10 split_part(COALESCE(um.clean_data_path, um.property_name), '->', 2), -- check1/check2/etc um.column_name) ELSE diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/ErrorCountingLoggerSink.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/ErrorCountingLoggerSink.cs index aff8108b90..cd9c75e401 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/ErrorCountingLoggerSink.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/ErrorCountingLoggerSink.cs @@ -18,8 +18,12 @@ namespace Unity.GrantManager.Web.Middleware; /// public sealed class ErrorCountingLoggerSink : ILogEventSink { + private static readonly TimeSpan PersistenceBackoff = TimeSpan.FromSeconds(30); private static IServiceScopeFactory? _scopeFactory; private static readonly AsyncLocal IsPersistingExceptionLog = new(); + private readonly object _persistenceGate = new(); + private bool _persistenceInFlight; + private DateTimeOffset _persistenceDisabledUntil; internal static readonly Counter ErrorCounter = Metrics.CreateCounter( @@ -59,6 +63,16 @@ public void Emit(LogEvent logEvent) return; } + lock (_persistenceGate) + { + if (_persistenceInFlight || DateTimeOffset.UtcNow < _persistenceDisabledUntil) + { + return; + } + + _persistenceInFlight = true; + } + _ = Task.Run(async () => { IsPersistingExceptionLog.Value = true; @@ -116,11 +130,19 @@ await exceptionLogs.CreateAsync(new CreateExceptionLogDto } catch { - // Swallow to avoid recursive logging from logger sink failures. + lock (_persistenceGate) + { + _persistenceDisabledUntil = DateTimeOffset.UtcNow.Add(PersistenceBackoff); + } } finally { IsPersistingExceptionLog.Value = false; + + lock (_persistenceGate) + { + _persistenceInFlight = false; + } } }); } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/ExceptionCounterMiddleware.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/ExceptionCounterMiddleware.cs index d8bf6faef4..9cdff84b83 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/ExceptionCounterMiddleware.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/ExceptionCounterMiddleware.cs @@ -19,6 +19,11 @@ public class ExceptionCounterMiddleware( ExceptionNotificationThrottle throttle, ILogger logger) { + private static readonly TimeSpan PersistenceBackoff = TimeSpan.FromSeconds(30); + private readonly object _persistenceGate = new(); + private bool _persistenceInFlight; + private DateTimeOffset _persistenceDisabledUntil; + // Notify only in these environments; add "Staging" if desired private static readonly HashSet NotifyEnvironments = new(StringComparer.OrdinalIgnoreCase) @@ -122,6 +127,19 @@ private void QueueLogNotification(HttpContext context, Exception ex) // we can safely use it after the request scope has ended var scopeFactory = context.RequestServices.GetRequiredService(); + // Acquire the single-flight gate only once the synchronous, potentially-throwing prep + // above has succeeded — otherwise an exception here would leave persistenceInFlight + // stuck "true" forever, since the Task.Run below (whose finally resets it) never starts. + lock (_persistenceGate) + { + if (_persistenceInFlight || DateTimeOffset.UtcNow < _persistenceDisabledUntil) + { + return; + } + + _persistenceInFlight = true; + } + _ = Task.Run(async () => { try @@ -132,6 +150,11 @@ private void QueueLogNotification(HttpContext context, Exception ex) var notifications = scope.ServiceProvider.GetRequiredService(); var exceptionLogs = scope.ServiceProvider.GetService(); + if (exceptionLogs == null) + { + OpenPersistenceBackoff(); + } + // Get current user and tenant name var userId = AbpUserTenantAccessor.GetCurrentUserId(scope.ServiceProvider); var userName = AbpUserTenantAccessor.GetCurrentUserName(scope.ServiceProvider) ?? "unknown"; @@ -262,6 +285,7 @@ await exceptionLogs.CreateAsync(new CreateExceptionLogDto } catch (Exception logEx) { + OpenPersistenceBackoff(); logger.LogWarning(logEx, "Failed to create exception log within UnitOfWork"); } } @@ -270,6 +294,7 @@ await exceptionLogs.CreateAsync(new CreateExceptionLogDto } catch (Exception uowEx) { + OpenPersistenceBackoff(); logger.LogWarning(uowEx, "Failed to complete UnitOfWork for exception handling"); } } @@ -279,9 +304,24 @@ await exceptionLogs.CreateAsync(new CreateExceptionLogDto notifyEx, "Failed to send Teams exception notification"); } + finally + { + lock (_persistenceGate) + { + _persistenceInFlight = false; + } + } }); } + private void OpenPersistenceBackoff() + { + lock (_persistenceGate) + { + _persistenceDisabledUntil = DateTimeOffset.UtcNow.Add(PersistenceBackoff); + } + } + private static string BuildApplicationStackExcerpt(Exception ex) { return ExceptionNotificationHelpers.BuildApplicationStackExcerpt(ex); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Applicants/Index.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Applicants/Index.js index 79bf2852df..e20f6c8488 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Applicants/Index.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Applicants/Index.js @@ -64,6 +64,7 @@ $(function () { getFiscalMonthColumn(columnIndex++), getBusinessNumberColumn(columnIndex++), getFiscalDayColumn(columnIndex++), + getFiscalYearEndColumn(columnIndex), getStartedOperatingDateColumn(columnIndex++), getIsDuplicatedColumn(columnIndex++), getCreationTimeColumn(columnIndex++), @@ -346,6 +347,18 @@ $(function () { } } + function getFiscalYearEndColumn(columnIndex) { + return { + title: 'Fiscal Year End', + data: 'fiscalYearEnd', + name: 'fiscalYearEnd', + className: 'data-table-header text-nowrap', + visible: false, + render: DataTable.render.date('YYYY-MM-DD', currentCultureName), + index: columnIndex + } + } + function getStartedOperatingDateColumn(columnIndex) { return { title: 'Started Operating Date', diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.cshtml index 4d20a2bf76..e18fef4833 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.cshtml @@ -1,11 +1,11 @@ @page @using Unity.GrantManager.ApplicationForms -@using Unity.Reporting.Permissions -@using Volo.Abp.AspNetCore.Mvc.UI.Layout; -@using Unity.GrantManager.Web.Pages.ApplicationForms; -@using Unity.GrantManager.Permissions; -@using Unity.Notifications.Permissions; -@using Unity.AI.Permissions; +@using Unity.Reporting.Permissions +@using Volo.Abp.AspNetCore.Mvc.UI.Layout; +@using Unity.GrantManager.Web.Pages.ApplicationForms; +@using Unity.GrantManager.Permissions; +@using Unity.Notifications.Permissions; +@using Unity.AI.Permissions; @using Volo.Abp.Authorization.Permissions; @using Unity.GrantManager.Web.Views.Shared.Components.Notifications; @@ -24,13 +24,13 @@ } @section scripts { - - - - - - -} + + + + + + +} @section styles { @@ -104,26 +104,26 @@ + - - @if (await PermissionChecker.IsGrantedAsync(AIPermissions.FormMapping.Generate)) - { - - } - + @if (await PermissionChecker.IsGrantedAsync(AIPermissions.FormMapping.Generate)) + { + + } + @@ -288,81 +288,22 @@ { } - - - - - - - - + + + + + + + + - + diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js index e655747a58..90f11986d9 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js @@ -999,54 +999,6 @@ UIElements.refreshAvailableWorksheetsHidden.val(data.chefsFormVersionId); } ); - - // AI Configuration tab - const btnSaveAIConfig = document.getElementById('btn-save-ai-config'); - const btnCancelAIConfig = document.getElementById('btn-cancel-ai-config'); - - if (btnSaveAIConfig) { - const aiFormId = document.getElementById('applicationFormId').value; - const automaticCheckbox = document.getElementById('AutomaticallyGenerateAIAnalysis'); - const manualCheckbox = document.getElementById('ManuallyInitiateAIAnalysis'); - - let lastSavedAIValues = { - automaticallyGenerateAIAnalysis: automaticCheckbox ? automaticCheckbox.checked : false, - manuallyInitiateAIAnalysis: manualCheckbox ? manualCheckbox.checked : false - }; - - btnSaveAIConfig.addEventListener('click', function () { - btnSaveAIConfig.disabled = true; - abp.ajax({ - url: `/api/app/application-form/${aiFormId}/ai-config`, - type: 'PATCH', - data: JSON.stringify({ - automaticallyGenerateAIAnalysis: automaticCheckbox ? automaticCheckbox.checked : false, - manuallyInitiateAIAnalysis: manualCheckbox ? manualCheckbox.checked : false - }), - contentType: 'application/json' - }) - .done(function () { - lastSavedAIValues = { - automaticallyGenerateAIAnalysis: automaticCheckbox ? automaticCheckbox.checked : false, - manuallyInitiateAIAnalysis: manualCheckbox ? manualCheckbox.checked : false - }; - abp.notify.success('AI configuration saved successfully.'); - }) - .fail(function () { - abp.notify.error('Failed to save AI configuration.'); - }) - .always(function () { - btnSaveAIConfig.disabled = false; - }); - }); - - if (btnCancelAIConfig) { - btnCancelAIConfig.addEventListener('click', function () { - if (automaticCheckbox) automaticCheckbox.checked = lastSavedAIValues.automaticallyGenerateAIAnalysis; - if (manualCheckbox) manualCheckbox.checked = lastSavedAIValues.manuallyInitiateAIAnalysis; - }); - } - } }); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Index.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Index.js index 1a56145547..c8f2985a48 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Index.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Index.js @@ -578,6 +578,8 @@ $(function () { getAssigneesColumn(columnIndex++), getStatusColumn(columnIndex++), getExternalStatusVisibilityColumn(columnIndex++), + getExternalStatusColumn(columnIndex++), + getPublishedStatusColumn(columnIndex++), getRequestedAmountColumn(columnIndex++), getApprovedAmountColumn(columnIndex++), getEconomicRegionColumn(columnIndex++), @@ -811,6 +813,34 @@ $(function () { } } + function getExternalStatusColumn(columnIndex) { + return { + title: l('ExternalStatus'), + data: 'externalStatus', + name: 'externalStatus', + className: 'data-table-header', + index: columnIndex, + render: function (data, type) { + const value = data ?? ''; + return type === 'display' ? dtTextRenderer.display(value) : value; + } + } + } + + function getPublishedStatusColumn(columnIndex) { + return { + title: l('PublishedStatus'), + data: 'publishedStatus', + name: 'publishedStatus', + className: 'data-table-header', + index: columnIndex, + render: function (data, type) { + const value = data ?? ''; + return type === 'display' ? dtTextRenderer.display(value) : value; + } + } + } + function getRequestedAmountColumn(columnIndex) { return { title: l('RequestedAmount'), diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantInfo/ApplicantSummaryViewModel.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantInfo/ApplicantSummaryViewModel.cs index c3279e2686..7e69cbb364 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantInfo/ApplicantSummaryViewModel.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantInfo/ApplicantSummaryViewModel.cs @@ -46,6 +46,9 @@ public class ApplicantSummaryViewModel [SelectItems(nameof(ApplicantInfoViewModel.FiscalDayList))] public string? FiscalDay { get; set; } + [Display(Name = "ApplicantInfoView:ApplicantInfo.FiscalYearEnd")] + public DateOnly? FiscalYearEnd { get; set; } + [Display(Name = "ApplicantInfoView:ApplicantInfo.Sector")] [SelectItems(nameof(ApplicantInfoViewModel.ApplicationSectorsList))] diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantInfo/Default.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantInfo/Default.cshtml index 34d3f5340f..af9382c330 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantInfo/Default.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantInfo/Default.cshtml @@ -170,7 +170,8 @@
- +
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantInfo/Default.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantInfo/Default.js index 59eef6c365..280c30981d 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantInfo/Default.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantInfo/Default.js @@ -701,28 +701,14 @@ function calculateFiscalYearEnd($container) { return; } - const today = new Date(); - const todayDate = new Date(today.getFullYear(), today.getMonth(), today.getDate()); - let year = todayDate.getFullYear(); - const isValidMonthDay = (y) => { - const d = new Date(y, month - 1, day); - return d.getMonth() === month - 1 && d.getDate() === day; - }; + const year = new Date().getFullYear(); + const isValidMonthDay = new Date(year, month - 1, day).getMonth() === month - 1; - if (!isValidMonthDay(year)) { + if (!isValidMonthDay) { $yearEndField.val(''); return; } - const candidate = new Date(year, month - 1, day); - if (candidate < todayDate) { - year += 1; - if (!isValidMonthDay(year)) { - $yearEndField.val(''); - return; - } - } - const mm = String(month).padStart(2, '0'); const dd = String(day).padStart(2, '0'); $yearEndField.val(`${year}-${mm}-${dd}`); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantOrganizationInfo/ApplicantOrganizationInfoViewComponent.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantOrganizationInfo/ApplicantOrganizationInfoViewComponent.cs index 991e48f85f..44714a4e5c 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantOrganizationInfo/ApplicantOrganizationInfoViewComponent.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantOrganizationInfo/ApplicantOrganizationInfoViewComponent.cs @@ -68,6 +68,7 @@ public async Task InvokeAsync(Guid applicantId) // Financial Information FiscalMonth = applicant.FiscalMonth ?? string.Empty, FiscalDay = applicant.FiscalDay?.ToString() ?? string.Empty, + FiscalYearEnd = applicant.FiscalYearEnd, StartedOperatingDate = applicant.StartedOperatingDate?.ToDateTime(TimeOnly.MinValue), RedStop = applicant.RedStop == true }; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantOrganizationInfo/ApplicantOrganizationInfoViewModel.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantOrganizationInfo/ApplicantOrganizationInfoViewModel.cs index f9370f9b3b..1a839ad0db 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantOrganizationInfo/ApplicantOrganizationInfoViewModel.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantOrganizationInfo/ApplicantOrganizationInfoViewModel.cs @@ -69,6 +69,9 @@ public class ApplicantOrganizationInfoViewModel [Display(Name = "Fiscal Year End Day")] public string FiscalDay { get; set; } = string.Empty; + [Display(Name = "Fiscal Year End")] + public DateOnly? FiscalYearEnd { get; set; } + [Display(Name = "Started Operating Date")] public DateTime? StartedOperatingDate { get; set; } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantOrganizationInfo/Default.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantOrganizationInfo/Default.cshtml index fa649fa7a7..5ea6dcdc63 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantOrganizationInfo/Default.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantOrganizationInfo/Default.cshtml @@ -163,7 +163,8 @@
- +
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantOrganizationInfo/Default.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantOrganizationInfo/Default.js index a9ce274fc8..1cf247d142 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantOrganizationInfo/Default.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantOrganizationInfo/Default.js @@ -324,6 +324,7 @@ populateRegisteredFields('', '', '', '', ''); }); } + function calculateApplicantFiscalYearEnd($container) { const monthVal = $container.find('#ApplicantOrganizationInfo_FiscalMonth').val(); const dayVal = $container.find('#ApplicantOrganizationInfo_FiscalDay').val(); @@ -348,28 +349,14 @@ return; } - const today = new Date(); - const todayDate = new Date(today.getFullYear(), today.getMonth(), today.getDate()); - let year = todayDate.getFullYear(); - const isValidMonthDay = (y) => { - const d = new Date(y, month - 1, day); - return d.getMonth() === month - 1 && d.getDate() === day; - }; + const year = new Date().getFullYear(); + const isValidMonthDay = new Date(year, month - 1, day).getMonth() === month - 1; - if (!isValidMonthDay(year)) { + if (!isValidMonthDay) { $yearEndField.val(''); return; } - const candidate = new Date(year, month - 1, day); - if (candidate < todayDate) { - year += 1; - if (!isValidMonthDay(year)) { - $yearEndField.val(''); - return; - } - } - const mm = String(month).padStart(2, '0'); const dd = String(day).padStart(2, '0'); $yearEndField.val(`${year}-${mm}-${dd}`); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/HistoryWidget/HistoryWidgetViewComponent.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/HistoryWidget/HistoryWidgetViewComponent.cs index 8072f43856..b21e16eab9 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/HistoryWidget/HistoryWidgetViewComponent.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/HistoryWidget/HistoryWidgetViewComponent.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Unity.GrantManager.Applications; using Unity.GrantManager.History; @@ -26,14 +27,21 @@ public async Task InvokeAsync(Guid applicationId) string? entityId = applicationId.ToString(); Dictionary applicationStatusDict = await GetApplicationStatusDict(); - HistoryWidgetViewModel model = new() - { - ApplicationStatusHistoryList = await historyAppService.GetEntityPropertyChangesAsync( + var historyList = await historyAppService.GetEntityPropertyChangesAsync( new GetEntityPropertyChangesInput { EntityId = entityId, PropertyNames = [Property_ApplicationStatusId, Property_ExternalStatusVisibility], - }, applicationStatusDict), + }, applicationStatusDict); + + // Suppress the automatic initial Unpublished entry created on new entities (no prior value) + historyList = historyList + .Where(h => !(h.PropertyName == Property_ExternalStatusVisibility && string.IsNullOrEmpty(h.OriginalValue))) + .ToList(); + + HistoryWidgetViewModel model = new() + { + ApplicationStatusHistoryList = historyList, }; return View(model); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.json b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.json index ec48801416..4a3b8af562 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.json +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.json @@ -157,41 +157,9 @@ "Logging": { "EnablePromptFileLog": false }, - "Operations": { - "Defaults": { - "Provider": "OpenAI", - "Profile": "Gpt5Mini", - "PromptVersion": "v1", - "ExecutionMode": "Sequential" - }, - "AttachmentSummary": { - "MaxCompletionTokens": 2000 - }, - "ApplicationAnalysis": { - "MaxCompletionTokens": 4000 - }, - "ApplicationScoring": { - "MaxCompletionTokens": 8000 - } - }, "OpenAI": { "ApiKey": "", - "Endpoint": "", - "Profiles": { - "Gpt4oMini": { - "DeploymentName": "gpt-4o-mini", - "MaxOutputTokenCountSupported": true, - "Temperature": 0.3 - }, - "Gpt5Mini": { - "DeploymentName": "gpt-5-mini", - "MaxOutputTokenCountSupported": false - }, - "Gpt5Nano": { - "DeploymentName": "gpt-5-nano", - "MaxOutputTokenCountSupported": false - } - } + "Endpoint": "" }, "UNITY_GITHUB_PAT": "" } diff --git a/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/AI/DataSeed/AIModelDataSeederTests.cs b/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/AI/DataSeed/AIModelDataSeederTests.cs index f0a66345d1..9018a0a016 100644 --- a/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/AI/DataSeed/AIModelDataSeederTests.cs +++ b/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/AI/DataSeed/AIModelDataSeederTests.cs @@ -16,13 +16,15 @@ namespace Unity.GrantManager.AI.DataSeed; public class AIModelDataSeederTests { [Fact] - public async Task Should_Seed_All_Configured_Profiles() + public async Task Should_Seed_All_Configured_Models() { var modelRepository = Substitute.For>(); var insertedModels = new List(); modelRepository - .FirstOrDefaultAsync(Arg.Any>>()) - .Returns((AIModel?)null); + .GetListAsync( + Arg.Any>>(), + cancellationToken: Arg.Any()) + .Returns(Task.FromResult(new List())); modelRepository .InsertAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(callInfo => @@ -37,13 +39,13 @@ public async Task Should_Seed_All_Configured_Profiles() await seeder.SeedAsync(new DataSeedContext()); insertedModels.Count.ShouldBe(3); - insertedModels.ShouldContain(model => model.Name == "Gpt4oMini" && model.IsActive); - insertedModels.ShouldContain(model => model.Name == "Gpt5Mini" && model.IsActive); - insertedModels.ShouldContain(model => model.Name == "Gpt5Nano" && model.IsActive); + insertedModels.ShouldContain(model => model.Name == "gpt-4o-mini" && model.Provider == "OpenAI" && model.IsActive); + insertedModels.ShouldContain(model => model.Name == "gpt-5-mini" && model.Provider == "OpenAI" && model.IsActive); + insertedModels.ShouldContain(model => model.Name == "gpt-5-nano" && model.Provider == "OpenAI" && model.IsActive); - var gpt4oMini = insertedModels.Single(model => model.Name == "Gpt4oMini"); - var gpt5Mini = insertedModels.Single(model => model.Name == "Gpt5Mini"); - var gpt5Nano = insertedModels.Single(model => model.Name == "Gpt5Nano"); + var gpt4oMini = insertedModels.Single(model => model.Name == "gpt-4o-mini"); + var gpt5Mini = insertedModels.Single(model => model.Name == "gpt-5-mini"); + var gpt5Nano = insertedModels.Single(model => model.Name == "gpt-5-nano"); DeserializeSettings(gpt4oMini.SettingsJson).MaxOutputTokenCountSupported.ShouldBeTrue(); DeserializeSettings(gpt4oMini.SettingsJson).Temperature.ShouldBe(0.3); @@ -53,6 +55,41 @@ public async Task Should_Seed_All_Configured_Profiles() DeserializeSettings(gpt5Nano.SettingsJson).Temperature.ShouldBeNull(); } + [Fact] + public async Task Should_Update_Existing_Model_When_Provider_Is_Stale() + { + var modelRepository = Substitute.For>(); + var existingModel = new AIModel(Guid.NewGuid(), "gpt-5-mini", "LegacyProvider") + { + IsActive = false, + SettingsJson = "{}" + }; + modelRepository + .GetListAsync( + Arg.Any>>(), + cancellationToken: Arg.Any()) + .Returns(callInfo => + { + var predicate = callInfo + .Arg>>() + .Compile(); + return Task.FromResult(new[] { existingModel }.Where(predicate).ToList()); + }); + + var seeder = new AIModelDataSeeder(modelRepository); + + await seeder.SeedAsync(new DataSeedContext()); + + existingModel.Provider.ShouldBe("OpenAI"); + existingModel.IsActive.ShouldBeTrue(); + DeserializeSettings(existingModel.SettingsJson).MaxOutputTokenCountSupported.ShouldBeFalse(); + await modelRepository.Received(1).UpdateAsync(existingModel, autoSave: true); + await modelRepository.DidNotReceive().InsertAsync( + Arg.Is(model => model.Name == existingModel.Name), + Arg.Any(), + Arg.Any()); + } + private static AIModelSettings DeserializeSettings(string settingsJson) { var settings = JsonSerializer.Deserialize(settingsJson); diff --git a/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/AI/DataSeed/AIPromptDataSeederTests.cs b/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/AI/DataSeed/AIPromptDataSeederTests.cs new file mode 100644 index 0000000000..a391d2cf08 --- /dev/null +++ b/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/AI/DataSeed/AIPromptDataSeederTests.cs @@ -0,0 +1,69 @@ +using NSubstitute; +using Shouldly; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Unity.AI.DataSeed; +using Unity.AI.Domain; +using Unity.AI.Runtime.Prompts; +using Volo.Abp.Data; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.MultiTenancy; +using Xunit; + +namespace Unity.GrantManager.AI.DataSeed; + +public class AIPromptDataSeederTests +{ + [Fact] + public async Task Should_Seed_The_Complete_BuiltIn_Prompt_Matrix() + { + var promptRepository = Substitute.For>(); + var insertedPrompts = new List(); + promptRepository + .FirstOrDefaultAsync(Arg.Any>>()) + .Returns((AIPrompt?)null); + promptRepository + .InsertAsync(Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(callInfo => + { + var prompt = callInfo.Arg(); + ArgumentNullException.ThrowIfNull(prompt); + insertedPrompts.Add(prompt); + return Task.FromResult(prompt); + }); + + var currentTenant = Substitute.For(); + currentTenant.Change(null).Returns(Substitute.For()); + var seeder = new AIPromptDataSeeder(promptRepository, currentTenant); + + await seeder.SeedAsync(new DataSeedContext()); + + insertedPrompts.Count.ShouldBe(12); + AssertVersions(insertedPrompts, AIPromptTypes.ApplicationAnalysis, 0, 1, 2); + AssertVersions(insertedPrompts, AIPromptTypes.AttachmentSummary, 0, 1, 2); + AssertVersions(insertedPrompts, AIPromptTypes.ApplicationScoring, 0, 1, 2); + AssertVersions(insertedPrompts, AIPromptTypes.FormMapping, 2); + AssertVersions(insertedPrompts, AIPromptTypes.FormWorksheet, 2); + AssertVersions(insertedPrompts, AIPromptTypes.FormScoresheet, 2); + insertedPrompts.All(prompt => + prompt.TenantId is null && + prompt.IsActive && + !string.IsNullOrWhiteSpace(prompt.SystemPrompt) && + !string.IsNullOrWhiteSpace(prompt.UserPrompt)).ShouldBeTrue(); + } + + private static void AssertVersions( + IEnumerable prompts, + string promptName, + params int[] expectedVersions) + { + prompts + .Where(prompt => prompt.Name == promptName) + .Select(prompt => prompt.VersionNumber) + .OrderBy(version => version) + .ShouldBe(expectedVersions); + } +} diff --git a/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/Operations/AIExecutionModeResolverTests.cs b/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/Operations/AIExecutionModeResolverTests.cs index 84a67f2af3..9396267b19 100644 --- a/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/Operations/AIExecutionModeResolverTests.cs +++ b/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/Operations/AIExecutionModeResolverTests.cs @@ -1,9 +1,14 @@ -using Microsoft.Extensions.Configuration; +using NSubstitute; using Shouldly; using System; -using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; +using Unity.AI.Domain; using Unity.AI.Operations; using Unity.AI.Runtime.Prompts; +using Volo.Abp.Domain.Repositories; using Xunit; namespace Unity.GrantManager.AI.Operations; @@ -11,48 +16,43 @@ namespace Unity.GrantManager.AI.Operations; public class AIExecutionModeResolverTests { [Fact] - public void ResolveMode_Uses_Operation_Override_Before_Default() + public async Task ResolveMode_Uses_Persisted_Operation_Mode() { - var configuration = new ConfigurationBuilder() - .AddInMemoryCollection(new Dictionary + var repository = Substitute.For>(); + repository.GetListAsync( + Arg.Any>>(), + cancellationToken: Arg.Any()) + .Returns(callInfo => { - ["Azure:Operations:Defaults:ExecutionMode"] = "Parallel", - [$"Azure:Operations:{AIPromptTypes.ApplicationScoring}:ExecutionMode"] = "Batch" - }) - .Build(); - - var resolver = new AIExecutionModeResolver(configuration); - - resolver.ResolveMode(AIPromptTypes.ApplicationScoring).ShouldBe(AIExecutionMode.Batch); - resolver.ResolveMode(AIPromptTypes.AttachmentSummary).ShouldBe(AIExecutionMode.Parallel); + var predicate = callInfo.Arg>>(); + return Task.FromResult(new[] + { + new AIOperation(Guid.NewGuid(), AIPromptTypes.ApplicationScoring, Guid.NewGuid()) + { + ExecutionMode = AIExecutionMode.Batch, + IsActive = true + } + }.Where(predicate.Compile()).ToList()); + }); + + var resolver = new AIExecutionModeResolver(repository); + + (await resolver.ResolveModeAsync(AIPromptTypes.ApplicationScoring)).ShouldBe(AIExecutionMode.Batch); } [Fact] - public void ResolveMode_Should_Throw_When_Default_Is_Missing() + public async Task ResolveMode_Throws_When_Operation_Is_Missing() { - var configuration = new ConfigurationBuilder() - .AddInMemoryCollection(new Dictionary()) - .Build(); - - var resolver = new AIExecutionModeResolver(configuration); - - var ex = Should.Throw(() => resolver.ResolveMode(AIPromptTypes.AttachmentSummary)); - ex.Message.ShouldContain(AIPromptTypes.AttachmentSummary); - } - - [Fact] - public void ResolveMode_Should_Throw_When_Configured_Value_Is_Invalid() - { - var configuration = new ConfigurationBuilder() - .AddInMemoryCollection(new Dictionary - { - ["Azure:Operations:Defaults:ExecutionMode"] = "Fast" - }) - .Build(); + var repository = Substitute.For>(); + repository.GetListAsync( + Arg.Any>>(), + cancellationToken: Arg.Any()) + .Returns(Task.FromResult(new System.Collections.Generic.List())); - var resolver = new AIExecutionModeResolver(configuration); + var resolver = new AIExecutionModeResolver(repository); - var ex = Should.Throw(() => resolver.ResolveMode(AIPromptTypes.AttachmentSummary)); - ex.Message.ShouldContain(AIPromptTypes.AttachmentSummary); + var exception = await Should.ThrowAsync( + () => resolver.ResolveModeAsync(AIPromptTypes.AttachmentSummary)); + exception.Message.ShouldContain(AIPromptTypes.AttachmentSummary); } } diff --git a/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/Operations/ApplicationScoringServiceTests.cs b/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/Operations/ApplicationScoringServiceTests.cs index 82f758a5d7..a5911fb7e7 100644 --- a/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/Operations/ApplicationScoringServiceTests.cs +++ b/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/Operations/ApplicationScoringServiceTests.cs @@ -1,4 +1,3 @@ -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; using Shouldly; @@ -9,10 +8,13 @@ using System.Threading; using System.Threading.Tasks; using Unity.AI; +using Unity.AI.Domain; using Unity.AI.Models; using Unity.AI.Operations; using Unity.AI.Requests; using Unity.AI.Responses; +using Unity.AI.Runtime.Prompts; +using Volo.Abp.Domain.Repositories; using Xunit; namespace Unity.GrantManager.AI.Operations; @@ -27,16 +29,26 @@ public async Task RegenerateAsync_Sequential_Mode_Uses_Per_Section_Requests() aiService.GenerateApplicationScoringAsync(Arg.Do(request => capturedRequests.Add(request)), Arg.Any()) .Returns(new ApplicationScoringResponse()); - var configuration = new ConfigurationBuilder() - .AddInMemoryCollection(new Dictionary + var operationRepository = Substitute.For>(); + operationRepository.GetListAsync( + Arg.Any>>(), + cancellationToken: Arg.Any()) + .Returns(callInfo => { - ["Azure:Operations:Defaults:ExecutionMode"] = "Sequential" - }) - .Build(); + var predicate = callInfo.Arg>>(); + return Task.FromResult(new[] + { + new AIOperation(Guid.NewGuid(), AIPromptTypes.ApplicationScoring, Guid.NewGuid()) + { + ExecutionMode = AIExecutionMode.Sequential, + IsActive = true + } + }.Where(predicate.Compile()).ToList()); + }); var service = new ApplicationScoringService( aiService, - new AIExecutionModeResolver(configuration), + new AIExecutionModeResolver(operationRepository), NullLogger.Instance); var result = await service.RegenerateAsync(new ApplicationScoringOperationInputDto diff --git a/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/Runtime/Execution/OpenAIConfigurationResolverTests.cs b/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/Runtime/Execution/OpenAIConfigurationResolverTests.cs index 7872860802..1ae5919a2b 100644 --- a/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/Runtime/Execution/OpenAIConfigurationResolverTests.cs +++ b/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/Runtime/Execution/OpenAIConfigurationResolverTests.cs @@ -43,7 +43,7 @@ public async Task Should_Resolve_Operation_Strictly_From_Database() modelRepository .GetAsync(modelId, cancellationToken: Arg.Any()) - .Returns(new AIModel(modelId, "Gpt5Mini") + .Returns(new AIModel(modelId, "gpt-5-mini", "OpenAI") { IsActive = true, SettingsJson = JsonSerializer.Serialize(new AIModelSettings @@ -92,7 +92,6 @@ public async Task Should_Resolve_Operation_Strictly_From_Database() new Dictionary { ["Azure:OpenAI:ApiKey"] = "secret", - ["Azure:OpenAI:Profiles:Gpt5Mini:DeploymentName"] = "gpt-5-mini" }, modelRepository, operationRepository, @@ -101,7 +100,7 @@ public async Task Should_Resolve_Operation_Strictly_From_Database() var settings = await resolver.ResolveOperationSettingsAsync(AIPromptTypes.ApplicationAnalysis); settings.ProviderName.ShouldBe("OpenAI"); - settings.ProfileName.ShouldBe("Gpt5Mini"); + settings.ProfileName.ShouldBe("gpt-5-mini"); settings.Endpoint.ShouldBe(new Uri("https://example.test")); settings.DeploymentName.ShouldBe("gpt-5-mini"); settings.Temperature.ShouldBe(0.25); @@ -121,7 +120,7 @@ public async Task Should_Throw_When_Operation_Is_Missing() modelRepository .GetAsync(modelId, cancellationToken: Arg.Any()) - .Returns(new AIModel(modelId, "Gpt5Mini") + .Returns(new AIModel(modelId, "gpt-5-mini", "OpenAI") { IsActive = true, SettingsJson = JsonSerializer.Serialize(new AIModelSettings @@ -169,7 +168,6 @@ public async Task Should_Throw_When_Operation_Is_Missing() new Dictionary { ["Azure:OpenAI:ApiKey"] = "secret", - ["Azure:OpenAI:Profiles:Gpt5Mini:DeploymentName"] = "gpt-5-mini" }, modelRepository, operationRepository, @@ -193,7 +191,7 @@ public async Task Should_Resolve_Model_Values_Strictly_From_Database() ArgumentNullException.ThrowIfNull(predicate); return Task.FromResult(new List { - new(Guid.NewGuid(), "Gpt5Mini") + new(Guid.NewGuid(), "gpt-5-mini", "OpenAI") { IsActive = true, SettingsJson = JsonSerializer.Serialize(new AIModelSettings @@ -208,10 +206,10 @@ public async Task Should_Resolve_Model_Values_Strictly_From_Database() var resolver = CreateResolver(modelRepository: modelRepository); resolver.ResolveProviderName().ShouldBe("OpenAI"); - (await resolver.ResolveDeploymentNameAsync()).ShouldBe("gpt-5-mini"); - (await resolver.ResolveEndpointAsync()).ShouldBe(new Uri("https://example.test")); - (await resolver.ResolveConfiguredTemperatureAsync()).ShouldBe(0.35); - (await resolver.ResolveMaxOutputTokenCountSupportedAsync()).ShouldBeTrue(); + (await resolver.ResolveDeploymentNameAsync("gpt-5-mini")).ShouldBe("gpt-5-mini"); + (await resolver.ResolveEndpointAsync("gpt-5-mini")).ShouldBe(new Uri("https://example.test")); + (await resolver.ResolveConfiguredTemperatureAsync("gpt-5-mini")).ShouldBe(0.35); + (await resolver.ResolveMaxOutputTokenCountSupportedAsync("gpt-5-mini")).ShouldBeTrue(); } [Fact] @@ -226,7 +224,7 @@ public async Task Should_Resolve_ApiKey_From_Provider_Secret() ArgumentNullException.ThrowIfNull(predicate); return Task.FromResult(new List { - new(Guid.NewGuid(), "Default") + new(Guid.NewGuid(), "Default", "OpenAI") { IsActive = true, SettingsJson = JsonSerializer.Serialize(new AIModelSettings @@ -261,7 +259,7 @@ public async Task Should_Resolve_Operation_Settings_From_Host_Context() modelRepository .GetAsync(modelId, cancellationToken: Arg.Any()) - .Returns(new AIModel(modelId, "Gpt5Mini") + .Returns(new AIModel(modelId, "gpt-5-mini", "OpenAI") { IsActive = true, SettingsJson = JsonSerializer.Serialize(new AIModelSettings @@ -340,7 +338,7 @@ public async Task Should_Select_Newest_Tenant_Prompt_And_Isolate_It_From_Global_ var modelRepository = Substitute.For>(); modelRepository.GetAsync(modelId, cancellationToken: Arg.Any()) - .Returns(new AIModel(modelId, "Gpt5Mini") + .Returns(new AIModel(modelId, "gpt-5-mini", "OpenAI") { IsActive = true, SettingsJson = JsonSerializer.Serialize(new AIModelSettings()) @@ -400,12 +398,8 @@ private static OpenAIConfigurationResolver CreateResolver( { var configurationValues = new Dictionary { - ["Azure:Operations:Defaults:Provider"] = "OpenAI", - ["Azure:Operations:Defaults:Profile"] = "Gpt5Mini", ["Azure:OpenAI:Endpoint"] = "https://example.test", - ["Azure:OpenAI:ApiKey"] = "secret", - ["Azure:OpenAI:Profiles:Gpt5Mini:DeploymentName"] = "gpt-5-mini", - ["Azure:OpenAI:Profiles:Gpt5Mini:MaxOutputTokenCountSupported"] = "true" + ["Azure:OpenAI:ApiKey"] = "secret" }; if (values != null) diff --git a/applications/Unity.GrantManager/test/Unity.GrantManager.Domain.Tests/Intake/Mapping/basicReportSchema.json b/applications/Unity.GrantManager/test/Unity.GrantManager.Domain.Tests/Intake/Mapping/basicReportSchema.json index 7ac7baf69c..6ba7f03fad 100644 --- a/applications/Unity.GrantManager/test/Unity.GrantManager.Domain.Tests/Intake/Mapping/basicReportSchema.json +++ b/applications/Unity.GrantManager/test/Unity.GrantManager.Domain.Tests/Intake/Mapping/basicReportSchema.json @@ -4809,7 +4809,7 @@ "id": "eaf9cpv", "key": "partner1Location", "data": { - "url": "https://unity-chefs-data.apps.silver.devops.gov.bc.ca/reference/common/locations-simple.json", + "url": "https://unity-chefs-data.apps.gold.devops.gov.bc.ca/reference/common/locations-simple.json", "json": "", "custom": "", "values": [ @@ -5787,7 +5787,7 @@ "id": "eywec1p", "key": "partner2Location", "data": { - "url": "https://unity-chefs-data.apps.silver.devops.gov.bc.ca/reference/common/locations-simple.json", + "url": "https://unity-chefs-data.apps.gold.devops.gov.bc.ca/reference/common/locations-simple.json", "json": "", "custom": "", "values": [ @@ -7495,7 +7495,7 @@ "id": "eo06q6", "key": "projectLocationDetailed", "data": { - "url": "https://unity-chefs-data.apps.silver.devops.gov.bc.ca/reference/common/locations-detailed.json", + "url": "https://unity-chefs-data.apps.gold.devops.gov.bc.ca/reference/common/locations-detailed.json", "json": "", "custom": "", "values": [ @@ -7981,7 +7981,7 @@ "id": "e11zgn", "key": "_Community1Location", "data": { - "url": "https://unity-chefs-data.apps.silver.devops.gov.bc.ca/reference/common/locations-simple.json", + "url": "https://unity-chefs-data.apps.gold.devops.gov.bc.ca/reference/common/locations-simple.json", "json": "", "custom": "", "values": [ @@ -8107,7 +8107,7 @@ "id": "ecup9jp", "key": "_Community2Location", "data": { - "url": "https://unity-chefs-data.apps.silver.devops.gov.bc.ca/reference/common/locations-simple.json", + "url": "https://unity-chefs-data.apps.gold.devops.gov.bc.ca/reference/common/locations-simple.json", "json": "", "custom": "", "values": [ @@ -8233,7 +8233,7 @@ "id": "ewc98lg", "key": "_Community3Location", "data": { - "url": "https://unity-chefs-data.apps.silver.devops.gov.bc.ca/reference/common/locations-simple.json", + "url": "https://unity-chefs-data.apps.gold.devops.gov.bc.ca/reference/common/locations-simple.json", "json": "", "custom": "", "values": [ diff --git a/documentation/External Dependency Summary.md b/documentation/External Dependency Summary.md index 0aa235f552..07082231d5 100644 --- a/documentation/External Dependency Summary.md +++ b/documentation/External Dependency Summary.md @@ -16,8 +16,8 @@ | 10 | OrgBook | ORGBOOK_API_BASE | orgbook.gov.bc.ca/api | Business registry search | | 11 | CHEFS | INTAKE_API_BASE | submit.digital.gov.bc.ca/app/api/v1 | Form definitions + submissions | | 12 | Azure OpenAI | OpenAI:Endpoint + OpenAI:ApiKey (Vault) | Azure-hosted (per env) | AI: AttachmentSummary, ApplicationAnalysis, ApplicationScoring | -| 13 | Reporting AI | REPORTING_AI | prod-unity-ai-reporting-d18498-prod.apps.silver.devops.gov.bc.ca | iFrame embed only — no backend HTTP call | -| 14 | Matomo Analytics | ANALYTICS_MATOMO_BASE | {env}-analytics-matomo.apps.silver.devops.gov.bc.ca | Browser-side page tracking | +| 13 | Reporting AI | REPORTING_AI | prod-unity-ai-reporting-d18498-prod.apps.gold.devops.gov.bc.ca | iFrame embed only — no backend HTTP call | +| 14 | Matomo Analytics | ANALYTICS_MATOMO_BASE | {env}-analytics-matomo.apps.gold.devops.gov.bc.ca | Browser-side page tracking | | 15 | HashiCorp Vault | (platform / ExternalSecrets) | vault.developer.gov.bc.ca | All API keys, OAuth secrets, DB credentials | ## Unity Applicant Portal (UAP): 4 External Dependencies @@ -26,7 +26,7 @@ |---|---|---|---|---| | 1 | Keycloak / LoginProxy | KEYCLOAK__AUTHSERVERURL | loginproxy.gov.bc.ca/auth | OIDC token exchange + userinfo | | 2 | OrgBook | orgbookApiUrl | orgbook.gov.bc.ca/api | Business registry - direct browser calls | -| 3 | Matomo Analytics | MATOMO__URL | {env}-analytics-matomo.apps.silver.devops.gov.bc.ca/ | Browser-side page tracking | +| 3 | Matomo Analytics | MATOMO__URL | {env}-analytics-matomo.apps.gold.devops.gov.bc.ca/ | Browser-side page tracking | | 4 | Unity Grant Manager API | Plugins:UNITY:Configuration:BaseUrl | http://{env}-unity-grantmanager-web (in-cluster) | Profile/tenant reads (HTTP) + Contact/Address mutations (RabbitMQ) | --- diff --git a/documentation/External-Dependency-Chart.md b/documentation/External-Dependency-Chart.md index 3874e4c446..066bd33524 100644 --- a/documentation/External-Dependency-Chart.md +++ b/documentation/External-Dependency-Chart.md @@ -63,8 +63,8 @@ graph TD | 10 | **OrgBook** | Business Registry Lookup | `ORGBOOK_API_BASE` | `https://orgbook.gov.bc.ca/api` | REST | GET search/autocomplete, GET topic/credential details | Application / OrgBookService | | 11 | **CHEFS / Intake** | Form Submissions (CHEFS) | `INTAKE_API_BASE` | `https://submit.digital.gov.bc.ca/app/api/v1` | REST | GET forms, GET versions, GET submissions | Application / FormsApiService, SubmissionAppService | | 12 | **Azure OpenAI** | AI Analysis & Scoring | `OpenAI:Endpoint` + `OpenAI:ApiKey` (Vault) | Azure-hosted endpoint (configured per env) | HTTPS / Azure SDK | Chat completions for AttachmentSummary, ApplicationAnalysis, ApplicationScoring | Unity.AI / OpenAITransportService | -| 13 | **Reporting AI Dashboard** | AI Reporting (Metabase-based) | `REPORTING_AI` | `https://prod-unity-ai-reporting-d18498-prod.apps.silver.devops.gov.bc.ca` | iFrame embed only (no backend HTTP call) | Embedded analytics dashboard | Unity.AI / AIReporting page | -| 14 | **Matomo Analytics** | Web Analytics | `ANALYTICS_MATOMO_BASE` | `https://prod-analytics-matomo.apps.silver.devops.gov.bc.ca` | Browser JS tag | Page views, user events | Web (frontend JS) | +| 13 | **Reporting AI Dashboard** | AI Reporting (Metabase-based) | `REPORTING_AI` | `https://prod-unity-ai-reporting-d18498-prod.apps.gold.devops.gov.bc.ca` | iFrame embed only (no backend HTTP call) | Embedded analytics dashboard | Unity.AI / AIReporting page | +| 14 | **Matomo Analytics** | Web Analytics | `ANALYTICS_MATOMO_BASE` | `https://prod-analytics-matomo.apps.gold.devops.gov.bc.ca` | Browser JS tag | Page views, user events | Web (frontend JS) | | 15 | **HashiCorp Vault** | Secrets Management | (platform) | `https://vault.developer.gov.bc.ca` | Vault API / ExternalSecrets operator | API keys, DB credentials, OAuth secrets | Platform | --- @@ -75,7 +75,7 @@ graph TD |---|---------|---------------|------------|----------------|----------|------------|-------| | 1 | **Keycloak / LoginProxy** | Authentication (SSO) | `KEYCLOAK__AUTHSERVERURL` / `KEYCLOAK__REALM` | `https://loginproxy.gov.bc.ca/auth` | OIDC | POST token exchange (code → token), GET /userinfo | Backend / AuthCallback | | 2 | **OrgBook** | Business Registry Lookup | `orgbookApiUrl` | `https://orgbook.gov.bc.ca/api` | REST | GET /v3/search/autocomplete, GET /v4/search/topic | Frontend / organization.component.ts | -| 3 | **Matomo Analytics** | Web Analytics | `MATOMO__URL` / `MATOMO__SITEID` | `https://prod-analytics-matomo.apps.silver.devops.gov.bc.ca/` | Browser JS tag | Page views, user events | Frontend | +| 3 | **Matomo Analytics** | Web Analytics | `MATOMO__URL` / `MATOMO__SITEID` | `https://prod-analytics-matomo.apps.gold.devops.gov.bc.ca/` | Browser JS tag | Page views, user events | Frontend | | 4 | **Unity Grant Manager API** | Internal Portal→UGM calls | `Plugins:UNITY:Configuration:BaseUrl` | `http://{env}-unity-grantmanager-web` (in-cluster) | REST (HTTP, in-cluster) | GET /api/app/applicant-profiles/tenants, GET /api/app/applicant-profiles/profile | Backend / UnityPlugin, Unity.Profile | --- @@ -140,9 +140,9 @@ Unity Applicant Portal (UAP) | Service | Dev | Test/UAT | Production | |---------|-----|----------|------------| | Keycloak | `dev.loginproxy.gov.bc.ca/auth` | `test.loginproxy.gov.bc.ca/auth` | `loginproxy.gov.bc.ca/auth` | -| Matomo | `dev-analytics-matomo.apps.silver.devops.gov.bc.ca` | `test-analytics-matomo.apps.silver.devops.gov.bc.ca` | `prod-analytics-matomo.apps.silver.devops.gov.bc.ca` | -| UGM ingress | `dev-unity.apps.silver.devops.gov.bc.ca` | `test-unity.apps.silver.devops.gov.bc.ca` | `prod-unity.apps.silver.devops.gov.bc.ca` | -| CHEFS (dev) | `chefs-test.apps.silver.devops.gov.bc.ca/app/api/v1` | — | `submit.digital.gov.bc.ca/app/api/v1` | +| Matomo | `dev-analytics-matomo.apps.gold.devops.gov.bc.ca` | `test-analytics-matomo.apps.gold.devops.gov.bc.ca` | `prod-analytics-matomo.apps.gold.devops.gov.bc.ca` | +| UGM ingress | `dev-unity.apps.gold.devops.gov.bc.ca` | `test-unity.apps.gold.devops.gov.bc.ca` | `prod-unity.apps.silver.devops.gov.bc.ca` | +| CHEFS (dev) | `chefs-test.apps.gold.devops.gov.bc.ca/app/api/v1` | — | `submit.digital.gov.bc.ca/app/api/v1` | | CSS Env | `dev` | — | `prod` | | Azure OpenAI | Per-env endpoint (Vault) | Per-env endpoint (Vault) | Per-env endpoint (Vault) | @@ -161,12 +161,12 @@ Unity Applicant Portal (UAP) | `GEOCODER_LOCATION_API_BASE` | `https://geocoder.api.gov.bc.ca` | UGM | | `GEOCODER_API_BASE` | `https://openmaps.gov.bc.ca/geo/pub/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=` | UGM | | `ORGBOOK_API_BASE` | `https://orgbook.gov.bc.ca/api` | UGM + UAP frontend | -| `ANALYTICS_MATOMO_BASE` | `https://prod-analytics-matomo.apps.silver.devops.gov.bc.ca` | UGM + UAP | -| `REPORTING_AI` | `https://prod-unity-ai-reporting-d18498-prod.apps.silver.devops.gov.bc.ca` | UGM (iFrame only) | +| `ANALYTICS_MATOMO_BASE` | `https://prod-analytics-matomo.apps.gold.devops.gov.bc.ca` | UGM + UAP | +| `REPORTING_AI` | `https://prod-unity-ai-reporting-d18498-prod.apps.gold.devops.gov.bc.ca` | UGM (iFrame only) | | `INTAKE_API_BASE` | `https://submit.digital.gov.bc.ca/app/api/v1` | UGM | | `OpenAI:Endpoint` + `OpenAI:ApiKey` | Azure OpenAI (per env, from Vault) | UGM | | `KEYCLOAK__AUTHSERVERURL` | `https://loginproxy.gov.bc.ca/auth` | UAP | -| `MATOMO__URL` | `https://prod-analytics-matomo.apps.silver.devops.gov.bc.ca/` | UAP | +| `MATOMO__URL` | `https://prod-analytics-matomo.apps.gold.devops.gov.bc.ca/` | UAP | | `Plugins:UNITY:Configuration:BaseUrl` | `http://{env}-unity-grantmanager-web` (in-cluster) | UAP | --- diff --git a/documentation/reporting/reporting-configuration.md b/documentation/reporting/reporting-configuration.md index 9d607119b1..3e7e65bf8a 100644 --- a/documentation/reporting/reporting-configuration.md +++ b/documentation/reporting/reporting-configuration.md @@ -119,15 +119,19 @@ This ensures established mappings are preserved and only genuinely new fields re ## Field Column Definitions -The configuration table displays one row per mappable field from the source. The first three column headers change depending on the active provider tab; the remaining three are constant across all providers. +The configuration table displays one row per mappable field from the source. For the `worksheet` / `worksheet_consolidated` providers, **Worksheet Name** is the leftmost column. After that, the next three column headers change depending on the active provider tab; the remaining three are constant across all providers. -**Provider-specific headers (columns 1–3):** +**Worksheet-only header (leftmost column):** **Worksheet Name** — shown only for the `worksheet` / `worksheet_consolidated` providers; absent (and every other column shifts one position left) for `formversion` / `formversion_consolidated` / `scoresheet`. + +**Provider-specific headers (next three columns):** - `formversion` / `formversion_consolidated` — **CHEFS Label**, **CHEFS Property Name**, **CHEFS Type** - `worksheet` / `worksheet_consolidated` — **Worksheet Label**, **Worksheet Property Name**, **Worksheet Type** - `scoresheet` — **Scoresheet Label**, **Scoresheet Property Name**, **Scoresheet Type** -**Constant headers (columns 4–6):** **Path**, **Report Column**, **Type Path** +**Constant headers (following three columns):** **Path**, **Report Column**, **Type Path** + +**Source Order** — present for every provider as a trailing column, hidden by default (see below). ### Label *(CHEFS Label / Worksheet Label / Scoresheet Label)* @@ -184,6 +188,26 @@ Indicates which form versions contain this field. Only present when using `formv - `v1` — field exists only in version 1 - `v1, v3` — field exists in versions 1 and 3 but not in all versions +### Worksheet Name *(worksheet providers only)* + +The name of the source `Worksheet` this field came from, including its version suffix (e.g., `grant_application-v2`). Only present for the `worksheet` and `worksheet_consolidated` providers; blank for `formversion` and `scoresheet`. + +### Source Order + +A numeric column, hidden by default, giving each field's 1-based position in the provider's calculated overall field order. Present for every provider. Not user-editable — for saved configurations it reflects the value stored in the mapping (older mappings are backfilled from live provider metadata) and is not affected by saved Report Column values. Users can reveal it via the column picker to inspect the underlying order, or to manually re-sort back to it after sorting by another column. + +### Default Sort Order + +For the `worksheet` and `worksheet_consolidated` providers, rows are returned pre-sorted (and numbered via **Source Order**) by: + +1. **Worksheet Name**, A–Z (the version suffix means later versions naturally sort after earlier ones) +2. **Section order** within each worksheet +3. **Field layout order** within each section (top to bottom, left to right) +4. **Checkbox group option order** — when a single Checkbox Group field expands into one row per option, the rows follow the order the options are defined on the field (not alphabetical) +5. **Data grid column order** — when a single Data Grid field expands into one row per column, the rows follow the order the columns are defined on the grid. For a mixed dynamic/static grid, CHEFS-extracted dynamic columns are emitted first (in CHEFS's own order), followed by any additional statically-defined columns not already covered + +For the `worksheet` and `worksheet_consolidated` providers, the table's default sort is by **Source Order**, ascending, until the user explicitly sorts by another column, at which point the existing sort/column-visibility persistence takes over. Other providers (`formversion`, `formversion_consolidated`, `scoresheet`) continue to default-sort by Label, unchanged — their **Source Order** column is still populated (reflecting each provider's natural field order) and available via the column picker, but is not the default sort key. + --- ## Column Name Validation