Template
chore: deploy initial code base #1
@@ -0,0 +1,172 @@
|
||||
# Strata Continuous Improvement — Claude Code Guide
|
||||
|
||||
## About
|
||||
|
||||
Healthcare finance analytics microservice owned by **The-Outliers** team. Helps hospitals identify cost-saving opportunities across encounter, charge code, general ledger, payroll, utilization variation, quality variation, network, and strategic opportunity domains.
|
||||
|
||||
**Tech stack:** .NET 8 (C# 12) + React 17 + TypeScript 4.9 + PostgreSQL + SQL Server + Snowflake
|
||||
|
||||
## Shell / repo navigation
|
||||
|
||||
- Claude is launched from the repo root (`C:/git/microservices/continuousimprovement`). Do not `cd` to the repo in commands.
|
||||
- Avoid compound commands that combine `cd` with pipes/redirection (e.g., `cd ... && ... 2>/dev/null | head ...`) because they trigger manual-approval warnings.
|
||||
- Prefer:
|
||||
- `find . ...` (when already in repo root), or
|
||||
- `find "C:/git/microservices/continuousimprovement" ...` (when an explicit path is needed).
|
||||
- If output limiting is needed, prefer `| head -50` without `2>/dev/null`, or run redirection in a separate step.
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
# Backend
|
||||
dotnet restore
|
||||
dotnet build
|
||||
dotnet test
|
||||
|
||||
# Frontend (run from src/Strata.ContinuousImprovement.Web/continuousimprovement/)
|
||||
npm install
|
||||
npm start # Dev server
|
||||
npm test # Interactive test runner
|
||||
npm run test-ci # CI test runner (no watch)
|
||||
```
|
||||
|
||||
## Coding Standards
|
||||
|
||||
Follow `.github/copilot-instructions.md` for naming, capitalization, project organization, API routes, DTOs, and principles. Key points:
|
||||
- Booleans: `is`, `has`, `can` prefix
|
||||
- Methods/properties/constants: PascalCase; variables: camelCase
|
||||
- No full acronyms (`EntityIdsCsv` not `EntityIdsCSV`)
|
||||
- Organize by business domain, not by type
|
||||
- RESTful routes: plural nouns, kebab-case
|
||||
- DTOs: no `DTO` suffix, only create when restricting/combining models
|
||||
|
||||
## Architecture
|
||||
|
||||
### Backend (C#/.NET 8)
|
||||
|
||||
| Project | Purpose | Path |
|
||||
|---------|---------|------|
|
||||
| **Api** | ASP.NET Core Web API host, controllers | `src/Strata.ContinuousImprovement.Api/` |
|
||||
| **Biz** | All business logic, services, DI registration | `src/Strata.ContinuousImprovement.Biz/` |
|
||||
| **Client** | HTTP client wrapper (`IContinuousImprovementService`) | `src/Strata.ContinuousImprovement.Client/` |
|
||||
| **Service** | Windows/hosted service, Hangfire worker host | `src/Strata.ContinuousImprovement.Service/` |
|
||||
| **Dashboard** | Separate web host (Startup + Program) | `src/Strata.ContinuousImprovement.Dashboard/` |
|
||||
| **DecisionSupport.Biz** | Encounter query services, Snowflake queries | `src/Strata.DecisionSupport.Biz/` |
|
||||
| **DecisionSupport.Models** | Shared encounter/Jazz/Snowflake DTOs | `src/Strata.DecisionSupport.Models/` |
|
||||
|
||||
### Frontend (React 17 + TypeScript)
|
||||
|
||||
**Root:** `src/Strata.ContinuousImprovement.Web/continuousimprovement/`
|
||||
|
||||
Feature modules live directly under `src/` (not a `features/` folder):
|
||||
- `charge-code/`, `encounter/`, `exploration/`, `free-form/`, `general-ledger/`, `initiatives/`, `network/`, `opportunities/`, `payroll/`, `quality-variation/`, `strategic-opportunities/`, `utilization-variation/`, `configuration/`
|
||||
|
||||
Each module follows: page component + `/components` + `/hooks` + `/data` + `/utils`
|
||||
|
||||
**Shared module:** `src/shared/` — components, hooks, data services, contexts, constants
|
||||
|
||||
### Key Constraints
|
||||
- **React 17** — do NOT use React 18 features (useId, automatic batching, etc.)
|
||||
- **react-router v5** — use `Switch`/`Route`/`Redirect`, NOT v6 patterns
|
||||
- **Create React App** (`react-scripts ^5.0.1`) — not Vite
|
||||
|
||||
## Strata Ecosystem (Proprietary — No Public Docs)
|
||||
|
||||
These packages are internal to Strata. No public documentation exists. Read source/types when needed:
|
||||
|
||||
| Package | Purpose |
|
||||
|---------|---------|
|
||||
| `@strata/tempo` | Design system (v6.33+). Import: `@strata/tempo/lib/<component>`. Query Tempo MCP server for docs. |
|
||||
| `@strata/core` | Auth (`useOidcSession`), HTTP (`getSecureService`), URL helpers |
|
||||
| `@strata/charts` | Charting library |
|
||||
| `@strata/jazz` | Jazz platform integration |
|
||||
| `@strata/logging` | Client-side logging |
|
||||
| `@strata/navbar` | Shared navigation bar |
|
||||
| `@strata/score` | Score calculations |
|
||||
| `@strata/signalr` | SignalR client wrapper |
|
||||
| `@strata/settings` | Settings service |
|
||||
| `@strata/intl` | Internationalization |
|
||||
| `Strata.CS.Jazz.*` | Backend Jazz platform libraries (DI, EF, auth, caching) |
|
||||
| `Riok.Mapperly` | Compile-time object mapping source generator |
|
||||
| `EasyCaching` | In-memory caching (provider: `continuousImprovement_cache`) |
|
||||
|
||||
## Commit Requirements
|
||||
|
||||
Format: `type(scope): subject` — enforced by `.commitlintrc.js`
|
||||
|
||||
**Types:** `build`, `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`
|
||||
**Scopes:** `api`, `biz`, `client`, `dashboard`, `service`, `web`, `ds-web`, `ds-models`
|
||||
|
||||
Rules:
|
||||
- Subject max 72 chars, no period, no sentence/start/pascal/upper case
|
||||
- Scope required (warning), lowercase only
|
||||
- Body and footer must have leading blank line
|
||||
- Footer: `Refs: JAZZ-XXXX` (Jira ticket reference)
|
||||
|
||||
Example:
|
||||
```
|
||||
feat(biz): add case type family drill-down for exploration
|
||||
|
||||
Refs: JAZZ-12345
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Backend (NUnit + Moq)
|
||||
- Unit tests: `tests/Strata.ContinuousImprovement.*.Test.Unit/`
|
||||
- Integration tests: `tests/Strata.ContinuousImprovement.*.Test.Integration/`
|
||||
- Jazz EF stub: `tests/Strata.ContinuousImprovement.JazzEntityFrameworkStub/`
|
||||
- Pattern: `[TestFixture]`, `[Test]`, `[SetUp]`/`[OneTimeSetUp]`, `Mock<IService>`
|
||||
- Run: `dotnet test`
|
||||
|
||||
### Frontend (Jest + Testing Library)
|
||||
- Test files: `<Component>.test.tsx` co-located with components
|
||||
- Custom render wrapper: `src/test-utils/testUtils.tsx` (wraps MemoryRouter + contexts)
|
||||
- Pattern: `render()`, `screen.findByRole()`, `jest.spyOn(service, 'method')`
|
||||
- Run: `npm test` (from `continuousimprovement/` dir) or `npm run test-ci` for CI
|
||||
|
||||
### E2E (Playwright)
|
||||
- Located in separate test infrastructure
|
||||
|
||||
## Feature Flags
|
||||
|
||||
Backend pattern:
|
||||
```csharp
|
||||
// Inject IFeatureFlagWrapper, call named convenience method or generic:
|
||||
await featureFlagWrapper.IsCiBenchmarkingEnabled();
|
||||
await featureFlagWrapper.IsFeatureFlagOn(FeatureFlag.ChargeCodeBeta, clientId);
|
||||
```
|
||||
|
||||
Frontend pattern:
|
||||
```tsx
|
||||
const { isBenchmarkingEnabled } = useFeatureFlags();
|
||||
```
|
||||
|
||||
Centralized definitions: `Biz/Utilities/FeatureFlag.cs` (enum) + `FeatureFlagLookup.cs` (key mapping)
|
||||
|
||||
See `docs/FeatureFlagUsage.md` for full flag inventory.
|
||||
|
||||
## Key Patterns
|
||||
|
||||
- **Interface-based DI** — All services have `IXxxService` interface, registered in `Biz/Configurations/ContinuousImprovementServiceExtensions.cs` via `services.AddScoped<IXxxService, XxxService>()`
|
||||
- **Primary constructor DI** (C# 12) — Services use `public class XxxService(IDep1 dep1, IDep2 dep2) : IXxxService`
|
||||
- **Riok.Mapperly** — Compile-time mapping via `[Mapper]` attribute on static partial classes (in `DecisionSupport.Biz/Mapperly/`)
|
||||
- **Embedded SQL resources** — `.sql` files as embedded resources, loaded via `Assembly.GetExecutingAssembly().ReadResource("QueryName.sql")` (see `Biz/Utilities/ResourceExtensions.cs`)
|
||||
- **EasyCaching** — In-memory provider named `continuousImprovement_cache`
|
||||
- **SignalR** — Typed hub `NotificationHub : Hub<INotificationHub>`, mapped at `/api/NotificationHub`
|
||||
- **Multi-database** — PostgreSQL (CentralDbContext), SQL Server (JazzDbContext via async factory), Snowflake (SnowflakeDatabaseContext)
|
||||
- **API versioning** — `[ApiVersion("1.0")]` + `[Route("api/v{api-version:apiVersion}/[controller]")]` + `SlugifyParameterTransformer`
|
||||
- **Frontend data services** — `getSecureService()` from `@strata/core/lib` providing typed HTTP helpers, exported as plain objects implementing interfaces
|
||||
- **Frontend state** — React Context pattern (`FeatureFlagContext`, `ConfigurationsContext`, `UserSettingsContext`) + custom hooks
|
||||
|
||||
## MCP Servers
|
||||
|
||||
- **Tempo** — Query `@strata/tempo` component docs, props, examples. Always check before using Tempo components.
|
||||
- **Jira/Atlassian** — Fetch ticket details, search issues, manage PRs via `mcp__atlassian__*` tools.
|
||||
|
||||
## Reference Docs
|
||||
|
||||
- `.claude/docs/backend-patterns.md` — Biz module anatomy, DI, controllers, EF, SQL, testing
|
||||
- `.claude/docs/frontend-patterns.md` — Feature modules, hooks, data services, Tempo, testing
|
||||
- `.claude/docs/agent-workflow.md` — AI-assisted development pipeline and context management
|
||||
- `.claude/docs/adversarial-prompts.md` — Review criteria templates
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
# Contributing Guidelines
|
||||
|
||||
Welcome, brave engineer. Before you type `git commit -m "oops"` and vanish for 3 weeks of PTO, let's align on how commits work in this repo.
|
||||
|
||||
We follow [**Conventional Commits**](https://www.conventionalcommits.org/) with **semantic versioning**.\
|
||||
This gives us:
|
||||
|
||||
- Automatic changelogs (no one likes writing them manually)
|
||||
- Predictable version bumps
|
||||
- Clear history that future-you (or your replacement) can actually read
|
||||
|
||||
---
|
||||
|
||||
## 1. Commit Message Format
|
||||
|
||||
Every commit **MUST** follow this format:
|
||||
|
||||
```
|
||||
<type>(<scope>): <description>
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional BREAKING CHANGE: ...]
|
||||
|
||||
[optional footer(s)]
|
||||
```
|
||||
|
||||
- \`\` → what you did
|
||||
- \`\` *(optional, but strongly encouraged)* → where you did it
|
||||
- \`\` → short explanation (imperative tense)
|
||||
- **Body** → additional details, rationale, or context (wrap at 72 chars if possible)
|
||||
- **Footer** → metadata such as references to backlog items or related issues
|
||||
|
||||
> ❗ There must be a blank line separating `BREAKING CHANGE:` from any footer lines.
|
||||
|
||||
### Examples
|
||||
|
||||
```
|
||||
feat(auth): add token refresh endpoint
|
||||
|
||||
Added a new endpoint for refreshing authentication tokens to reduce
|
||||
full login frequency.
|
||||
|
||||
Refs: JIRA-1234
|
||||
```
|
||||
|
||||
```
|
||||
fix(api): correctly parse null values
|
||||
|
||||
Previously, null values in payloads caused 500 errors due to improper
|
||||
validation. Updated parser to handle null safely.
|
||||
|
||||
BREAKING CHANGE: Parser behavior has changed for null payloads.
|
||||
|
||||
Refs: JIRA-9876
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Allowed Commit Types
|
||||
|
||||
| Type | When to Use |
|
||||
| ---------- | ----------------------------------------- |
|
||||
| `build` | Build system changes (configs, scripts) |
|
||||
| `feat` | A new feature |
|
||||
| `fix` | A bug fix |
|
||||
| `docs` | Documentation updates |
|
||||
| `style` | Non-functional style changes (formatting) |
|
||||
| `refactor` | Code change that isn’t a bug or feature |
|
||||
| `test` | Adding/fixing tests |
|
||||
| `chore` | Maintenance tasks (deps, configs) |
|
||||
|
||||
---
|
||||
|
||||
## 3. Scopes
|
||||
|
||||
Scopes keep commits relevant. Commonly you'll find service, or module names, used as the scope:
|
||||
|
||||
```
|
||||
feat(api): add user endpoint
|
||||
fix(web): handle expired token
|
||||
```
|
||||
> While unrelated areas should be split into separate commits, multiple scopes can be combined in a single commit if they are related:
|
||||
`refactor(web,api): clean up regex for widget payloads`
|
||||
|
||||
If you're touching multiple unrelated areas, split the work.\
|
||||
If you’re touching the **entire repo**, you may omit the scope (e.g. `chore: update prettier config`).
|
||||
|
||||
---
|
||||
|
||||
## 4. Commit Body
|
||||
|
||||
Use the body to:
|
||||
|
||||
- Explain **why** the change was made
|
||||
- Add context or reasoning (if it’s not obvious from the diff)
|
||||
- Mention relevant technical details
|
||||
|
||||
> ❗ Keep the body wrapped at 72 characters where possible for better readability in CLI tools.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
fix(auth): improve token handling
|
||||
|
||||
Added stricter validation to prevent expired tokens from being used
|
||||
in refresh calls. This fixes intermittent 401 errors for long-lived
|
||||
sessions.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Commit Footer
|
||||
|
||||
Use the footer for:
|
||||
|
||||
- `BREAKING CHANGE:` declarations (with a blank line after it)
|
||||
- References to backlog items, tickets, or issues
|
||||
- Co-authorship metadata if needed (`Co-authored-by:`)
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
feat(ui): add dark mode toggle
|
||||
|
||||
Added a dark mode toggle in the user settings page.
|
||||
|
||||
BREAKING CHANGE: Removed old theme switcher.
|
||||
|
||||
Refs: JIRA-4567
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Linting & Enforcement
|
||||
|
||||
This repo uses:
|
||||
|
||||
- [**commitlint**](https://github.com/conventional-changelog/commitlint) to reject bad commit messages
|
||||
- [**husky**](https://typicode.github.io/husky) to run lint checks pre-commit
|
||||
|
||||
After cloning the repository, run `npm install` to ensure husky hooks are installed and initialized locally. If you skip this step, GitHub Actions will run the same commit linting on your pull requests. Any bad commits that bypass local checks will result in a failed PR check.
|
||||
|
||||
If you try to commit `do some stuff`, the hook will fail and mock you.\
|
||||
Run `npm run commit` if you want a guided prompt.
|
||||
|
||||
---
|
||||
|
||||
## 7. Pull Requests
|
||||
|
||||
- Keep PRs small and focused.
|
||||
- Please use `--amend` commits or `--fixup` commits where appropriate
|
||||
- Squash `fixup:` commits, and any commits that don’t add value (e.g. `chore: typo`).
|
||||
- Ensure commit messages still follow Conventional Commits after squashing.
|
||||
|
||||
---
|
||||
|
||||
## 8. Versioning
|
||||
|
||||
We use **semantic versioning**:
|
||||
|
||||
- `fix:` → Patch (`1.0.1`)
|
||||
- `feat:` → Minor (`1.1.0`)
|
||||
- `BREAKING CHANGE:` → Major (`2.0.0`)
|
||||
|
||||
Your commit messages drive the versioning calculation.
|
||||
|
||||
---
|
||||
|
||||
### TL;DR
|
||||
|
||||
- Use Conventional Commits.
|
||||
- Include a scope if it makes sense.
|
||||
- Write meaningful bodies when needed.
|
||||
- Use footers for breaking changes or ticket references.
|
||||
- Leave a blank line between BREAKING CHANGE and footer.
|
||||
- Your branch's commit history should tell the story of what you did.
|
||||
- Bad commits will be rejected by hooks, mocked by your peers, and possibly framed on Slack or in some PowerPoint presentation as "what not to do."
|
||||
|
||||
---
|
||||
|
||||
Happy committing!
|
||||
|
||||
+284
@@ -0,0 +1,284 @@
|
||||
ARG PROJECT=Strata.ContinuousImprovement
|
||||
ARG DSPROJECT=Strata.DecisionSupport
|
||||
ARG VERSION=0.0.0
|
||||
ARG SONARURL=''
|
||||
ARG SONARLOGIN=''
|
||||
ARG SONARBRANCH=''
|
||||
|
||||
##############
|
||||
# Base image #
|
||||
##############
|
||||
FROM ecr.ops.stratanetwork.net/strata.microsoft.dotnet.aspnet:8.0 AS base
|
||||
|
||||
# Add TMZ files for CST
|
||||
RUN cp /usr/share/zoneinfo/America/Chicago "/usr/share/zoneinfo/Central Standard Time"
|
||||
|
||||
RUN sed -i 's/^Components: main$/& contrib/' /etc/apt/sources.list.d/debian.sources \
|
||||
&& apt-get -qq update --allow-releaseinfo-change \
|
||||
&& apt-get -qq install -y \
|
||||
libc6 \
|
||||
libc6-dev \
|
||||
libgdiplus \
|
||||
libx11-dev \
|
||||
ttf-mscorefonts-installer \
|
||||
fontconfig \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& ln -s /usr/lib/x86_64-linux-gnu/libdl.so.2 /usr/lib/x86_64-linux-gnu/libdl.so
|
||||
|
||||
###############
|
||||
# Build image #
|
||||
###############
|
||||
FROM ecr.ops.stratanetwork.net/strata.microsoft.dotnet.sdk:8.0 AS build
|
||||
ARG PROJECT
|
||||
ARG DSPROJECT
|
||||
ARG VERSION
|
||||
ARG SONARURL
|
||||
ARG SONARLOGIN
|
||||
ARG SONARBRANCH
|
||||
|
||||
# Add TMZ files for CST
|
||||
RUN cp /usr/share/zoneinfo/America/Chicago "/usr/share/zoneinfo/Central Standard Time"
|
||||
|
||||
RUN sed -i 's/^Components: main$/& contrib/' /etc/apt/sources.list.d/debian.sources \
|
||||
&& apt-get -qq update --allow-releaseinfo-change \
|
||||
&& apt-get -qq install -y \
|
||||
libc6 \
|
||||
libc6-dev \
|
||||
libgdiplus \
|
||||
libx11-dev \
|
||||
ttf-mscorefonts-installer \
|
||||
fontconfig \
|
||||
awscli \
|
||||
jq \
|
||||
&& rm -rf /var/lib/apt/lists/*\
|
||||
&& ln -s /usr/lib/x86_64-linux-gnu/libdl.so.2 /usr/lib/x86_64-linux-gnu/libdl.so
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
|
||||
# Copy project files and restore
|
||||
COPY ["${PROJECT}.sln", "${PROJECT}.sln"]
|
||||
COPY src/**/*.csproj ./
|
||||
RUN for file in $(ls *.csproj); do mkdir -p src/${file%.*}/ && mv $file src/${file%.*}/; done
|
||||
COPY tests/**/*.csproj ./
|
||||
RUN for file in $(ls *.csproj); do mkdir -p tests/${file%.*}/ && mv $file tests/${file%.*}/; done
|
||||
|
||||
RUN dotnet restore "${PROJECT}.sln" \
|
||||
--source https://api.nuget.org/v3/index.json \
|
||||
--source https://proget.sdt.local/nuget/nuget/v3/index.json
|
||||
|
||||
|
||||
# Copy files and build
|
||||
COPY . .
|
||||
|
||||
RUN if [ "${SONARLOGIN}" != "" ] ; then dotnet sonarscanner begin \
|
||||
/k:"${PROJECT}" \
|
||||
/d:sonar.scm.provider=git \
|
||||
/d:sonar.host.url="${SONARURL}" \
|
||||
${SONARBRANCH} \
|
||||
/d:sonar.login="${SONARLOGIN}" \
|
||||
/d:sonar.cs.opencover.reportsPaths="/test-results/cover.xml" \
|
||||
/d:sonar.cs.vstest.reportsPaths="/test-results/*.trx" \
|
||||
/d:sonar.dependencyCheck.jsonReportPath=/src/buildlogs/dependency-check-report.json \
|
||||
/d:sonar.dependencyCheck.htmlReportPath=/src/buildlogs/dependency-check-report.html \
|
||||
/v:sonar.projectVersion="${VERSION}"; fi
|
||||
|
||||
|
||||
RUN dotnet build "${PROJECT}.sln" \
|
||||
--configuration Release \
|
||||
--no-restore
|
||||
|
||||
# Run tests
|
||||
# #############################
|
||||
# # ContinuousImprovement Api #
|
||||
# #############################
|
||||
|
||||
RUN dotnet test "tests/${PROJECT}.Api.Test.Unit/${PROJECT}.Api.Test.Unit.csproj" \
|
||||
--configuration Release \
|
||||
--no-restore \
|
||||
--no-build \
|
||||
--verbosity=normal \
|
||||
--logger="trx;LogFileName=/test-results/api-api-tests.trx" \
|
||||
-p:CollectCoverage=true \
|
||||
-p:CoverletOutput='/test-results/' \
|
||||
-p:Exclude=\"[Strata.ContinuousImprovement.Biz.Test.Unit]*,[Strata.ContinuousImprovement.JazzEntityFrameworkStub]*\"
|
||||
|
||||
RUN CREDS=$(aws sts assume-role --role-arn arn:aws:iam::809820578507:role/sdt-continuous-improvement-service-role --role-session-name "DockerBuildSession"); \
|
||||
export AWS_ACCESS_KEY_ID="$(echo $CREDS | jq -r '.Credentials.AccessKeyId')"; \
|
||||
export AWS_SECRET_ACCESS_KEY="$(echo $CREDS | jq -r '.Credentials.SecretAccessKey')"; \
|
||||
export AWS_SESSION_TOKEN="$(echo $CREDS | jq -r '.Credentials.SessionToken')"; \
|
||||
dotnet test "tests/${PROJECT}.Api.Test.Integration/${PROJECT}.Api.Test.Integration.csproj" \
|
||||
--configuration Release \
|
||||
--no-restore \
|
||||
--no-build \
|
||||
--verbosity=normal \
|
||||
--logger="trx;LogFileName=/test-results/integration-tests-api.trx" \
|
||||
-p:CollectCoverage=true \
|
||||
-p:CoverletOutputFormat=opencover \
|
||||
-p:CoverletOutput='/test-results/' \
|
||||
-p:MergeWith='/test-results/coverage.json' \
|
||||
-p:Exclude=\"[Strata.ContinuousImprovement.Api.Test.Unit]*,[Strata.ContinuousImprovement.JazzEntityFrameworkStub]*\"
|
||||
|
||||
# #############################
|
||||
# # ContinuousImprovement Biz #
|
||||
# #############################
|
||||
|
||||
RUN dotnet test "/src/tests/${PROJECT}.Biz.Test.Unit/${PROJECT}.Biz.Test.Unit.csproj" \
|
||||
--configuration Release \
|
||||
--no-restore \
|
||||
--no-build \
|
||||
--verbosity=normal \
|
||||
--logger="trx;LogFileName=/test-results/unit-tests-biz.trx" \
|
||||
-p:CollectCoverage=true \
|
||||
-p:CoverletOutput='/test-results/' \
|
||||
-p:MergeWith='/test-results/coverage.json' \
|
||||
-p:Exclude=\"[Strata.ContinuousImprovement.Biz.Test.Unit]*,[Strata.ContinuousImprovement.JazzEntityFrameworkStub]*\"
|
||||
|
||||
RUN CREDS=$(aws sts assume-role --role-arn arn:aws:iam::809820578507:role/sdt-continuous-improvement-service-role --role-session-name "DockerBuildSession"); \
|
||||
export AWS_ACCESS_KEY_ID="$(echo $CREDS | jq -r '.Credentials.AccessKeyId')"; \
|
||||
export AWS_SECRET_ACCESS_KEY="$(echo $CREDS | jq -r '.Credentials.SecretAccessKey')"; \
|
||||
export AWS_SESSION_TOKEN="$(echo $CREDS | jq -r '.Credentials.SessionToken')"; \
|
||||
dotnet test "tests/${PROJECT}.Biz.Test.Integration/${PROJECT}.Biz.Test.Integration.csproj" \
|
||||
--configuration Release \
|
||||
--no-restore \
|
||||
--no-build \
|
||||
--verbosity=normal \
|
||||
--logger="trx;LogFileName=/test-results/integration-tests-biz.trx" \
|
||||
-p:CollectCoverage=true \
|
||||
-p:CoverletOutputFormat=opencover \
|
||||
-p:CoverletOutput='/test-results/' \
|
||||
-p:MergeWith='/test-results/coverage.json' \
|
||||
-p:Exclude=\"[Strata.ContinuousImprovement.Biz.Test.Unit]*,[Strata.ContinuousImprovement.JazzEntityFrameworkStub]*\"
|
||||
|
||||
# ################################
|
||||
# # ContinuousImprovement Client #
|
||||
# ################################
|
||||
|
||||
RUN dotnet test "tests/${PROJECT}.Client.Test.Unit/${PROJECT}.Client.Test.Unit.csproj" \
|
||||
--configuration Release \
|
||||
--no-restore \
|
||||
--no-build \
|
||||
--verbosity=normal \
|
||||
--logger="trx;LogFileName=/test-results/unit-tests-client.trx" \
|
||||
-p:CollectCoverage=true \
|
||||
-p:CoverletOutputFormat=opencover \
|
||||
-p:CoverletOutput='/test-results/' \
|
||||
-p:MergeWith='/test-results/coverage.json' \
|
||||
-p:Exclude=\"[Strata.ContinuousImprovement.Client.Test.Unit]*,[Strata.ContinuousImprovement.JazzEntityFrameworkStub]*\"
|
||||
|
||||
RUN CREDS=$(aws sts assume-role --role-arn arn:aws:iam::809820578507:role/sdt-continuous-improvement-service-role --role-session-name "DockerBuildSession"); \
|
||||
export AWS_ACCESS_KEY_ID="$(echo $CREDS | jq -r '.Credentials.AccessKeyId')"; \
|
||||
export AWS_SECRET_ACCESS_KEY="$(echo $CREDS | jq -r '.Credentials.SecretAccessKey')"; \
|
||||
export AWS_SESSION_TOKEN="$(echo $CREDS | jq -r '.Credentials.SessionToken')"; \
|
||||
dotnet test "tests/${PROJECT}.Client.Test.Integration/${PROJECT}.Client.Test.Integration.csproj" \
|
||||
--configuration Release \
|
||||
--no-restore \
|
||||
--no-build \
|
||||
--verbosity=normal \
|
||||
--logger="trx;LogFileName=/test-results/integration-tests-client.trx" \
|
||||
-p:CollectCoverage=true \
|
||||
-p:CoverletOutputFormat=opencover \
|
||||
-p:CoverletOutput='/test-results/' \
|
||||
-p:MergeWith='/test-results/coverage.json' \
|
||||
-p:Exclude=\"[Strata.ContinuousImprovement.Client.Test.Integration]*,[Strata.ContinuousImprovement.JazzEntityFrameworkStub]*\"
|
||||
|
||||
# ###################
|
||||
# # DecisionSupport #
|
||||
# ###################
|
||||
|
||||
RUN dotnet test "tests/${DSPROJECT}.Biz.Test.Unit/${DSPROJECT}.Biz.Test.Unit.csproj" \
|
||||
--configuration Release \
|
||||
--no-restore \
|
||||
--no-build \
|
||||
--verbosity=normal \
|
||||
--logger="trx;LogFileName=/test-results/unit-tests-dsbiz.trx" \
|
||||
-p:CollectCoverage=true \
|
||||
-p:CoverletOutputFormat=opencover \
|
||||
-p:CoverletOutput='/test-results/' \
|
||||
-p:MergeWith='/test-results/coverage.json' \
|
||||
-p:Exclude=\"[Strata.ContinuousImprovement.Biz.Test.Unit]*,[Strata.ContinuousImprovement.JazzEntityFrameworkStub]*\"
|
||||
|
||||
RUN CREDS=$(aws sts assume-role --role-arn arn:aws:iam::809820578507:role/sdt-continuous-improvement-service-role --role-session-name "DockerBuildSession"); \
|
||||
export AWS_ACCESS_KEY_ID="$(echo $CREDS | jq -r '.Credentials.AccessKeyId')"; \
|
||||
export AWS_SECRET_ACCESS_KEY="$(echo $CREDS | jq -r '.Credentials.SecretAccessKey')"; \
|
||||
export AWS_SESSION_TOKEN="$(echo $CREDS | jq -r '.Credentials.SessionToken')"; \
|
||||
dotnet test "tests/${DSPROJECT}.Biz.Test.Integration/${DSPROJECT}.Biz.Test.Integration.csproj" \
|
||||
--configuration Release \
|
||||
--no-restore \
|
||||
--no-build \
|
||||
--verbosity=normal \
|
||||
--logger="trx;LogFileName=/test-results/integration-tests-dsbiz.trx" \
|
||||
-p:CollectCoverage=true \
|
||||
-p:CoverletOutputFormat=opencover \
|
||||
-p:CoverletOutput='/test-results/' \
|
||||
-p:MergeWith='/test-results/coverage.json' \
|
||||
-p:Exclude=\"[Strata.ContinuousImprovement.Biz.Test.Unit]*,[Strata.ContinuousImprovement.JazzEntityFrameworkStub]*\"
|
||||
|
||||
RUN mv /test-results/coverage.opencover.xml /test-results/cover.xml
|
||||
|
||||
# Publish project
|
||||
RUN dotnet publish "src/${PROJECT}.Api/${PROJECT}.Api.csproj" \
|
||||
--configuration Release \
|
||||
--no-restore \
|
||||
--no-build \
|
||||
--output /app
|
||||
|
||||
# Pack client project
|
||||
RUN dotnet pack "src/${PROJECT}.Client/${PROJECT}.Client.csproj" \
|
||||
--configuration Release \
|
||||
--no-restore \
|
||||
--no-build \
|
||||
--include-symbols \
|
||||
--include-source \
|
||||
--output /pack \
|
||||
-property:PackageVersion=${VERSION}
|
||||
|
||||
# Pack DS Biz project
|
||||
RUN dotnet pack "src/${DSPROJECT}.Biz/${DSPROJECT}.Biz.csproj" \
|
||||
--configuration Release \
|
||||
--no-restore \
|
||||
--no-build \
|
||||
--include-symbols \
|
||||
--include-source \
|
||||
--output /pack \
|
||||
-property:PackageVersion=${VERSION}
|
||||
|
||||
# Pack DS models project
|
||||
RUN dotnet pack "src/${DSPROJECT}.Models/${DSPROJECT}.Models.csproj" \
|
||||
--configuration Release \
|
||||
--no-restore \
|
||||
--no-build \
|
||||
--include-symbols \
|
||||
--include-source \
|
||||
--output /pack \
|
||||
-property:PackageVersion=${VERSION}
|
||||
|
||||
RUN if [ "${SONARLOGIN}" != "" ] ; then \
|
||||
/dc/dependency-check/bin/dependency-check.sh -f JSON -f HTML -s . -o ./buildlogs \
|
||||
--suppression ./sonarsuppressions.xml \
|
||||
--noupdate --nodeAuditSkipDevDependencies --disableNodeJS --disableRetireJS --ossIndexRemoteErrorWarnOnly true \
|
||||
--dbDriverName "com.microsoft.sqlserver.jdbc.SQLServerDriver" \
|
||||
--connectionString "jdbc:sqlserver://ddensqldevops01.sdt.local;Database=DependencyCheck;encrypt=true;trustServerCertificate=true;" \
|
||||
--dbUser "dcuser" --dbPassword "${SONARLOGIN}" && \
|
||||
dotnet sonarscanner end /d:sonar.login="${SONARLOGIN}"; \
|
||||
fi
|
||||
|
||||
RUN mkdir -p /src/buildlogs && touch /src/buildlogs/dependency-check-report.html
|
||||
|
||||
###############
|
||||
# Final image #
|
||||
###############
|
||||
FROM base AS final
|
||||
ARG PROJECT
|
||||
WORKDIR /app
|
||||
ARG PROJECT
|
||||
ENV PROJECTDLL=${PROJECT}.Api.dll
|
||||
ENV DOTNET_ENVIRONMENT=Development
|
||||
ENV ASPNETCORE_ENVIRONMENT=Development
|
||||
|
||||
COPY --from=build /app /app
|
||||
COPY --from=build /pack /pack
|
||||
COPY --from=build /test-results /test-results
|
||||
COPY --from=build /src/buildlogs/dependency-check-report.html /dc/dependency-check-report.html
|
||||
|
||||
ENTRYPOINT dotnet ${PROJECTDLL}
|
||||
@@ -0,0 +1,73 @@
|
||||
ARG PROJECT=Strata.ContinuousImprovement
|
||||
ARG VERSION=0.0.0
|
||||
|
||||
|
||||
##############
|
||||
# Base image #
|
||||
##############
|
||||
FROM ecr.ops.stratanetwork.net/strata.microsoft.dotnet.aspnet:8.0 AS base
|
||||
# Add TMZ files for CST
|
||||
RUN cp /usr/share/zoneinfo/America/Chicago "/usr/share/zoneinfo/Central Standard Time"
|
||||
|
||||
RUN sed -i 's/^Components: main$/& contrib/' /etc/apt/sources.list.d/debian.sources \
|
||||
&& apt-get update --allow-releaseinfo-change \
|
||||
&& apt-get install -y \
|
||||
libc6 \
|
||||
libc6-dev \
|
||||
libgdiplus \
|
||||
libx11-dev \
|
||||
ttf-mscorefonts-installer \
|
||||
fontconfig \
|
||||
&& rm -rf /var/lib/apt/lists/*\
|
||||
&& ln -s /usr/lib/x86_64-linux-gnu/libdl.so.2 /usr/lib/x86_64-linux-gnu/libdl.so
|
||||
|
||||
###############
|
||||
# Build image #
|
||||
###############
|
||||
FROM ecr.ops.stratanetwork.net/strata.microsoft.dotnet.sdk:8.0 as build
|
||||
ARG PROJECT
|
||||
ARG VERSION
|
||||
|
||||
# Add TMZ files for CST
|
||||
RUN cp /usr/share/zoneinfo/America/Chicago "/usr/share/zoneinfo/Central Standard Time"
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
# Copy project files and restore
|
||||
COPY ["${PROJECT}.sln", "${PROJECT}.sln"]
|
||||
COPY src/**/*.csproj ./
|
||||
RUN for file in $(ls *.csproj); do mkdir -p src/${file%.*}/ && mv $file src/${file%.*}/; done
|
||||
COPY tests/**/*.csproj ./
|
||||
RUN for file in $(ls *.csproj); do mkdir -p tests/${file%.*}/ && mv $file tests/${file%.*}/; done
|
||||
|
||||
RUN dotnet restore "${PROJECT}.sln" \
|
||||
--source https://api.nuget.org/v3/index.json \
|
||||
--source https://proget.sdt.local/nuget/nuget/v3/index.json
|
||||
|
||||
# Copy files and build
|
||||
COPY . .
|
||||
|
||||
RUN dotnet build "${PROJECT}.sln" \
|
||||
--configuration Release \
|
||||
--no-restore
|
||||
|
||||
# Publish project
|
||||
RUN dotnet publish "src/${PROJECT}.Dashboard/${PROJECT}.Dashboard.csproj" \
|
||||
--configuration Release \
|
||||
--no-restore \
|
||||
--no-build \
|
||||
--output /app
|
||||
|
||||
###############
|
||||
# Final image #
|
||||
###############
|
||||
FROM base AS final
|
||||
ARG PROJECT
|
||||
WORKDIR /app
|
||||
ARG PROJECT
|
||||
ENV PROJECTDLL=${PROJECT}.Dashboard.dll
|
||||
ENV DOTNET_ENVIRONMENT=Development
|
||||
ENV ASPNETCORE_ENVIRONMENT=Development
|
||||
|
||||
COPY --from=build /app /app
|
||||
ENTRYPOINT dotnet ${PROJECTDLL}
|
||||
@@ -0,0 +1,73 @@
|
||||
ARG PROJECT=Strata.ContinuousImprovement
|
||||
ARG VERSION=0.0.0
|
||||
|
||||
|
||||
##############
|
||||
# Base image #
|
||||
##############
|
||||
FROM ecr.ops.stratanetwork.net/strata.microsoft.dotnet.aspnet:8.0 AS base
|
||||
# Add TMZ files for CST
|
||||
RUN cp /usr/share/zoneinfo/America/Chicago "/usr/share/zoneinfo/Central Standard Time"
|
||||
|
||||
RUN sed -i 's/^Components: main$/& contrib/' /etc/apt/sources.list.d/debian.sources \
|
||||
&& apt-get update --allow-releaseinfo-change \
|
||||
&& apt-get install -y \
|
||||
libc6 \
|
||||
libc6-dev \
|
||||
libgdiplus \
|
||||
libx11-dev \
|
||||
ttf-mscorefonts-installer \
|
||||
fontconfig \
|
||||
&& rm -rf /var/lib/apt/lists/*\
|
||||
&& ln -s /usr/lib/x86_64-linux-gnu/libdl.so.2 /usr/lib/x86_64-linux-gnu/libdl.so
|
||||
|
||||
###############
|
||||
# Build image #
|
||||
###############
|
||||
FROM ecr.ops.stratanetwork.net/strata.microsoft.dotnet.sdk:8.0 as build
|
||||
ARG PROJECT
|
||||
ARG VERSION
|
||||
|
||||
# Add TMZ files for CST
|
||||
RUN cp /usr/share/zoneinfo/America/Chicago "/usr/share/zoneinfo/Central Standard Time"
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
# Copy project files and restore
|
||||
COPY ["${PROJECT}.sln", "${PROJECT}.sln"]
|
||||
COPY src/**/*.csproj ./
|
||||
RUN for file in $(ls *.csproj); do mkdir -p src/${file%.*}/ && mv $file src/${file%.*}/; done
|
||||
COPY tests/**/*.csproj ./
|
||||
RUN for file in $(ls *.csproj); do mkdir -p tests/${file%.*}/ && mv $file tests/${file%.*}/; done
|
||||
|
||||
RUN dotnet restore "${PROJECT}.sln" \
|
||||
--source https://api.nuget.org/v3/index.json \
|
||||
--source https://proget.sdt.local/nuget/nuget/v3/index.json
|
||||
|
||||
# Copy files and build
|
||||
COPY . .
|
||||
|
||||
RUN dotnet build "${PROJECT}.sln" \
|
||||
--configuration Release \
|
||||
--no-restore
|
||||
|
||||
# Publish project
|
||||
RUN dotnet publish "src/${PROJECT}.Service/${PROJECT}.Service.csproj" \
|
||||
--configuration Release \
|
||||
--no-restore \
|
||||
--no-build \
|
||||
--output /app
|
||||
|
||||
###############
|
||||
# Final image #
|
||||
###############
|
||||
FROM base AS final
|
||||
ARG PROJECT
|
||||
WORKDIR /app
|
||||
ARG PROJECT
|
||||
ENV PROJECTDLL=${PROJECT}.Service.dll
|
||||
ENV DOTNET_ENVIRONMENT=Development
|
||||
ENV ASPNETCORE_ENVIRONMENT=Development
|
||||
|
||||
COPY --from=build /app /app
|
||||
ENTRYPOINT dotnet ${PROJECTDLL}
|
||||
@@ -0,0 +1,199 @@
|
||||
ARG PROJECT=Strata.ContinuousImprovement
|
||||
ARG DSPROJECT=Strata.DecisionSupport
|
||||
ARG VERSION=0.0.0
|
||||
ARG SONARURL=''
|
||||
ARG SONARLOGIN=''
|
||||
ARG SONARBRANCH=''
|
||||
|
||||
##############
|
||||
# Base image #
|
||||
##############
|
||||
FROM ecr.ops.stratanetwork.net/strata.microsoft.dotnet.aspnet:8.0 AS base
|
||||
|
||||
# Add TMZ files for CST
|
||||
RUN cp /usr/share/zoneinfo/America/Chicago "/usr/share/zoneinfo/Central Standard Time"
|
||||
|
||||
RUN sed -i 's/^Components: main$/& contrib/' /etc/apt/sources.list.d/debian.sources \
|
||||
&& apt-get -qq update --allow-releaseinfo-change \
|
||||
&& apt-get -qq install -y \
|
||||
libc6 \
|
||||
libc6-dev \
|
||||
libgdiplus \
|
||||
libx11-dev \
|
||||
ttf-mscorefonts-installer \
|
||||
fontconfig \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& ln -s /usr/lib/x86_64-linux-gnu/libdl.so.2 /usr/lib/x86_64-linux-gnu/libdl.so
|
||||
|
||||
###############
|
||||
# Build image #
|
||||
###############
|
||||
FROM ecr.ops.stratanetwork.net/strata.microsoft.dotnet.sdk:8.0 AS build
|
||||
ARG PROJECT
|
||||
ARG DSPROJECT
|
||||
ARG VERSION
|
||||
ARG SONARURL
|
||||
ARG SONARLOGIN
|
||||
ARG SONARBRANCH
|
||||
|
||||
# Add TMZ files for CST
|
||||
RUN cp /usr/share/zoneinfo/America/Chicago "/usr/share/zoneinfo/Central Standard Time"
|
||||
|
||||
RUN sed -i 's/^Components: main$/& contrib/' /etc/apt/sources.list.d/debian.sources \
|
||||
&& apt-get -qq update --allow-releaseinfo-change \
|
||||
&& apt-get -qq install -y \
|
||||
libc6 \
|
||||
libc6-dev \
|
||||
libgdiplus \
|
||||
libx11-dev \
|
||||
ttf-mscorefonts-installer \
|
||||
fontconfig \
|
||||
&& rm -rf /var/lib/apt/lists/*\
|
||||
&& ln -s /usr/lib/x86_64-linux-gnu/libdl.so.2 /usr/lib/x86_64-linux-gnu/libdl.so
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
# Copy project files and restore
|
||||
COPY ["${PROJECT}.sln", "${PROJECT}.sln"]
|
||||
COPY src/**/*.csproj ./
|
||||
RUN for file in $(ls *.csproj); do mkdir -p src/${file%.*}/ && mv $file src/${file%.*}/; done
|
||||
COPY tests/**/*.csproj ./
|
||||
RUN for file in $(ls *.csproj); do mkdir -p tests/${file%.*}/ && mv $file tests/${file%.*}/; done
|
||||
|
||||
RUN dotnet restore "${PROJECT}.sln" \
|
||||
--source https://api.nuget.org/v3/index.json \
|
||||
--source https://proget.sdt.local/nuget/nuget/v3/index.json
|
||||
|
||||
|
||||
# Copy files and build
|
||||
COPY . .
|
||||
|
||||
RUN if [ "${SONARLOGIN}" != "" ] ; then dotnet sonarscanner begin \
|
||||
/k:"${PROJECT}" \
|
||||
/d:sonar.scm.provider=git \
|
||||
/d:sonar.host.url="${SONARURL}" \
|
||||
${SONARBRANCH} \
|
||||
/d:sonar.login="${SONARLOGIN}" \
|
||||
/d:sonar.cs.opencover.reportsPaths="/test-results/cover.xml" \
|
||||
/d:sonar.cs.vstest.reportsPaths="/test-results/*.trx" \
|
||||
/d:sonar.dependencyCheck.jsonReportPath=/src/buildlogs/dependency-check-report.json \
|
||||
/d:sonar.dependencyCheck.htmlReportPath=/src/buildlogs/dependency-check-report.html \
|
||||
/v:sonar.projectVersion="${VERSION}"; fi
|
||||
|
||||
|
||||
RUN dotnet build "${PROJECT}.sln" \
|
||||
--configuration Release \
|
||||
--no-restore
|
||||
|
||||
# Run tests
|
||||
RUN dotnet test "tests/${PROJECT}.Api.Test.Unit/${PROJECT}.Api.Test.Unit.csproj" \
|
||||
--configuration Release \
|
||||
--no-restore \
|
||||
--no-build \
|
||||
--verbosity=normal \
|
||||
--logger="trx;LogFileName=/test-results/api-api-tests.trx" \
|
||||
-p:CollectCoverage=true \
|
||||
-p:CoverletOutput='/test-results/' \
|
||||
-p:Exclude=\"[Strata.ContinuousImprovement.Biz.Test.Unit]*,[Strata.ContinuousImprovement.JazzEntityFrameworkStub]*\"
|
||||
|
||||
RUN dotnet test "/src/tests/${PROJECT}.Biz.Test.Unit/${PROJECT}.Biz.Test.Unit.csproj" \
|
||||
--configuration Release \
|
||||
--no-restore \
|
||||
--no-build \
|
||||
--verbosity=normal \
|
||||
--logger="trx;LogFileName=/test-results/unit-tests-biz.trx" \
|
||||
-p:CollectCoverage=true \
|
||||
-p:CoverletOutput='/test-results/' \
|
||||
-p:MergeWith='/test-results/coverage.json' \
|
||||
-p:Exclude=\"[Strata.ContinuousImprovement.Biz.Test.Unit]*,[Strata.ContinuousImprovement.JazzEntityFrameworkStub]*\"
|
||||
|
||||
RUN dotnet test "tests/${PROJECT}.Client.Test.Unit/${PROJECT}.Client.Test.Unit.csproj" \
|
||||
--configuration Release \
|
||||
--no-restore \
|
||||
--no-build \
|
||||
--verbosity=normal \
|
||||
--logger="trx;LogFileName=/test-results/unit-tests-client.trx" \
|
||||
-p:CollectCoverage=true \
|
||||
-p:CoverletOutput='/test-results/' \
|
||||
-p:MergeWith='/test-results/coverage.json' \
|
||||
-p:Exclude=\"[Strata.ContinuousImprovement.Biz.Test.Unit]*,[Strata.ContinuousImprovement.JazzEntityFrameworkStub]*\"
|
||||
|
||||
RUN dotnet test "tests/${DSPROJECT}.Biz.Test.Unit/${DSPROJECT}.Biz.Test.Unit.csproj" \
|
||||
--configuration Release \
|
||||
--no-restore \
|
||||
--no-build \
|
||||
--verbosity=normal \
|
||||
--logger="trx;LogFileName=/test-results/unit-tests-dsbiz.trx" \
|
||||
-p:CollectCoverage=true \
|
||||
-p:CoverletOutputFormat=opencover \
|
||||
-p:CoverletOutput='/test-results/' \
|
||||
-p:MergeWith='/test-results/coverage.json' \
|
||||
-p:Exclude=\"[Strata.ContinuousImprovement.Biz.Test.Unit]*,[Strata.ContinuousImprovement.JazzEntityFrameworkStub]*\"
|
||||
|
||||
RUN mv /test-results/coverage.opencover.xml /test-results/cover.xml
|
||||
|
||||
# Publish project
|
||||
RUN dotnet publish "src/${PROJECT}.Api/${PROJECT}.Api.csproj" \
|
||||
--configuration Release \
|
||||
--no-restore \
|
||||
--no-build \
|
||||
--output /app
|
||||
|
||||
# Pack client project
|
||||
RUN dotnet pack "src/${PROJECT}.Client/${PROJECT}.Client.csproj" \
|
||||
--configuration Release \
|
||||
--no-restore \
|
||||
--no-build \
|
||||
--include-symbols \
|
||||
--include-source \
|
||||
--output /pack \
|
||||
-property:PackageVersion=${VERSION}
|
||||
|
||||
# Pack DS Biz project
|
||||
RUN dotnet pack "src/${DSPROJECT}.Biz/${DSPROJECT}.Biz.csproj" \
|
||||
--configuration Release \
|
||||
--no-restore \
|
||||
--no-build \
|
||||
--include-symbols \
|
||||
--include-source \
|
||||
--output /pack \
|
||||
-property:PackageVersion=${VERSION}
|
||||
|
||||
# Pack DS models project
|
||||
RUN dotnet pack "src/${DSPROJECT}.Models/${DSPROJECT}.Models.csproj" \
|
||||
--configuration Release \
|
||||
--no-restore \
|
||||
--no-build \
|
||||
--include-symbols \
|
||||
--include-source \
|
||||
--output /pack \
|
||||
-property:PackageVersion=${VERSION}
|
||||
|
||||
# /* Running locally
|
||||
# RUN if [ "${SONARLOGIN}" != "" ] ; then \
|
||||
# /dc/dependency-check/bin/dependency-check.sh -f JSON -f HTML -s . -o ./buildlogs \
|
||||
# --suppression ./sonarsuppressions.xml \
|
||||
# --noupdate --nodeAuditSkipDevDependencies --disableNodeJS --disableRetireJS --ossIndexRemoteErrorWarnOnly true \
|
||||
# --dbDriverName "com.microsoft.sqlserver.jdbc.SQLServerDriver" \
|
||||
# --connectionString "jdbc:sqlserver://ddensqldevops01.sdt.local;Database=DependencyCheck;encrypt=true;trustServerCertificate=true;" \
|
||||
# --dbUser "dcuser" --dbPassword "${SONARLOGIN}" && \
|
||||
# dotnet sonarscanner end /d:sonar.login="${SONARLOGIN}"; \
|
||||
# fi
|
||||
# */
|
||||
|
||||
###############
|
||||
# Final image #
|
||||
###############
|
||||
FROM base AS final
|
||||
ARG PROJECT
|
||||
WORKDIR /app
|
||||
ARG PROJECT
|
||||
ENV PROJECTDLL=${PROJECT}.Api.dll
|
||||
ENV DOTNET_ENVIRONMENT=Development
|
||||
ENV ASPNETCORE_ENVIRONMENT=Development
|
||||
|
||||
COPY --from=build /app /app
|
||||
COPY --from=build /pack /pack
|
||||
COPY --from=build /test-results /test-results
|
||||
|
||||
ENTRYPOINT dotnet ${PROJECTDLL}
|
||||
@@ -0,0 +1,28 @@
|
||||
ARG SONARLOGIN=''
|
||||
ARG VERSION=0.0.0
|
||||
|
||||
FROM node:18.17.0 as build
|
||||
ARG SONARLOGIN
|
||||
ARG VERSION
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
RUN npm config set @strata:registry http://proget.sdt.local/npm/npm
|
||||
RUN npm config set strict-ssl false
|
||||
COPY src/Strata.ContinuousImprovement.Web/continuousimprovement/package*.json ./
|
||||
RUN npm install --legacy-peer-deps
|
||||
RUN npm ci --legacy-peer-deps
|
||||
|
||||
# Copy in rest of app and build
|
||||
COPY src/Strata.ContinuousImprovement.Web/continuousimprovement/ .
|
||||
RUN npm run build
|
||||
|
||||
# Run tests
|
||||
# RUN npm run test-ci
|
||||
|
||||
# Build release container
|
||||
FROM nginx:alpine
|
||||
COPY --from=build /app/build /usr/share/nginx/html
|
||||
COPY src/Strata.ContinuousImprovement.Web/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
@@ -1,2 +1,55 @@
|
||||
# continuousimprovement
|
||||
# Strata.ContinuousImprovement
|
||||
|
||||
[](https://github.com/stratadecision/continuousimprovement/actions/workflows/build.yaml)
|
||||
[](https://github.com/stratadecision/continuousimprovement/actions/workflows/e2e-testrunner.yml)
|
||||
|
||||
## General Information
|
||||
This microserivce and react UI provide access for the Continuous Improvements Opportunities
|
||||
* whenever a change is made locally, i think the continuousimprovement needs to be unistalled and reinstalled
|
||||
|
||||
## Credits
|
||||
This microservice is developed and maintained by The-Outliers
|
||||
|
||||
## install continuousimprovement
|
||||
dotnet new -i C:\git\strata.continuousimprovements.microservices\continuousimprovements\strata.microservice
|
||||
## unistall continuousimprovement
|
||||
dotnet new -u C:\git\strata.continuousimprovements.microservices\continuousimprovements\strata.microservice
|
||||
|
||||
# create new api project with postgres project from continuousimprovement
|
||||
dotnet new strata.microservice -o C:\git\strata.foobar -n Strata.Foobar --api --biz --postgres
|
||||
|
||||
## Branching and Versioning
|
||||
|
||||
| branch | version format | example |
|
||||
| ---------- | --------------------- | --------------- |
|
||||
| master | #.#.# | 1.2.3 |
|
||||
| feature/\* | #.#+1.0-featureName.# | 1.3.0-newfeat.1 |
|
||||
| fix/\* | #.#.#+1-fixName.# | 1.2.4-bug.1 |
|
||||
|
||||
See our [confluence page](https://confluence.sdt.local/display/DOP/Branching+and+Versioning) for more information
|
||||
|
||||
## Usage
|
||||
|
||||
### Build and Run
|
||||
|
||||
```
|
||||
docker-compose up -d --build
|
||||
```
|
||||
|
||||
- https://localhost:8443/index (swagger api)
|
||||
- https://localhost:8443/hangfire (hangfire dashboard)
|
||||
- http://localhost:8081 (redis commander)
|
||||
|
||||
### Extracting nuget package
|
||||
|
||||
```
|
||||
docker create --name throwaway stratacontinuousimprovement_continuousimprovement-api
|
||||
docker cp throwaway:/pack .
|
||||
docker rm throwaway
|
||||
```
|
||||
|
||||
### Cleanup
|
||||
|
||||
```
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.4.33403.182
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.ContinuousImprovement.Api", "src\Strata.ContinuousImprovement.Api\Strata.ContinuousImprovement.Api.csproj", "{C4CD8DC4-3B88-4BA6-9F78-C641D365C2EE}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.ContinuousImprovement.Biz.Test.Unit", "tests\Strata.ContinuousImprovement.Biz.Test.Unit\Strata.ContinuousImprovement.Biz.Test.Unit.csproj", "{775E79BD-A8F5-4756-8E94-390A72018BF8}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.ContinuousImprovement.Service", "src\Strata.ContinuousImprovement.Service\Strata.ContinuousImprovement.Service.csproj", "{EB523C92-2FED-4631-A73B-17F37B742E7C}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.ContinuousImprovement.Biz", "src\Strata.ContinuousImprovement.Biz\Strata.ContinuousImprovement.Biz.csproj", "{533D0BEF-FBF2-47E2-98E7-11AE930E2E65}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.ContinuousImprovement.Web", "src\Strata.ContinuousImprovement.Web\Strata.ContinuousImprovement.Web.csproj", "{ABCB26F8-6044-41A8-BBBE-B00E95BB585D}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.ContinuousImprovement.Dashboard", "src\Strata.ContinuousImprovement.Dashboard\Strata.ContinuousImprovement.Dashboard.csproj", "{51D04755-17C4-49CF-944B-D4347E7C550F}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.ContinuousImprovement.Client", "src\Strata.ContinuousImprovement.Client\Strata.ContinuousImprovement.Client.csproj", "{EDBE4F6B-46D0-4332-9A38-7F2C011AA490}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.ContinuousImprovement.Api.Test.Unit", "tests\Strata.ContinuousImprovement.Api.Test.Unit\Strata.ContinuousImprovement.Api.Test.Unit.csproj", "{DA1AE588-5B04-43CA-BA0C-9DF8F737C97A}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.ContinuousImprovement.JazzEntityFrameworkStub", "tests\Strata.ContinuousImprovement.JazzEntityFrameworkStub\Strata.ContinuousImprovement.JazzEntityFrameworkStub.csproj", "{D04952E8-BDF1-44C6-819C-A066BADE165B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.DecisionSupport.Biz", "src\Strata.DecisionSupport.Biz\Strata.DecisionSupport.Biz.csproj", "{12F6978F-C62B-4268-8B7B-72887B53C223}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.DecisionSupport.Biz.Test.Unit", "tests\Strata.DecisionSupport.Biz.Test.Unit\Strata.DecisionSupport.Biz.Test.Unit.csproj", "{EAB9B29E-CAA6-4974-A8D5-D0AAF3FF2B0C}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.DecisionSupport.Models", "src\Strata.DecisionSupport.Models\Strata.DecisionSupport.Models.csproj", "{7743C7CC-91E6-4347-95EC-F18B2F173291}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.ContinuousImprovement.Client.Test.Unit", "tests\Strata.ContinuousImprovement.Client.Test.Unit\Strata.ContinuousImprovement.Client.Test.Unit.csproj", "{F4FCAF36-CDD7-4341-8546-5BF6DD8501CE}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Strata.DecisionSupport.Biz.Test.Integration", "tests\Strata.DecisionSupport.Biz.Test.Integration\Strata.DecisionSupport.Biz.Test.Integration.csproj", "{0DE4A5ED-BA94-40AD-9553-3C4312DCC86C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Strata.ContinuousImprovement.Biz.Test.Integration", "tests\Strata.ContinuousImprovement.Biz.Test.Integration\Strata.ContinuousImprovement.Biz.Test.Integration.csproj", "{EFC6B15D-C22B-480B-9E50-9BBAA33A9030}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Strata.ContinuousImprovement.Client.Test.Integration", "tests\Strata.ContinuousImprovement.Client.Test.Integration\Strata.ContinuousImprovement.Client.Test.Integration.csproj", "{4515A4CD-906B-4204-86A4-5DE5BCE43E1D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Strata.ContinuousImprovement.Api.Test.Integration", "tests\Strata.ContinuousImprovement.Api.Test.Integration\Strata.ContinuousImprovement.Api.Test.Integration.csproj", "{A730D5B3-50A1-5419-8945-E1C70294B5B0}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{C4CD8DC4-3B88-4BA6-9F78-C641D365C2EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C4CD8DC4-3B88-4BA6-9F78-C641D365C2EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C4CD8DC4-3B88-4BA6-9F78-C641D365C2EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C4CD8DC4-3B88-4BA6-9F78-C641D365C2EE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{775E79BD-A8F5-4756-8E94-390A72018BF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{775E79BD-A8F5-4756-8E94-390A72018BF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{775E79BD-A8F5-4756-8E94-390A72018BF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{775E79BD-A8F5-4756-8E94-390A72018BF8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EB523C92-2FED-4631-A73B-17F37B742E7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EB523C92-2FED-4631-A73B-17F37B742E7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EB523C92-2FED-4631-A73B-17F37B742E7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EB523C92-2FED-4631-A73B-17F37B742E7C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{533D0BEF-FBF2-47E2-98E7-11AE930E2E65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{533D0BEF-FBF2-47E2-98E7-11AE930E2E65}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{533D0BEF-FBF2-47E2-98E7-11AE930E2E65}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{533D0BEF-FBF2-47E2-98E7-11AE930E2E65}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{ABCB26F8-6044-41A8-BBBE-B00E95BB585D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{ABCB26F8-6044-41A8-BBBE-B00E95BB585D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{ABCB26F8-6044-41A8-BBBE-B00E95BB585D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{ABCB26F8-6044-41A8-BBBE-B00E95BB585D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{51D04755-17C4-49CF-944B-D4347E7C550F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{51D04755-17C4-49CF-944B-D4347E7C550F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{51D04755-17C4-49CF-944B-D4347E7C550F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{51D04755-17C4-49CF-944B-D4347E7C550F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EDBE4F6B-46D0-4332-9A38-7F2C011AA490}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EDBE4F6B-46D0-4332-9A38-7F2C011AA490}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EDBE4F6B-46D0-4332-9A38-7F2C011AA490}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EDBE4F6B-46D0-4332-9A38-7F2C011AA490}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DA1AE588-5B04-43CA-BA0C-9DF8F737C97A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DA1AE588-5B04-43CA-BA0C-9DF8F737C97A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DA1AE588-5B04-43CA-BA0C-9DF8F737C97A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DA1AE588-5B04-43CA-BA0C-9DF8F737C97A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D04952E8-BDF1-44C6-819C-A066BADE165B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D04952E8-BDF1-44C6-819C-A066BADE165B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D04952E8-BDF1-44C6-819C-A066BADE165B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D04952E8-BDF1-44C6-819C-A066BADE165B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{12F6978F-C62B-4268-8B7B-72887B53C223}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{12F6978F-C62B-4268-8B7B-72887B53C223}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{12F6978F-C62B-4268-8B7B-72887B53C223}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{12F6978F-C62B-4268-8B7B-72887B53C223}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EAB9B29E-CAA6-4974-A8D5-D0AAF3FF2B0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EAB9B29E-CAA6-4974-A8D5-D0AAF3FF2B0C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EAB9B29E-CAA6-4974-A8D5-D0AAF3FF2B0C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EAB9B29E-CAA6-4974-A8D5-D0AAF3FF2B0C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7743C7CC-91E6-4347-95EC-F18B2F173291}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7743C7CC-91E6-4347-95EC-F18B2F173291}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7743C7CC-91E6-4347-95EC-F18B2F173291}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7743C7CC-91E6-4347-95EC-F18B2F173291}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F4FCAF36-CDD7-4341-8546-5BF6DD8501CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F4FCAF36-CDD7-4341-8546-5BF6DD8501CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F4FCAF36-CDD7-4341-8546-5BF6DD8501CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F4FCAF36-CDD7-4341-8546-5BF6DD8501CE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0DE4A5ED-BA94-40AD-9553-3C4312DCC86C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0DE4A5ED-BA94-40AD-9553-3C4312DCC86C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0DE4A5ED-BA94-40AD-9553-3C4312DCC86C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0DE4A5ED-BA94-40AD-9553-3C4312DCC86C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EFC6B15D-C22B-480B-9E50-9BBAA33A9030}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EFC6B15D-C22B-480B-9E50-9BBAA33A9030}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EFC6B15D-C22B-480B-9E50-9BBAA33A9030}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EFC6B15D-C22B-480B-9E50-9BBAA33A9030}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4515A4CD-906B-4204-86A4-5DE5BCE43E1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4515A4CD-906B-4204-86A4-5DE5BCE43E1D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4515A4CD-906B-4204-86A4-5DE5BCE43E1D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4515A4CD-906B-4204-86A4-5DE5BCE43E1D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A730D5B3-50A1-5419-8945-E1C70294B5B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A730D5B3-50A1-5419-8945-E1C70294B5B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A730D5B3-50A1-5419-8945-E1C70294B5B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A730D5B3-50A1-5419-8945-E1C70294B5B0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {F6CD834D-51B1-4B10-9DE1-DFE5A16D4177}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,2 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/FilterSettingsManager/CoverageFilterXml/@EntryValue"><data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="Strata.ContinuousImprovement.Biz" ModuleVersionMask="*" ClassMask="Microsoft.Extensions.DependencyInjection.*" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data></s:String></wpf:ResourceDictionary>
|
||||
@@ -0,0 +1,347 @@
|
||||
# Feature Flag Usage Documentation - IFeatureFlagServiceClient.IsEnabledAsync
|
||||
|
||||
This document provides a comprehensive overview of all feature flags used throughout the Continuous Improvement codebase via the `IFeatureFlagServiceClient.IsEnabledAsync` method.
|
||||
|
||||
## Overview
|
||||
|
||||
The application uses feature flags to control functionality on a per-client basis in this multi-tenant system. Feature flags are checked using the `IFeatureFlagServiceClient` service and are typically scoped to a specific database GUID.
|
||||
|
||||
### Feature Flag Index
|
||||
|
||||
| # | Feature Flag | Description | Section |
|
||||
|---|--------------|-------------|---------|
|
||||
| 1 | `ciflexibleexplorationenabled` | Controls access to flexible exploration features for non-SDT employees | [Section 1](#1-ciflexibleexplorationenabled) |
|
||||
| 2 | `enableencounteropportunityintempo` | Controls whether encounter opportunities are enabled in the Tempo interface | [Section 2](#2-enableencounteropportunityintempo) |
|
||||
| 3 | `ciflexibleexplorationpage3enabled` | Controls access to the flexible exploration information page (third page) | [Section 3](#3-ciflexibleexplorationpage3enabled) |
|
||||
| 4 | `enablepayrollopportunityintempo` | Controls whether payroll opportunities are enabled in the Tempo interface | [Section 4](#4-enablepayrollopportunityintempo) |
|
||||
| 5 | `chargecodebeta` | Controls access to charge code beta features | [Section 5](#5-chargecodebeta) |
|
||||
| 6 | `cibenchmarkingenabled` | Controls benchmarking features in continuous improvement | [Section 6](#6-cibenchmarkingenabled) |
|
||||
| 7 | `ciopportunityworkbooks` | Controls opportunity workbook features | [Section 7](#7-ciopportunityworkbooks) |
|
||||
| 8 | `networkopportunitiesenabled` | Controls network opportunity features | [Section 8](#8-networkopportunitiesenabled) |
|
||||
| 9 | `strategicopportunityforchargecode` | Controls strategic opportunity features specifically for charge codes | [Section 9](#9-strategicopportunityforchargecode) |
|
||||
| 10 | `ciexplorationpopulationmanagementlaunchesstrategicopportunitywizard` | Controls if the Add Opportunity button on the Exploration Population page launches the Strategic Opportunity Wizard |
|
||||
## Feature Flag Keys and Usage
|
||||
|
||||
### 1. "ciflexibleexplorationenabled"
|
||||
**Purpose**: Controls access to flexible exploration features for non-SDT employees
|
||||
|
||||
**Usage Locations**:
|
||||
- `AllOpportunitiesController.cs` (line 40)
|
||||
- `ExplorationOpportunitiesController.cs` (line 45)
|
||||
|
||||
**Implementation Pattern**:
|
||||
```csharp
|
||||
// C# example
|
||||
public async Task<IActionResult> SomeAction()
|
||||
{
|
||||
if (await _featureFlagServiceClient.IsEnabledAsync("ciflexibleexplorationenabled"))
|
||||
{
|
||||
// Code for flexible exploration features
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback or default behavior
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Context**: Enables flexible exploration functionality. SDT employees always have access, while client users need this feature flag enabled.
|
||||
|
||||
---
|
||||
|
||||
### 2. "enableencounteropportunityintempo"
|
||||
**Purpose**: Controls whether encounter opportunities are enabled in the Tempo interface
|
||||
|
||||
**Usage Locations**:
|
||||
- `EncounterOpportunitiesController.cs` (lines 90, 175)
|
||||
|
||||
**Implementation Pattern**:
|
||||
```csharp
|
||||
// C# example
|
||||
public async Task<IActionResult> TempoAction()
|
||||
{
|
||||
if (await _featureFlagServiceClient.IsEnabledAsync("enableencounteropportunityintempo"))
|
||||
{
|
||||
// Code for encounter opportunities in Tempo
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback to Jazz interface or show message
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Context**: Feature flag to enable encounter opportunity functionality in the modern Tempo interface versus legacy Jazz interface.
|
||||
|
||||
---
|
||||
|
||||
### 3. "ciflexibleexplorationpage3enabled"
|
||||
**Purpose**: Controls access to the flexible exploration information page (third page)
|
||||
|
||||
**Usage Locations**:
|
||||
- `ExplorationController.cs` (line 45)
|
||||
|
||||
**Implementation Pattern**:
|
||||
```csharp
|
||||
// C# example
|
||||
public async Task<IActionResult> ExplorationPageAction()
|
||||
{
|
||||
if (await _featureFlagServiceClient.IsEnabledAsync("ciflexibleexplorationpage3enabled"))
|
||||
{
|
||||
// Code for the third page of flexible exploration
|
||||
}
|
||||
else
|
||||
{
|
||||
// Redirect or show less data
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Context**: Enables the third page of flexible exploration functionality for enhanced data exploration capabilities.
|
||||
|
||||
---
|
||||
|
||||
### 4. "enablepayrollopportunityintempo"
|
||||
**Purpose**: Controls whether payroll opportunities are enabled in the Tempo interface
|
||||
|
||||
**Usage Locations**:
|
||||
- `PayrollOpportunitiesController.cs` (line 85)
|
||||
|
||||
**Implementation Pattern**:
|
||||
```csharp
|
||||
// C# example
|
||||
public async Task<IActionResult> PayrollTempoAction()
|
||||
{
|
||||
if (await _featureFlagServiceClient.IsEnabledAsync("enablepayrollopportunityintempo"))
|
||||
{
|
||||
// Code for payroll opportunities in Tempo
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback to other methods or show message
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Context**: Feature flag to enable payroll opportunity functionality in the modern Tempo interface.
|
||||
|
||||
---
|
||||
|
||||
### 5. "chargecodebeta"
|
||||
**Purpose**: Controls access to charge code beta features
|
||||
|
||||
**Usage Locations**:
|
||||
- `ChargeCodeOpportunitiesController.cs` (line 243)
|
||||
|
||||
**Implementation Pattern**:
|
||||
```csharp
|
||||
// C# example
|
||||
public async Task<IActionResult> ChargeCodeBetaAction()
|
||||
{
|
||||
if (await _featureFlagServiceClient.IsEnabledAsync("chargecodebeta"))
|
||||
{
|
||||
// Code for charge code beta features
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hide or disable beta features
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Context**: Beta feature flag for charge code opportunities functionality, controlling access to new charge code features.
|
||||
|
||||
---
|
||||
|
||||
### 6. "cibenchmarkingenabled"
|
||||
**Purpose**: Controls benchmarking features in continuous improvement
|
||||
|
||||
**Usage Locations**:
|
||||
- Frontend TypeScript components
|
||||
- Configuration and peer group functionality
|
||||
|
||||
**Implementation Pattern**:
|
||||
```typescript
|
||||
// TypeScript example
|
||||
if (featureFlagService.isEnabled("cibenchmarkingenabled")) {
|
||||
// Code for benchmarking features
|
||||
} else {
|
||||
// Alternative code path
|
||||
}
|
||||
```
|
||||
|
||||
**Context**: Enables benchmarking functionality in the CI application, allowing comparison against peer groups.
|
||||
|
||||
---
|
||||
|
||||
### 7. "ciopportunityworkbooks"
|
||||
**Purpose**: Controls opportunity workbook features
|
||||
|
||||
**Usage Locations**:
|
||||
- Referenced in TypeScript interface definitions
|
||||
- Workbook management components
|
||||
|
||||
**Context**: Feature flag for workbook functionality related to opportunities.
|
||||
|
||||
---
|
||||
|
||||
### 8. "networkopportunitiesenabled"
|
||||
**Purpose**: Controls network opportunity features
|
||||
|
||||
**Usage Locations**:
|
||||
- Frontend navigation and components
|
||||
- Network opportunity controllers
|
||||
|
||||
**Implementation Pattern**:
|
||||
```typescript
|
||||
// TypeScript example
|
||||
if (featureFlagService.isEnabled("networkopportunitiesenabled")) {
|
||||
// Code for network opportunity features
|
||||
} else {
|
||||
// Code for standard opportunities
|
||||
}
|
||||
```
|
||||
|
||||
**Context**: Enables network opportunity functionality for multi-client opportunity management.
|
||||
|
||||
---
|
||||
|
||||
### 9. "strategicopportunityforchargecode"
|
||||
**Purpose**: Controls strategic opportunity features specifically for charge codes
|
||||
|
||||
**Usage Locations**:
|
||||
- Frontend configuration components
|
||||
- Strategic opportunity management
|
||||
|
||||
**Implementation Pattern**:
|
||||
```typescript
|
||||
// TypeScript example
|
||||
if (featureFlagService.isEnabled("strategicopportunityforchargecode")) {
|
||||
// Code for strategic opportunities for charge codes
|
||||
} else {
|
||||
// Fallback code
|
||||
}
|
||||
```
|
||||
|
||||
**Context**: Enables strategic opportunity functionality specifically for charge code-based opportunities.
|
||||
|
||||
---
|
||||
|
||||
### 10. "ciexplorationpopulationmanagementlaunchesstrategicopportunitywizard"
|
||||
**Purpose**: Controls strategic opportunity features specifically for charge codes
|
||||
|
||||
**Usage Locations**:
|
||||
- Frontend configuration components
|
||||
- Strategic opportunity management
|
||||
|
||||
**Implementation Pattern**:
|
||||
```typescript
|
||||
// TypeScript example
|
||||
if (featureFlagService.isEnabled("ciexplorationpopulationmanagementlaunchesstrategicopportunitywizard")) {
|
||||
// Code to launch strategic opportunities wizard
|
||||
} else {
|
||||
// Fallback code
|
||||
}
|
||||
```
|
||||
|
||||
**Context**: Enables the Add Opportunity button on the Exploration Population page to launch the Strategic Opportunity wizard.
|
||||
|
||||
|
||||
## Key Controllers Using Feature Flags
|
||||
|
||||
| Controller | Feature Flags Used | Purpose |
|
||||
|------------|-------------------|---------|
|
||||
| `AllOpportunitiesController` | `ciflexibleexplorationenabled` | Flexible exploration access control |
|
||||
| `ExplorationOpportunitiesController` | `ciflexibleexplorationenabled` | Exploration features |
|
||||
| `ExplorationController` | `ciflexibleexplorationpage3enabled` | Advanced exploration pages |
|
||||
| `EncounterOpportunitiesController` | `enableencounteropportunityintempo` | Tempo interface integration |
|
||||
| `PayrollOpportunitiesController` | `enablepayrollopportunityintempo` | Payroll Tempo integration |
|
||||
| `ChargeCodeOpportunitiesController` | `chargecodebeta` | Beta charge code features |
|
||||
| `SettingsController` | All CI area flags | Global feature management |
|
||||
|
||||
## Frontend Integration
|
||||
|
||||
The frontend TypeScript code integrates with feature flags through:
|
||||
|
||||
- **Navigation Control**: Menu items visibility based on feature flags
|
||||
- **Component Rendering**: Conditional rendering of UI components
|
||||
- **Service Availability**: API service availability checks
|
||||
- **User Interface**: Feature-specific UI elements and workflows
|
||||
|
||||
### Example Frontend Usage
|
||||
```typescript
|
||||
// TypeScript example
|
||||
async function loadOpportunities() {
|
||||
if (await featureFlagService.isEnabled("ciflexibleexplorationenabled")) {
|
||||
// Load flexible exploration opportunities
|
||||
} else {
|
||||
// Load standard opportunities
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Database Scope
|
||||
|
||||
All feature flags are scoped to specific database GUIDs, enabling per-client feature control in the multi-tenant architecture. This allows different clients to have different features enabled based on their subscription level or beta participation.
|
||||
|
||||
## Testing Considerations
|
||||
|
||||
When testing feature flag functionality:
|
||||
|
||||
1. **Mock the `IFeatureFlagServiceClient`** in unit tests
|
||||
2. **Test both enabled and disabled states** for each feature
|
||||
3. **Verify SDT employee bypass logic** where applicable
|
||||
4. **Test database GUID scoping** for multi-tenant scenarios
|
||||
|
||||
### Example Test Mock
|
||||
```csharp
|
||||
// C# example using Moq
|
||||
var featureFlagMock = new Mock<IFeatureFlagServiceClient>();
|
||||
featureFlagMock.Setup(ff => ff.IsEnabledAsync("ciflexibleexplorationenabled"))
|
||||
.ReturnsAsync(true);
|
||||
|
||||
// Use featureFlagMock.Object in tests
|
||||
```
|
||||
|
||||
## Service Registration
|
||||
|
||||
Feature flags are registered in the dependency injection container via the `IFeatureFlagServiceClient` service, typically configured in the `Startup.cs` or `Program.cs` file for the application.
|
||||
|
||||
## Logging and Monitoring
|
||||
|
||||
Many feature flag checks include structured logging to track usage patterns:
|
||||
|
||||
```csharp
|
||||
// C# example
|
||||
if (await _featureFlagServiceClient.IsEnabledAsync("ciflexibleexplorationenabled"))
|
||||
{
|
||||
_logger.LogInformation("Flexible exploration feature enabled");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("Flexible exploration feature disabled");
|
||||
}
|
||||
```
|
||||
|
||||
## Maintenance Notes
|
||||
|
||||
- All feature flag keys should be documented when added
|
||||
- Consider feature flag lifecycle and removal strategy for permanent features
|
||||
- Monitor feature flag usage through logging for analysis
|
||||
- Ensure consistent naming conventions for new feature flags
|
||||
- Regularly review and clean up obsolete feature flags
|
||||
- Update this documentation when new feature flags are added
|
||||
|
||||
## Feature Flag Lifecycle
|
||||
|
||||
1. **Development**: New feature flags are added for experimental or beta features
|
||||
2. **Testing**: Features are tested with flags enabled/disabled
|
||||
3. **Rollout**: Gradual enabling of features for specific clients
|
||||
4. **Stabilization**: Features become stable and widely adopted
|
||||
5. **Cleanup**: Feature flags are removed once features are permanent
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: November 2025*
|
||||
*Document Version: 1.0*
|
||||
*Total Feature Flags Documented: 9*
|
||||
@@ -0,0 +1,602 @@
|
||||
# Add Opportunity Type Playbook
|
||||
|
||||
Single source of truth for adding a new strategic opportunity type. Derived from the Charge Code (CC) and General Ledger (GL) implementations.
|
||||
|
||||
## Quick-Scan Checklist
|
||||
|
||||
Use this to track progress. Each item references a detailed section below.
|
||||
|
||||
### Backend
|
||||
- [ ] [Entity](#1-entity) — `StrategicOpportunity{Type}.cs` (TPT child table)
|
||||
- [ ] [Enums](#2-enums) — `OpportunityTypes` value + `MeasureType` values
|
||||
- [ ] [DTOs](#3-dtos) — Create DTO + Update DTO
|
||||
- [ ] [Detail Service](#4-detail-service) — `IStrategic{Type}OpportunityDetailService` + implementation
|
||||
- [ ] [Query Layer](#5-query-layer) — SQL files, ServiceQuery, QueryBuilder
|
||||
- [ ] [Distribution Process](#6-distribution-process) — `DistributionProcess{Type}.cs` + SQL builder
|
||||
- [ ] [Dimension Config](#7-dimension-config) — `{Type}DimensionsConfig` in `StrategicOpportunityService`
|
||||
- [ ] [Service Methods](#8-service-methods) — CRUD on `IStrategicOpportunityService`
|
||||
- [ ] [Controller Endpoints](#9-controller-endpoints) — REST endpoints
|
||||
- [ ] [DI Registration](#10-di-registration) — `ContinuousImprovementServiceExtensions.cs`
|
||||
- [ ] [Migration](#11-database-migration) — EF Core migration for child table
|
||||
|
||||
### Frontend
|
||||
- [ ] [Data Model](#f1-data-model) — `IStrategicOpportunity{Type}Data.ts`
|
||||
- [ ] [Measure Types](#f2-measure-types) — enum values + display names in `MeasureType.ts`
|
||||
- [ ] [Strategy Hook](#f3-strategy-hook) — `use{Type}Strategy.ts` implementing `IOpportunityTypeStrategy`
|
||||
- [ ] [Define Component](#f4-define-component) — `{Type}Define.tsx`
|
||||
- [ ] [Detail Store](#f5-detail-store) — `StrategicOpportunity{Type}DetailStoreTreeData.ts`
|
||||
- [ ] [Detail Grid](#f6-detail-grid) — `StrategicOpportunity{Type}DetailGrid.tsx`
|
||||
- [ ] [Layout Config](#f7-layout-config) — entry in `IStrategicOpportunityLayout.ts`
|
||||
- [ ] [Dimension Enums](#f8-dimension-enums) — fields/sections/columns in `StrategicOpportunityDetailDimension.ts`
|
||||
- [ ] [Service Methods](#f9-service-methods) — HTTP calls in `strategicOpportunityService.tsx`
|
||||
- [ ] [Wizard Integration](#f10-wizard-integration) — hook call + strategy switch in `StrategicOpportunity.tsx`
|
||||
- [ ] [Routes](#f11-routes) — create/edit routes in `Navigation.tsx`
|
||||
- [ ] [List Page](#f12-list-page) — ButtonMenu option in `StrategicItems.tsx`
|
||||
|
||||
### Cross-Cutting
|
||||
- [ ] [OpportunityTypes enum](#x1-opportunitytypes-enum) — both backend + frontend must match
|
||||
- [ ] [Detail Page Integration](#x2-detail-page-integration) — `StrategicOpportunityDetailPage.tsx` branches
|
||||
- [ ] [Tests](#x3-testing)
|
||||
|
||||
---
|
||||
|
||||
## Backend Steps
|
||||
|
||||
### 1. Entity
|
||||
|
||||
Create `Biz/StrategicOpportunities/StrategicOpportunity{Type}.cs` inheriting from `StrategicOpportunity` (TPT pattern).
|
||||
|
||||
**Pattern:**
|
||||
```csharp
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities
|
||||
{
|
||||
[Table("strategic_opportunity_{type}")]
|
||||
public class StrategicOpportunity{Type} : StrategicOpportunity
|
||||
{
|
||||
// Type-specific properties
|
||||
public string SomeHPath { get; set; }
|
||||
|
||||
public StrategicOpportunity{Type}() { }
|
||||
|
||||
// Copy constructor (used by CopyStrategicOpportunity)
|
||||
public StrategicOpportunity{Type}(StrategicOpportunity{Type} opportunity, string opportunityName)
|
||||
{
|
||||
// Copy all base properties
|
||||
Name = opportunityName;
|
||||
OpportunityType = opportunity.OpportunityType;
|
||||
OpportunityFiltersJSON = opportunity.OpportunityFiltersJSON;
|
||||
Rollup1Id = opportunity.Rollup1Id;
|
||||
Rollup2Id = opportunity.Rollup2Id;
|
||||
Rollup3Id = opportunity.Rollup3Id;
|
||||
BaselineType = opportunity.BaselineType;
|
||||
BaselineDistribution = opportunity.BaselineDistribution;
|
||||
BaselineStartDate = opportunity.BaselineStartDate;
|
||||
BaselineEndDate = opportunity.BaselineEndDate;
|
||||
EstimatedTrackingDuration = opportunity.EstimatedTrackingDuration;
|
||||
StrataId = opportunity.StrataId;
|
||||
LayoutJSON = opportunity.LayoutJSON;
|
||||
TeamMembers = opportunity.TeamMembers;
|
||||
OwnerGuid = opportunity.OwnerGuid;
|
||||
ExecutiveSponsorGuid = opportunity.ExecutiveSponsorGuid;
|
||||
CostLeaderGuid = opportunity.CostLeaderGuid;
|
||||
|
||||
// Copy type-specific properties
|
||||
SomeHPath = opportunity.SomeHPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**DbContext:** Add `DbSet<StrategicOpportunity{Type}>` to `CentralDbContext` and configure in `OnModelCreating`.
|
||||
|
||||
**References:**
|
||||
- `Biz/StrategicOpportunities/StrategicOpportunityChargeCode.cs`
|
||||
- `Biz/StrategicOpportunities/StrategicOpportunityGL.cs`
|
||||
- Base: `Biz/StrategicOpportunities/StrategicOpportunity.cs`
|
||||
|
||||
### 2. Enums
|
||||
|
||||
**OpportunityTypes** (`Biz/Generics/OpportunityTypes.cs` or wherever defined):
|
||||
Add a new enum value. Current values:
|
||||
- `StrategicOpportunityChargeCode` (value in `IAllOpportunity.ts` frontend)
|
||||
- `StrategicOpportunityGL`
|
||||
|
||||
**MeasureType** (`Biz/StrategicOpportunities/Enums/MeasureType.cs`):
|
||||
Add new measure type values after the last GL value (currently 37 = `GLMarginPerAdjDischarge`).
|
||||
```csharp
|
||||
// {Type} measure types (38+)
|
||||
{Type}Measure1 = 38,
|
||||
{Type}Measure2,
|
||||
// ...
|
||||
```
|
||||
|
||||
Also add a `{TYPE}_MEASURE_TYPES` constant list in `MeasureTypeConstants`.
|
||||
|
||||
**Other enums:** If the type has a concept like GL's `GLAccountType`, create a new enum file in `Biz/StrategicOpportunities/Enums/`.
|
||||
|
||||
### 3. DTOs
|
||||
|
||||
Create two DTOs in `Biz/StrategicOpportunities/Dtos/`:
|
||||
|
||||
**Create DTO** — `StrategicOpportunity{Type}Dto.cs`:
|
||||
```csharp
|
||||
public class StrategicOpportunity{Type}Dto : StrategicOpportunityDto
|
||||
{
|
||||
// Type-specific properties matching the entity
|
||||
public string SomeHPath { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
**Update DTO** — `StrategicOpportunity{Type}UpdateDto.cs`:
|
||||
```csharp
|
||||
public class StrategicOpportunity{Type}UpdateDto
|
||||
{
|
||||
public StrategicOpportunity{Type}Dto Opportunity { get; set; }
|
||||
public bool ShouldPopulateOpportunityDetails { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
**References:**
|
||||
- `Dtos/StrategicOpportunityGLDto.cs` / `StrategicOpportunityGLUpdateDto.cs`
|
||||
- `Dtos/StrategicOpportunityChargeCodeDto.cs` / `StrategicOpportunityChargeCodeUpdateDto.cs`
|
||||
|
||||
### 4. Detail Service
|
||||
|
||||
Create interface + implementation for populating detail data.
|
||||
|
||||
**Interface** — `IStrategic{Type}OpportunityDetailService.cs`:
|
||||
```csharp
|
||||
public interface IStrategic{Type}OpportunityDetailService
|
||||
{
|
||||
Task PopulateOpportunityDetailsAsync(StrategicOpportunity{Type} opportunity, CancellationToken ct);
|
||||
Task<StrategicOpportunityDetailData> GetDetailDataAsync(long opportunityId, bool isTracking, List<int> dimensions, CancellationToken ct);
|
||||
}
|
||||
```
|
||||
|
||||
**Implementation** — `Strategic{Type}OpportunityDetailService.cs`:
|
||||
Uses primary constructor DI, queries Snowflake/SQL for dimension data, populates `StrategicOpportunityDetail` rows.
|
||||
|
||||
**References:**
|
||||
- `IStrategicChargeCodeOpportunityDetailService.cs` + `StrategicChargeCodeOpportunityDetailService.cs`
|
||||
- `IStrategicGLOpportunityDetailService.cs` + `StrategicGLOpportunityDetailService.cs`
|
||||
|
||||
### 5. Query Layer
|
||||
|
||||
**SQL files** (embedded resources in `Biz/StrategicOpportunities/Queries/` or similar):
|
||||
- `StrategicOpportunity{Type}DetailCTE.sql` — main detail data query
|
||||
- Dimension-specific queries as needed
|
||||
|
||||
**ServiceQuery** — `StrategicOpportunity{Type}ServiceQuery.cs`:
|
||||
Loads SQL from embedded resources, provides query text to the detail service.
|
||||
|
||||
**QueryBuilder** — `StrategicOpportunity{Type}DetailQueryBuilder.cs`:
|
||||
Implements `IStrategicOpportunityDetailQueryBuilder`, builds parameterized queries for dimensions.
|
||||
|
||||
**References:**
|
||||
- `Queries/StrategicOpportunityGLDetailQueryBuilder.cs`
|
||||
- `Queries/StrategicOpportunityGLServiceQuery.cs`
|
||||
- `StrategicOpportunityServiceQuery.cs` (CC version)
|
||||
|
||||
### 6. Distribution Process
|
||||
|
||||
Create in `Biz/StrategicOpportunities/DistributionProcess/{Type}/`:
|
||||
|
||||
- `DistributionProcess{Type}.cs` — orchestrates data distribution
|
||||
- `DistributionProcess{Type}SqlBuilder.cs` — builds SQL for distribution
|
||||
- `Distribution{Type}Summary.cs` — summary model
|
||||
|
||||
Update `DistributionProcessFactory.cs` to handle the new type.
|
||||
|
||||
**References:**
|
||||
- `DistributionProcess/ChargeCode/DistributionProcessChargeCode.cs`
|
||||
- `DistributionProcess/GL/DistributionProcessGL.cs`
|
||||
- `DistributionProcess/DistributionProcessFactory.cs`
|
||||
|
||||
### 7. Dimension Config
|
||||
|
||||
In `StrategicOpportunityService.cs`, add a `{Type}DimensionsConfig` that maps dimension IDs to dimension names/hierarchy paths for the new type.
|
||||
|
||||
### 8. Service Methods
|
||||
|
||||
Add to `IStrategicOpportunityService.cs`:
|
||||
```csharp
|
||||
Task<long> CreateOpportunity{Type}Async(StrategicOpportunity{Type}Dto dto, CancellationToken ct);
|
||||
Task<long> UpdateOpportunity{Type}(long id, StrategicOpportunity{Type}UpdateDto dto, CancellationToken ct);
|
||||
Task<StrategicOpportunityEditData> GetStrategicOpportunity{Type}(long id, CancellationToken ct);
|
||||
Task<bool> Delete{Type}OpportunityAsync(long id, CancellationToken ct);
|
||||
```
|
||||
|
||||
Implement in `StrategicOpportunityService.cs`.
|
||||
|
||||
### 9. Controller Endpoints
|
||||
|
||||
Add endpoints to the strategic opportunities controller (or create a new controller if separation is needed):
|
||||
```
|
||||
POST /api/v1.0/strategic-opportunities/{type} — Create
|
||||
PUT /api/v1.0/strategic-opportunities/{type}/{id} — Update
|
||||
GET /api/v1.0/strategic-opportunities/{id}/{type} — Get for edit
|
||||
DELETE /api/v1.0/strategic-opportunities/{type}/{id} — Delete
|
||||
POST /api/v1.0/strategic-opportunities/{id}/{type}-detail — Get detail data
|
||||
PUT /api/v1.0/strategic-opportunities/{id}/{type}-details — Update goal values
|
||||
```
|
||||
|
||||
### 10. DI Registration
|
||||
|
||||
In `Biz/Configurations/ContinuousImprovementServiceExtensions.cs`:
|
||||
```csharp
|
||||
services.AddScoped<IStrategic{Type}OpportunityDetailService, Strategic{Type}OpportunityDetailService>();
|
||||
```
|
||||
|
||||
### 11. Database Migration
|
||||
|
||||
```bash
|
||||
dotnet ef migrations add AddStrategicOpportunity{Type}Table -p src/Strata.ContinuousImprovement.Biz -s src/Strata.ContinuousImprovement.Api
|
||||
```
|
||||
|
||||
**TPT gotcha:** EF Core generates the child table PK with the same name as the parent (`pk_strategic_opportunity`). You must manually rename it in the migration file:
|
||||
```csharp
|
||||
// Change this:
|
||||
name: "pk_strategic_opportunity",
|
||||
// To this:
|
||||
name: "pk_strategic_opportunity_{type}",
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Frontend Steps
|
||||
|
||||
All paths relative to `src/Strata.ContinuousImprovement.Web/continuousimprovement/src/`.
|
||||
|
||||
### F1. Data Model
|
||||
|
||||
Create `strategic-opportunities/data/IStrategicOpportunity{Type}Data.ts`:
|
||||
```typescript
|
||||
import { IStrategicOpportunityData, defaultStrategicOpportunityData } from './IStrategicOpportunityData';
|
||||
|
||||
export interface IStrategicOpportunity{Type}Data extends IStrategicOpportunityData {
|
||||
// Type-specific properties
|
||||
someHPath: string;
|
||||
}
|
||||
|
||||
export const defaultStrategicOpportunity{Type}Data: IStrategicOpportunity{Type}Data = {
|
||||
...defaultStrategicOpportunityData,
|
||||
someHPath: ''
|
||||
};
|
||||
```
|
||||
|
||||
**References:**
|
||||
- `data/IStrategicOpportunityChargeCodeData.ts`
|
||||
- `data/IStrategicOpportunityGLData.ts`
|
||||
|
||||
### F2. Measure Types
|
||||
|
||||
In `strategic-opportunities/data/MeasureType.ts`:
|
||||
|
||||
1. Add enum values (must match backend `MeasureType.cs`):
|
||||
```typescript
|
||||
// {Type} measure types (sync with backend, values N-M)
|
||||
{Type}Measure1 = N,
|
||||
{Type}Measure2,
|
||||
```
|
||||
|
||||
2. Add display names array:
|
||||
```typescript
|
||||
export const {type}MeasureTypeData: IKeyNameItem[] = [
|
||||
{ key: MeasureType.{Type}Measure1, name: 'Display Name 1' },
|
||||
// ...
|
||||
];
|
||||
```
|
||||
|
||||
3. Add entries to `mapMeasureTypeToDesiredResult`.
|
||||
|
||||
4. Add helper like `is{Type}PerMeasureType()` if the type has "per unit" style measures.
|
||||
|
||||
### F3. Strategy Hook
|
||||
|
||||
Create `strategic-opportunities/wizard/{type}/use{Type}Strategy.ts`:
|
||||
|
||||
Must implement `IOpportunityTypeStrategy` from `wizard/types/IOpportunityTypeStrategy.ts`:
|
||||
|
||||
```typescript
|
||||
export interface IOpportunityTypeStrategy {
|
||||
getDefaultData(): IStrategicOpportunityData;
|
||||
renderDefineStep(context: IDefineStepContext): React.ReactElement | null;
|
||||
renderModals(context: IModalContext): React.ReactElement | null;
|
||||
validateDefineStep(formValues: any): string | true;
|
||||
marshalDefineStep(formValues: any, context: IMarshalContext): Partial<IStrategicOpportunityData>;
|
||||
onFormValuesChanged(changedValues: any, context: IFormChangeContext): void;
|
||||
loadEditData(data: IStrategicOpportunityEditData, context: IEditLoadContext): void;
|
||||
loadPrePopulationData?(params: URLSearchParams, context: IPrePopContext): Promise<void>;
|
||||
create(data: IStrategicOpportunityData): Promise<number>;
|
||||
update(opportunityId: number, data: IStrategicOpportunityData, shouldPopulate: boolean): Promise<boolean>;
|
||||
shouldRepopulateOnEdit(context: IRepopulateContext): boolean;
|
||||
getMeasureTypeData(): IKeyNameItem[];
|
||||
}
|
||||
```
|
||||
|
||||
**Key responsibilities:**
|
||||
- `getDefaultData()` — return `defaultStrategicOpportunity{Type}Data` with correct `opportunityType`
|
||||
- `renderDefineStep()` — render the type-specific Define component via `React.createElement`
|
||||
- `validateDefineStep()` — return error string or `true`
|
||||
- `marshalDefineStep()` — convert form values to data model fields
|
||||
- `create()/update()` — call type-specific service methods
|
||||
- `getMeasureTypeData()` — return the type-specific measure list
|
||||
|
||||
**Props pattern:**
|
||||
```typescript
|
||||
export interface I{Type}StrategyProps {
|
||||
form: FormInstance;
|
||||
isActive: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
**References:**
|
||||
- `wizard/charge-code/useChargeCodeStrategy.ts`
|
||||
- `wizard/general-ledger/useGLStrategy.ts`
|
||||
|
||||
### F4. Define Component
|
||||
|
||||
Create `strategic-opportunities/wizard/{type}/{Type}Define.tsx`:
|
||||
|
||||
Type-specific Step 2 content — pickers, filters, measure type selection, desired result.
|
||||
|
||||
**References:**
|
||||
- `wizard/charge-code/ChargeCodeDefine.tsx`
|
||||
- `wizard/general-ledger/GeneralLedgerDefine.tsx`
|
||||
|
||||
### F5. Detail Store
|
||||
|
||||
Create `strategic-opportunities/data/store/StrategicOpportunity{Type}DetailStoreTreeData.ts`:
|
||||
|
||||
Extends `StoreTreeData` with the type-specific grid item interface. Handles:
|
||||
- Loading data into tree structure
|
||||
- Goal editing (onChange)
|
||||
- Row config by dimensions
|
||||
|
||||
**References:**
|
||||
- `data/store/StrategicOpportunityDetailStoreTreeData.ts` (CC)
|
||||
- `data/store/StrategicOpportunityGLDetailStoreTreeData.ts` (GL)
|
||||
|
||||
### F6. Detail Grid
|
||||
|
||||
Create `strategic-opportunities/detail/StrategicOpportunity{Type}DetailGrid.tsx`:
|
||||
|
||||
DataGrid component with type-specific columns. Receives `root`, `columns`, `levelsCount`, `loading`, `expandedKeys`, `onChange`, `isEditable`.
|
||||
|
||||
**References:**
|
||||
- `detail/StrategicOpportunityDetailGrid.tsx` (CC)
|
||||
- `detail/StrategicOpportunityGLDetailGrid.tsx` (GL)
|
||||
|
||||
### F7. Layout Config
|
||||
|
||||
In `strategic-opportunities/detail/data/IStrategicOpportunityLayout.ts`:
|
||||
|
||||
1. Add default layout:
|
||||
```typescript
|
||||
export const defaultStrategicOpportunity{Type}Layout: IStrategicOpportunityLayout = {
|
||||
rows: [strategicOpportunityDetailDimensionTotal, ...default{Type}Dimensions],
|
||||
columns: [...default{Type}Columns]
|
||||
};
|
||||
```
|
||||
|
||||
2. Add layout config:
|
||||
```typescript
|
||||
export const {type}OpportunityLayoutConfig: IDetailLayoutConfig = {
|
||||
defaultLayout: defaultStrategicOpportunity{Type}Layout,
|
||||
rowLayoutList: {type}RowLayoutList,
|
||||
columnLayoutList: {type}ColumnLayoutList,
|
||||
requiredRows: [StrategicOpportunityDetailDimension.{TypeDimension}],
|
||||
requiredColumns: [{Type}DetailField.{RequiredColumn}]
|
||||
};
|
||||
```
|
||||
|
||||
3. Update factory function:
|
||||
```typescript
|
||||
export function getDetailLayoutConfig(...): IDetailLayoutConfig {
|
||||
if (opportunityType === OpportunityTypes.StrategicOpportunity{Type}) {
|
||||
return {type}OpportunityLayoutConfig;
|
||||
}
|
||||
// ... existing cases
|
||||
}
|
||||
```
|
||||
|
||||
### F8. Dimension Enums
|
||||
|
||||
In `strategic-opportunities/detail/data/StrategicOpportunityDetailDimension.ts`:
|
||||
|
||||
1. Add dimension values to `StrategicOpportunityDetailDimension` enum (if new dimensions needed)
|
||||
2. Add dimension ID to `StrategicOpportunityDetailDimensionId` enum
|
||||
3. Create detail field enum:
|
||||
```typescript
|
||||
export enum StrategicOpportunity{Type}DetailField {
|
||||
// Type-specific grid columns
|
||||
}
|
||||
```
|
||||
4. Create column name/header maps
|
||||
5. Create field section enum + section-to-field mappings
|
||||
6. Add helper functions for parsing columns/dimensions by string
|
||||
|
||||
### F9. Service Methods
|
||||
|
||||
In `strategic-opportunities/data/strategicOpportunityService.tsx`, add:
|
||||
```typescript
|
||||
createOpportunity{Type}: async (opportunity) => httpPost(`${url}/{type}`, opportunity),
|
||||
updateOpportunity{Type}: async (id, opportunity, shouldPopulate) =>
|
||||
httpPut(`${url}/{type}/${id}`, { opportunity, shouldPopulateOpportunityDetails: shouldPopulate }),
|
||||
getOpportunity{Type}: async (id) => httpGet(`${url}/${id}/GetOpportunity{Type}`),
|
||||
getOpportunity{Type}DetailData: async (id, isTracking, dimensions) =>
|
||||
httpPost(`${url}/${id}/${isTracking ? '{type}-tracking-detail' : '{type}-detail'}`, dimensions),
|
||||
updateOpportunity{Type}DetailsData: async (id, data) =>
|
||||
httpPut(`${url}/${id}/{type}-details`, data),
|
||||
```
|
||||
|
||||
Also update `IStrategicOpportunityService.ts` interface with the new methods.
|
||||
|
||||
### F10. Wizard Integration
|
||||
|
||||
In `strategic-opportunities/wizard/StrategicOpportunity.tsx`:
|
||||
|
||||
1. Import the new strategy hook:
|
||||
```typescript
|
||||
import { use{Type}Strategy } from './{type}/use{Type}Strategy';
|
||||
```
|
||||
|
||||
2. Add hook call (must always be called — React rules of hooks):
|
||||
```typescript
|
||||
const {type}Strategy = use{Type}Strategy({ form, isActive: opportunityType === OpportunityTypes.StrategicOpportunity{Type} });
|
||||
```
|
||||
|
||||
3. Update `activeStrategy` selection:
|
||||
```typescript
|
||||
const is{Type} = opportunityType === OpportunityTypes.StrategicOpportunity{Type};
|
||||
// All hooks always called
|
||||
const ccStrategy = useChargeCodeStrategy({ form, isActive: !isGL && !is{Type} });
|
||||
const glStrategy = useGLStrategy({ form, isActive: isGL });
|
||||
const {type}Strategy = use{Type}Strategy({ form, isActive: is{Type} });
|
||||
const activeStrategy = is{Type} ? {type}Strategy : isGL ? glStrategy : ccStrategy;
|
||||
```
|
||||
|
||||
### F11. Routes
|
||||
|
||||
In `shared/Navigation.tsx`, add create and edit routes:
|
||||
```tsx
|
||||
<Route
|
||||
path={['/strategic-opportunities/create/{type}']}
|
||||
exact
|
||||
component={(props) => StrategicOpportunity({ ...props, opportunityType: OpportunityTypes.StrategicOpportunity{Type} })}
|
||||
key='/strategic-opportunities/create/{type}'
|
||||
/>
|
||||
<Route
|
||||
path={['/strategic-opportunities/edit/{type}/:id']}
|
||||
exact
|
||||
component={(props) => StrategicOpportunity({ ...props, opportunityType: OpportunityTypes.StrategicOpportunity{Type} })}
|
||||
key='/strategic-opportunities/edit/{type}'
|
||||
/>
|
||||
```
|
||||
|
||||
### F12. List Page
|
||||
|
||||
In `strategic-opportunities/opportunities/StrategicItems.tsx`, add to the ButtonMenu items:
|
||||
```typescript
|
||||
items={[
|
||||
{ key: 'charge-code', label: 'Charge Code' },
|
||||
{ key: 'general-ledger', label: 'General Ledger' },
|
||||
{ key: '{type}', label: '{Display Name}' }
|
||||
]}
|
||||
```
|
||||
|
||||
And handle the click:
|
||||
```typescript
|
||||
onClick={(e) => {
|
||||
// ... existing cases
|
||||
else if (e.key === '{type}') {
|
||||
history.push('/strategic-opportunities/create/{type}');
|
||||
}
|
||||
}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Cross-Cutting
|
||||
|
||||
### X1. OpportunityTypes Enum
|
||||
|
||||
Both backend and frontend must have matching values:
|
||||
|
||||
**Backend** — `Biz/Generics/OpportunityTypes.cs` (or wherever `OpportunityTypes` is defined)
|
||||
**Frontend** — `opportunities/opportunities/data/IAllOpportunity.ts`
|
||||
|
||||
Current values:
|
||||
```
|
||||
StrategicOpportunityChargeCode = 17 (approx)
|
||||
StrategicOpportunityGL = 18 (approx)
|
||||
```
|
||||
New type should be the next sequential value.
|
||||
|
||||
### X2. Detail Page Integration
|
||||
|
||||
In `strategic-opportunities/detail/StrategicOpportunityDetailPage.tsx`, add branches for:
|
||||
|
||||
1. `storeTreeData` selection (around line 181):
|
||||
```typescript
|
||||
if (is{Type}) {
|
||||
return new StrategicOpportunity{Type}DetailStoreTreeData(DefaultGridItem);
|
||||
}
|
||||
```
|
||||
|
||||
2. Detail data fetching (around line 252):
|
||||
```typescript
|
||||
if (is{Type}) {
|
||||
// Fetch type-specific detail data
|
||||
}
|
||||
```
|
||||
|
||||
3. Grid column parsing (around line 305):
|
||||
```typescript
|
||||
if (is{Type}) {
|
||||
_gridColumns = getStrategicOpportunity{Type}DetailColumnsByString(layout.columns);
|
||||
}
|
||||
```
|
||||
|
||||
4. Grid rendering (around line 853):
|
||||
```typescript
|
||||
is{Type} ? (
|
||||
<StrategicOpportunity{Type}DetailGrid ... />
|
||||
) : isGL ? (
|
||||
...
|
||||
)
|
||||
```
|
||||
|
||||
5. Save handler — map dirty data to goal values for the new type.
|
||||
|
||||
6. Edit navigation path:
|
||||
```typescript
|
||||
const typePath = is{Type} ? '{type}' : isGL ? 'general-ledger' : 'charge-code';
|
||||
```
|
||||
|
||||
7. Comments drawer `opportunityType` prop.
|
||||
|
||||
### X3. Testing
|
||||
|
||||
**Backend:**
|
||||
- Unit tests for the detail service
|
||||
- Unit tests for the distribution process
|
||||
- Unit tests for new service methods
|
||||
|
||||
**Frontend:**
|
||||
- Unit test for the strategy hook (validate, marshal, create/update)
|
||||
- Unit test for the Define component
|
||||
- Unit test for the detail grid component
|
||||
|
||||
---
|
||||
|
||||
## File Reference Table
|
||||
|
||||
| Pattern | CC Reference | GL Reference |
|
||||
|---------|-------------|--------------|
|
||||
| Entity | `StrategicOpportunityChargeCode.cs` | `StrategicOpportunityGL.cs` |
|
||||
| Create DTO | `StrategicOpportunityChargeCodeDto.cs` | `StrategicOpportunityGLDto.cs` |
|
||||
| Update DTO | `StrategicOpportunityChargeCodeUpdateDto.cs` | `StrategicOpportunityGLUpdateDto.cs` |
|
||||
| Detail Service | `StrategicChargeCodeOpportunityDetailService.cs` | `StrategicGLOpportunityDetailService.cs` |
|
||||
| Distribution | `DistributionProcess/ChargeCode/` | `DistributionProcess/GL/` |
|
||||
| Query Builder | `StrategicOpportunityDetailQueryBuilder.cs` | `StrategicOpportunityGLDetailQueryBuilder.cs` |
|
||||
| Service Query | `StrategicOpportunityServiceQuery.cs` | `StrategicOpportunityGLServiceQuery.cs` |
|
||||
| Data Model | `IStrategicOpportunityChargeCodeData.ts` | `IStrategicOpportunityGLData.ts` |
|
||||
| Strategy Hook | `useChargeCodeStrategy.ts` | `useGLStrategy.ts` |
|
||||
| Define Component | `ChargeCodeDefine.tsx` | `GeneralLedgerDefine.tsx` |
|
||||
| Store | `StrategicOpportunityDetailStoreTreeData.ts` | `StrategicOpportunityGLDetailStoreTreeData.ts` |
|
||||
| Detail Grid | `StrategicOpportunityDetailGrid.tsx` | `StrategicOpportunityGLDetailGrid.tsx` |
|
||||
|
||||
## Tips and Gotchas
|
||||
|
||||
1. **TPT PK collision**: Always check the generated migration for duplicate PK constraint names. Rename `pk_strategic_opportunity` to `pk_strategic_opportunity_{type}`.
|
||||
|
||||
2. **React hooks order**: All strategy hooks must be called unconditionally in `StrategicOpportunity.tsx` (React rules of hooks). Use the `isActive` prop to no-op inactive strategies.
|
||||
|
||||
3. **MeasureType sync**: Backend `MeasureType` enum values must exactly match frontend `MeasureType` const enum values. Double-check after adding.
|
||||
|
||||
4. **Layout compatibility**: The `isLayoutCompatible()` function checks `requiredColumns`. Make sure your type's required columns are unique to avoid stale layouts from other types being loaded.
|
||||
|
||||
5. **Formatting**: Run `npx eslint --fix <files>` after editing (not `npx prettier --write`).
|
||||
@@ -0,0 +1,404 @@
|
||||
# Strategic Opportunity Wizard Refactor — Migration Guide
|
||||
|
||||
## Who This Is For
|
||||
|
||||
If you wrote or maintain `StrategicOpportunity.tsx` (the wizard orchestrator) or any of its step components, this document explains what's changing, why, and how your code maps to the new structure.
|
||||
|
||||
---
|
||||
|
||||
## Why Are We Doing This?
|
||||
|
||||
The wizard was built for **Charge Code** opportunities only. Now we need to add **General Ledger (GL)** opportunities that use the same 5-step wizard flow but have completely different "Define Opportunity" logic (departments + GL accounts instead of encounter groups + charge codes).
|
||||
|
||||
The current orchestrator (`StrategicOpportunity.tsx`, 670 lines) has charge-code-specific state, validation, data marshaling, and service calls baked directly in. If we added GL logic the same way, the file would balloon with `if (opportunityType === 'GL')` branches everywhere.
|
||||
|
||||
Instead, we're extracting type-specific logic into **strategy hooks** — one for Charge Code, one for GL — and keeping the orchestrator focused on what's shared: wizard navigation, form management, and the steps that are identical for both types.
|
||||
|
||||
---
|
||||
|
||||
## The Big Picture: Before vs After
|
||||
|
||||
### Before (today)
|
||||
|
||||
```
|
||||
StrategicOpportunity.tsx (670 lines)
|
||||
├── Wizard navigation + form setup
|
||||
├── 12+ CC-specific state variables (chargeCodeMembers, selectedChargeCodes, etc.)
|
||||
├── CC-specific functions (loadChargeCodePickerModels, etc.)
|
||||
├── CC-specific validation in handleChange (step 1)
|
||||
├── CC-specific data marshaling in updateOpportunity (step 1)
|
||||
├── CC-specific edit loading in loadEditData
|
||||
├── CC-specific pre-population from exploration
|
||||
├── CC-specific create/update service calls
|
||||
└── CC-specific repopulation detection on edit
|
||||
```
|
||||
|
||||
Everything lives in one file. There's no separation between "wizard infrastructure" and "charge code logic."
|
||||
|
||||
### After (refactored)
|
||||
|
||||
```
|
||||
StrategicOpportunity.tsx (~300 lines) — Generic orchestrator
|
||||
├── Wizard navigation + form setup (unchanged)
|
||||
├── Shared state only (primaryMeasures, filters, rollups, workbooks, baseline)
|
||||
├── Calls activeStrategy.validateDefineStep() instead of inline CC validation
|
||||
├── Calls activeStrategy.marshalDefineStep() instead of inline CC marshaling
|
||||
├── Calls activeStrategy.loadEditData() for type-specific edit fields
|
||||
├── Calls activeStrategy.create() / activeStrategy.update()
|
||||
└── Renders activeStrategy.renderDefineStep() for step 1
|
||||
|
||||
charge-code/useChargeCodeStrategy.ts (~250 lines) — All the CC logic extracted
|
||||
├── CC state (chargeCodeMembers, selectedChargeCodes, etc.)
|
||||
├── CC functions (loadChargeCodePickerModels, etc.)
|
||||
├── CC step 1 validation + marshaling
|
||||
├── CC edit loading + pre-population
|
||||
└── CC create/update service calls
|
||||
|
||||
general-ledger/useGLStrategy.ts — New GL logic
|
||||
├── GL state (accountType, departments, GL accounts)
|
||||
├── GL step 1 validation + marshaling
|
||||
├── GL edit loading
|
||||
└── GL create/update service calls
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What Stays the Same
|
||||
|
||||
These things are **not changing** (or changing only trivially):
|
||||
|
||||
| Component | What's happening |
|
||||
|-----------|-----------------|
|
||||
| **StrategicOpportunityDetail.tsx** (Step 0) | Shared. No change except minor display support for GL type. |
|
||||
| **StrategicOpportunityDefineTimeframe.tsx** (Step 3) | Shared. No change. |
|
||||
| **StrategicOpportunityExternalLinks.tsx** (Step 4) | Shared. No change. |
|
||||
| **StrategicOpportunityRenameMeasure.tsx** | Shared utility. No change. |
|
||||
| **StrategicOpportunityFilters.tsx** | Shared utility. No change. |
|
||||
| **All data/ files** | No changes to interfaces, services, enums. |
|
||||
| **Wizard step validation for steps 0, 2, 3, 4** | Stays in the orchestrator — these steps are identical for both types. |
|
||||
|
||||
---
|
||||
|
||||
## What's Moving
|
||||
|
||||
### Files that move (same code, new location)
|
||||
|
||||
| Old Location | New Location | Why |
|
||||
|-------------|-------------|-----|
|
||||
| `wizard/StrategicOpportunityDefine.tsx` | `wizard/charge-code/ChargeCodeDefine.tsx` | This component is 100% charge-code-specific. Renaming makes that explicit. |
|
||||
| `wizard/ChargeCodeCustomPickerModal.tsx` | `wizard/charge-code/ChargeCodeCustomPickerModal.tsx` | Only used by charge code define step. |
|
||||
|
||||
These are **moves + renames**, not rewrites. The component logic inside is the same.
|
||||
|
||||
### State that moves from orchestrator → CC strategy hook
|
||||
|
||||
All of these `useState` calls currently in `StrategicOpportunity.tsx` will move into `useChargeCodeStrategy.ts`:
|
||||
|
||||
```typescript
|
||||
// These move OUT of the orchestrator:
|
||||
const [chargeCodeMembers, setChargeCodeMembers] = useState(...);
|
||||
const [selectedChargeCodes, setSelectedChargeCodes] = useState(...);
|
||||
const [selectedChargeCodesInitial, setSelectedChargeCodesInitial] = useState(...);
|
||||
const [expPopulationMembers, setExpPopulationMembers] = useState(...);
|
||||
const [selectedExpPopulations, setSelectedExpPopulations] = useState(...);
|
||||
const [patientPopulationsInitial, setPatientPopulationsInitial] = useState(...);
|
||||
const [expPopulationsInitial, setExpPopulationsInitial] = useState(...);
|
||||
const [expPopCostDriver, setExpPopCostDriver] = useState(...);
|
||||
const [uploadModalVisible, setUploadModalVisible] = useState(...);
|
||||
const [isPrePopulated, setIsPrePopulated] = useState(...);
|
||||
```
|
||||
|
||||
```typescript
|
||||
// These STAY in the orchestrator (shared by both types):
|
||||
const [stepNumber, setStepNumber] = useState(0);
|
||||
const [opportunity, setOpportunity] = useState(...); // now typed as IStrategicOpportunityData (base)
|
||||
const [primaryMeasures, setPrimaryMeasures] = useState(...);
|
||||
const [workbooks, setWorkbooks] = useState(...);
|
||||
const [opportunityFilters, setOpportunityFilters] = useState(...);
|
||||
const [baselinePeriodInitial, setBaselinePeriodInitial] = useState(...);
|
||||
const [rollupColumns, setRollupColumns] = useState(...);
|
||||
const [isEditMode, setIsEditMode] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
```
|
||||
|
||||
### Functions that move from orchestrator → CC strategy hook
|
||||
|
||||
| Function | Why it's CC-specific |
|
||||
|----------|---------------------|
|
||||
| `loadChargeCodePickerModels()` | Fetches charge code picker data |
|
||||
| `loadExpPopulationPickerModels()` | Fetches exploration populations |
|
||||
| `setPrimaryMeasureChargeCodes()` | Sets CC-specific form field |
|
||||
| `setExplorationPopulations()` | Sets CC-specific encounter group |
|
||||
| `handleChargeCodesUploaded()` | Handles CC upload modal result |
|
||||
| Pre-population `useEffect` (lines 584-642) | Only applies to CC from exploration |
|
||||
| CC-specific parts of `loadEditData` | Loading encounter groups, charge codes |
|
||||
| CC-specific parts of `handleFormValuesChanged` | CC form field change handling |
|
||||
| Step 1 block in `handleChange` | CC-specific validation checks |
|
||||
| Step 1 block in `updateOpportunity` | CC-specific data marshaling |
|
||||
| `handleSubmit` service calls | `createOpportunityChargeCode` / `updateOpportunityChargeCode` |
|
||||
| Repopulation detection logic | Comparing CC-specific initial vs current state |
|
||||
|
||||
---
|
||||
|
||||
## The Strategy Interface
|
||||
|
||||
Both hooks implement this common interface so the orchestrator doesn't need to know which type it's working with:
|
||||
|
||||
```typescript
|
||||
interface IOpportunityTypeStrategy {
|
||||
// Default data for creating a new opportunity of this type
|
||||
getDefaultData(): IStrategicOpportunityData;
|
||||
|
||||
// Renders the "Define Opportunity" step (step 1) — CC shows encounter groups + charge codes, GL shows departments + accounts
|
||||
renderDefineStep(context): JSX.Element;
|
||||
|
||||
// Renders any modals this type needs (CC has upload modal, GL has none)
|
||||
renderModals(context): JSX.Element | null;
|
||||
|
||||
// Returns true if step 1 form values are valid for this type
|
||||
validateDefineStep(formValues): boolean;
|
||||
|
||||
// Converts form values into opportunity data fields for step 1
|
||||
marshalDefineStep(formValues, context): Partial<IStrategicOpportunityData>;
|
||||
|
||||
// Handles form value changes that need type-specific reactions
|
||||
onFormValuesChanged(changedValues, context): void;
|
||||
|
||||
// Loads type-specific fields when editing an existing opportunity
|
||||
loadEditData(data, form, context): void;
|
||||
|
||||
// (CC only) Pre-populates from exploration population URL params
|
||||
loadPrePopulationData?(params, form, context): Promise<void>;
|
||||
|
||||
// Calls the correct create API
|
||||
create(data): Promise<number>;
|
||||
|
||||
// Calls the correct update API
|
||||
update(opportunityId, data, shouldPopulate): Promise<boolean>;
|
||||
|
||||
// Determines if editing has changed enough to require goal recalculation
|
||||
shouldRepopulateOnEdit(context): boolean;
|
||||
|
||||
// Returns the measure type list for the "Additional Measures" step dropdown
|
||||
getMeasureTypeData(): { key: number; name: string }[];
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How the Orchestrator Changes
|
||||
|
||||
### Before: inline CC logic
|
||||
|
||||
```typescript
|
||||
// In handleChange, step 1 validation:
|
||||
} else if (currentStep === 1) {
|
||||
if (!detailValues.encounterGroup || !detailValues.encounterGroups || ...) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### After: delegates to strategy
|
||||
|
||||
```typescript
|
||||
} else if (currentStep === 1) {
|
||||
if (!activeStrategy.validateDefineStep(detailValues)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Before: inline CC rendering
|
||||
|
||||
```typescript
|
||||
<Wizard.Step key='1' title='Define Opportunity'>
|
||||
<StrategicOpportunityDefine
|
||||
encounterGroup={opportunity.encounterGroup}
|
||||
chargeCodeMembers={chargeCodeMembers}
|
||||
selectedChargeCodes={selectedChargeCodes}
|
||||
// ... 14 props
|
||||
/>
|
||||
</Wizard.Step>
|
||||
```
|
||||
|
||||
### After: strategy renders its own step
|
||||
|
||||
```typescript
|
||||
<Wizard.Step key='1' title='Define Opportunity'>
|
||||
{activeStrategy.renderDefineStep({ form, filters, updateFilters, ... })}
|
||||
</Wizard.Step>
|
||||
```
|
||||
|
||||
### Before: inline CC service call
|
||||
|
||||
```typescript
|
||||
const opportunityId = await strategicOpportunityService.createOpportunityChargeCode(data);
|
||||
```
|
||||
|
||||
### After: strategy handles service call
|
||||
|
||||
```typescript
|
||||
const opportunityId = await activeStrategy.create(data);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hook Selection: Both Hooks Always Called
|
||||
|
||||
React requires hooks to be called unconditionally (same hooks, same order, every render). So the orchestrator calls **both** hooks every time:
|
||||
|
||||
```typescript
|
||||
const ccStrategy = useChargeCodeStrategy({ form, ... });
|
||||
const glStrategy = useGLStrategy({ form, ... });
|
||||
|
||||
const activeStrategy = opportunityType === OpportunityTypes.StrategicOpportunityGL
|
||||
? glStrategy
|
||||
: ccStrategy;
|
||||
```
|
||||
|
||||
The inactive hook holds trivial default state — a few empty arrays and `false` booleans. No API calls, no heavy computation. This is a standard React pattern.
|
||||
|
||||
---
|
||||
|
||||
## New Folder Structure
|
||||
|
||||
```
|
||||
strategic-opportunities/
|
||||
wizard/
|
||||
StrategicOpportunity.tsx ← Slimmed-down orchestrator
|
||||
StrategicOpportunityDetail.tsx ← Step 0 (unchanged)
|
||||
StrategicOpportunityDefineAdditional.tsx ← Step 2 (adds measureTypeData prop)
|
||||
StrategicOpportunityDefineTimeframe.tsx ← Step 3 (unchanged)
|
||||
StrategicOpportunityExternalLinks.tsx ← Step 4 (unchanged)
|
||||
StrategicOpportunityRenameMeasure.tsx ← Shared utility (unchanged)
|
||||
StrategicOpportunityFilters.tsx ← Shared utility (unchanged)
|
||||
types/
|
||||
IOpportunityTypeStrategy.ts ← Strategy interface + context types
|
||||
charge-code/
|
||||
useChargeCodeStrategy.ts ← CC strategy hook (extracted from orchestrator)
|
||||
ChargeCodeDefine.tsx ← Moved from StrategicOpportunityDefine.tsx
|
||||
ChargeCodeCustomPickerModal.tsx ← Moved from wizard root
|
||||
general-ledger/
|
||||
useGLStrategy.ts ← GL strategy hook (new)
|
||||
GeneralLedgerDefine.tsx ← GL define step (new)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## StrategicOpportunityDefineAdditional Change
|
||||
|
||||
This component currently hardcodes the charge code measure type list:
|
||||
|
||||
```typescript
|
||||
// Line 18 — imports CC-only measure types
|
||||
import { getDesiredResultByMeasureType, measureTypeData } from '../data/MeasureType';
|
||||
|
||||
// Line 31 — hardcoded to CC measures
|
||||
const secondaryMeasureItems = useMemo(() => measureTypeData.map(...), []);
|
||||
```
|
||||
|
||||
After the refactor, it receives `measureTypeData` as a **prop** from the orchestrator, which gets it from `activeStrategy.getMeasureTypeData()`. This way CC shows CC measures and GL shows GL measures in the "Add Additional Measure" dropdown.
|
||||
|
||||
```typescript
|
||||
// Before
|
||||
<StrategicOpportunityDefineAdditional
|
||||
getMeasureItem={getMeasureItem}
|
||||
updateFiltersForSecondaryMeasure={updateFiltersForSecondaryMeasure}
|
||||
validateAndUpdateMeasureName={validateAndUpdateMeasureName}
|
||||
/>
|
||||
|
||||
// After — adds measureTypeData prop
|
||||
<StrategicOpportunityDefineAdditional
|
||||
getMeasureItem={getMeasureItem}
|
||||
updateFiltersForSecondaryMeasure={updateFiltersForSecondaryMeasure}
|
||||
validateAndUpdateMeasureName={validateAndUpdateMeasureName}
|
||||
measureTypeData={activeStrategy.getMeasureTypeData()}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## New Routes
|
||||
|
||||
Two new routes added to `Navigation.tsx`:
|
||||
|
||||
| Route | Purpose |
|
||||
|-------|---------|
|
||||
| `/strategic-opportunities/create/general-ledger` | Create a new GL strategic opportunity |
|
||||
| `/strategic-opportunities/edit/general-ledger/:strategicOpportunityId` | Edit an existing GL strategic opportunity |
|
||||
|
||||
These follow the exact same pattern as the existing CC routes (lines 226-236 in Navigation.tsx).
|
||||
|
||||
---
|
||||
|
||||
## What the GL Define Step Looks Like
|
||||
|
||||
Instead of Encounter Groups + Charge Codes + Upload, the GL define step has:
|
||||
|
||||
1. **Account Type** — Radio: Revenue / Expense / Margin
|
||||
2. **Department** — ScoreMemberPicker for GL departments
|
||||
3. **GL Accounts** — Conditional pickers based on account type:
|
||||
- Revenue → Revenue account picker
|
||||
- Expense → Expense account picker
|
||||
- Margin → Both revenue + expense pickers
|
||||
- Optional statistic account picker (checkbox toggle)
|
||||
4. **Filters** — Same shared StrategicFilterBar component
|
||||
5. **Primary Measure** — Dropdown from `glMeasureTypeData` (instead of `measureTypeData`)
|
||||
6. **Desired Result + Threshold** — Same UI as CC
|
||||
|
||||
---
|
||||
|
||||
## How to Verify Nothing Broke
|
||||
|
||||
After the refactor, test these CC flows — they should behave **identically** to today:
|
||||
|
||||
1. **Create CC opportunity**: Navigate to `/strategic-opportunities/create/charge-code`, complete all 5 steps, verify it saves
|
||||
2. **Edit CC opportunity**: Open an existing CC strategic opportunity, click Edit, verify all fields load correctly, make a change, save
|
||||
3. **Pre-populate from exploration**: From an exploration population, click "Create Strategic Opportunity" — verify encounter group, charge codes, and baseline are pre-filled
|
||||
4. **Goal reset warning**: Edit an opportunity, change the filters or baseline period, click Save — verify the "resets goals" confirmation modal appears
|
||||
5. **Run frontend tests**: `cd src/Strata.ContinuousImprovement.Web/continuousimprovement && npm run test-ci`
|
||||
|
||||
---
|
||||
|
||||
## Mapping Cheat Sheet: "Where Did My Code Go?"
|
||||
|
||||
| If you're looking for... | It's now in... |
|
||||
|--------------------------|---------------|
|
||||
| `chargeCodeMembers` state | `useChargeCodeStrategy.ts` |
|
||||
| `selectedChargeCodes` state | `useChargeCodeStrategy.ts` |
|
||||
| `loadChargeCodePickerModels()` | `useChargeCodeStrategy.ts` |
|
||||
| `loadExpPopulationPickerModels()` | `useChargeCodeStrategy.ts` |
|
||||
| `handleChargeCodesUploaded()` | `useChargeCodeStrategy.ts` |
|
||||
| Pre-population `useEffect` | `useChargeCodeStrategy.ts` → `loadPrePopulationData()` |
|
||||
| `StrategicOpportunityDefine` component | `charge-code/ChargeCodeDefine.tsx` (same code, new name) |
|
||||
| `ChargeCodeCustomPickerModal` | `charge-code/ChargeCodeCustomPickerModal.tsx` (moved) |
|
||||
| Step 1 validation in `handleChange` | `useChargeCodeStrategy.ts` → `validateDefineStep()` |
|
||||
| Step 1 marshaling in `updateOpportunity` | `useChargeCodeStrategy.ts` → `marshalDefineStep()` |
|
||||
| `createOpportunityChargeCode` call | `useChargeCodeStrategy.ts` → `create()` |
|
||||
| `updateOpportunityChargeCode` call | `useChargeCodeStrategy.ts` → `update()` |
|
||||
| Repopulation check (`shouldPopulateOpportunityDetails`) | `useChargeCodeStrategy.ts` → `shouldRepopulateOnEdit()` |
|
||||
| Wizard navigation (`handleChange`, `setStepNumber`) | `StrategicOpportunity.tsx` (stayed) |
|
||||
| Form setup (`Form.useForm()`) | `StrategicOpportunity.tsx` (stayed) |
|
||||
| Shared state (primaryMeasures, filters, rollups) | `StrategicOpportunity.tsx` (stayed) |
|
||||
| Steps 0, 2, 3, 4 validation | `StrategicOpportunity.tsx` (stayed) |
|
||||
| `handleCancel`, `handleSubmit` skeleton | `StrategicOpportunity.tsx` (stayed) |
|
||||
|
||||
---
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Why not just use `if/else` for GL vs CC in the existing orchestrator?**
|
||||
A: The orchestrator would grow to 1000+ lines with interleaved branching. Strategy hooks keep each type's logic cohesive and independently testable.
|
||||
|
||||
**Q: Why hooks instead of classes?**
|
||||
A: React state (`useState`, `useEffect`) only works inside hooks or components. A class-based strategy would need workarounds to manage React state.
|
||||
|
||||
**Q: Does the inactive strategy hook waste memory?**
|
||||
A: Negligible. It holds a few empty arrays and `false` booleans. No API calls fire for the inactive type.
|
||||
|
||||
**Q: Will this break existing CC tests?**
|
||||
A: No. The CC behavior is identical — logic is just relocated. Import paths in tests may need updating if they reference moved files.
|
||||
|
||||
**Q: Can I still debug the CC flow the same way?**
|
||||
A: Yes. Set breakpoints in `useChargeCodeStrategy.ts` for CC-specific logic, or in `StrategicOpportunity.tsx` for wizard navigation. The call chain is just one level deeper for type-specific operations.
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<config>
|
||||
<add key="defaultPushSource" value="https://proget.ops.stratanetwork.net/nuget/nuget" />
|
||||
</config>
|
||||
<packageSources>
|
||||
<add key="NuGet official package source" value="https://api.nuget.org/v3/index.json" />
|
||||
<add key="Strata package source" value="https://proget.ops.stratanetwork.net/nuget/nuget" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
Generated
+1717
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"scripts": {
|
||||
"commit": "commit",
|
||||
"prepare": "husky",
|
||||
"generate-swagger": "node scripts/fetch-swagger.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^19.2.1",
|
||||
"@commitlint/prompt-cli": "^19.2.1",
|
||||
"@commitlint/config-conventional": "^19.1.0",
|
||||
"husky": "^9.0.11"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Generates swagger.json from the API DLL using the Swashbuckle CLI.
|
||||
* Does NOT require the API to be running.
|
||||
*
|
||||
* Prerequisites:
|
||||
* dotnet tool install --global Swashbuckle.AspNetCore.Cli --version 6.8.1
|
||||
* dotnet build src/Strata.ContinuousImprovement.Api/
|
||||
*
|
||||
* Usage: npm run generate-swagger
|
||||
*/
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const ROOT = path.join(__dirname, '..');
|
||||
const DLL = path.join(ROOT, 'src/Strata.ContinuousImprovement.Api/bin/Debug/net8.0/Strata.ContinuousImprovement.Api.dll');
|
||||
const OUTPUT = path.join(ROOT, 'src/Strata.ContinuousImprovement.Web/continuousimprovement/swagger.json');
|
||||
const DOC_NAME = 'v1';
|
||||
|
||||
if (!fs.existsSync(DLL)) {
|
||||
console.log('API DLL not found — building first...');
|
||||
execSync('dotnet build src/Strata.ContinuousImprovement.Api/ --configuration Debug', {
|
||||
cwd: ROOT,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
}
|
||||
|
||||
console.log('Generating swagger.json from API DLL...');
|
||||
execSync(`swagger tofile --output "${OUTPUT}" "${DLL}" ${DOC_NAME}`, {
|
||||
cwd: ROOT,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
const size = fs.statSync(OUTPUT).size;
|
||||
console.log(`swagger.json written to ${OUTPUT} (${(size / 1024).toFixed(1)} KB)`);
|
||||
@@ -0,0 +1,105 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get Octopus Deploy release information for a specific project and environments.
|
||||
|
||||
.DESCRIPTION
|
||||
This script retrieves the latest release information from Octopus Deploy for a specified project across multiple environments
|
||||
and generates a markdown report.
|
||||
|
||||
.PARAMETER yourApiKey
|
||||
The API key for authenticating with the Octopus Deploy server (mandatory).
|
||||
.PARAMETER pr_projectName
|
||||
The name of the Octopus Deploy project (default is "strata.continuousimprovement").
|
||||
.PARAMETER pr_path
|
||||
The path to the git repository (default is the parent directory of the script).
|
||||
|
||||
.EXAMPLE
|
||||
``` powershell
|
||||
.\src\Get_Octo_Information.ps1 -yourApiKey
|
||||
```
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[parameter(Mandatory=$true)] [string]$yourApiKey,
|
||||
[string]$pr_projectName = "strata.continuousimprovement",
|
||||
[string]$pr_path = (Join-Path $PSScriptRoot "..")
|
||||
)
|
||||
|
||||
# Set variables
|
||||
$apiKey = $yourApiKey
|
||||
$octopusUrl = "https://octopus.sdt.local/"
|
||||
$projectName = $pr_projectName # "strata.continuousimprovement"
|
||||
|
||||
#region Classes
|
||||
class octoRelease {
|
||||
[string]$environmentName
|
||||
[string]$release
|
||||
[string]$releaseNotes
|
||||
|
||||
# Default constructor
|
||||
octoRelease() { $this.Init(@{}) }
|
||||
|
||||
# Convenience constructor
|
||||
octoRelease([hashtable]$properties) { $this.Init($properties) }
|
||||
|
||||
octoRelease([string]$envName, [string]$rels, [string]$notes)
|
||||
{
|
||||
$this.Init(@{ environmentName = $envName; release = $rels; releaseNotes = $notes })
|
||||
}
|
||||
|
||||
# Shared initializer method
|
||||
[void] Init([hashtable]$Properties) {
|
||||
foreach ($Property in $Properties.Keys) {
|
||||
$this.$Property = $Properties.$Property
|
||||
}
|
||||
#$this.end = $this.range[-1];
|
||||
}
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
# Set headers
|
||||
$headers = @{
|
||||
"X-Octopus-ApiKey" = $apiKey
|
||||
}
|
||||
|
||||
$octoReleaseList = @()
|
||||
@("Dev","QA","Prod") | ForEach-Object {
|
||||
$environmentName = $_
|
||||
# Get project ID
|
||||
$projectResponse = Invoke-RestMethod -Uri "$octopusUrl/api/projects/all" -Headers $headers
|
||||
$project = $projectResponse | Where-Object { $_.Name -eq $projectName }
|
||||
$projectId = $project.Id
|
||||
|
||||
# Get environment ID
|
||||
$envResponse = Invoke-RestMethod -Uri "$octopusUrl/api/environments" -Headers $headers
|
||||
$environment = $envResponse.Items | Where-Object { $_.Name -eq $environmentName }
|
||||
$environmentId = $environment.Id
|
||||
# Get latest deployment
|
||||
$deploymentResponse = Invoke-RestMethod -Uri "$octopusUrl/api/deployments?projects=$projectId&environments=$environmentId&take=1&order=desc" -Headers $headers
|
||||
$latestDeployment = $deploymentResponse.Items[0]
|
||||
$releaseId = $latestDeployment.ReleaseId
|
||||
|
||||
# Get release details
|
||||
$releaseDetails = Invoke-RestMethod -Uri "$octopusUrl/api/releases/$releaseId" -Headers $headers
|
||||
|
||||
# Output results
|
||||
$release = $releaseDetails.Version
|
||||
$releaseNotes = $releaseDetails.ReleaseNotes
|
||||
$octoReleaseList += [octoRelease]::new($environmentName, $release, $releaseNotes)
|
||||
#Write-Host $releaseDetails
|
||||
}
|
||||
|
||||
$filePath = Join-Path $pr_path "/Octo_Release$((Get-Date).ToString("yyyyMMdd")).md"
|
||||
$report = "# " + $projectName +" Octo Releases " + (Get-Date).ToString("yyyy-MM-dd")+ "`n## Details`n"
|
||||
|
||||
$report += $octoReleaseList | `
|
||||
Format-Table @{name="environment"; Expression={$_.environmentName}}, @{Name="| release"; Expression={"| " + $_.release}}, @{Name="| notes"; Expression={"| " + $_.releaseNotes}} -AutoSize | `
|
||||
Out-String
|
||||
|
||||
$report = $report -replace "--\s+--", "-- | --"
|
||||
Write-Output $report.Replace("- | -", "- + -")
|
||||
|
||||
Set-Content -Path $filePath -Value $report
|
||||
|
||||
Write-Host -ForegroundColor DarkRed $filePath
|
||||
@@ -0,0 +1,241 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get git tags and messages from multiple repositories.
|
||||
|
||||
.DESCRIPTION
|
||||
This script retrieves git tags and their associated messages from multiple repositories and generates a markdown report.
|
||||
|
||||
.PARAMETER pr_patch
|
||||
The patch version number to start from (default is 0).
|
||||
.PARAMETER pr_minor_start
|
||||
The starting minor version number (mandatory).
|
||||
.PARAMETER pr_minor_end
|
||||
The ending minor version number (mandatory).
|
||||
.PARAMETER pr_tag
|
||||
The tag string to filter by (default is "- CI -").
|
||||
.PARAMETER pr_path
|
||||
The path to the git repository (default is the parent directory of the script).
|
||||
.PARAMETER pr_giturl
|
||||
The base URL for the git repository (default is "https://github.com/stratadecision/continuousimprovement/pull/").
|
||||
.PARAMETER pr_codeFreeze
|
||||
The code freeze version string (optional).
|
||||
.PARAMETER pr_qaRelease
|
||||
The QA release version string (optional).
|
||||
.PARAMETER pr_withPRLinks
|
||||
Boolean indicating whether to include PR links in the report (default is false).
|
||||
|
||||
.EXAMPLE
|
||||
``` powershell
|
||||
.\src\Get_Tags_and_messages.ps1 -pr_patch 0 -pr_minor_start 587 -pr_minor_end 630 -pr_codeFreeze "0.30.4" -pr_qaRelease "0.30.4"
|
||||
```
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[int]$pr_patch,
|
||||
[parameter(Mandatory=$true)] [int]$pr_minor_start,
|
||||
[parameter(Mandatory=$true)] [int]$pr_minor_end,
|
||||
[string]$pr_tag = "- CI -",
|
||||
[string]$pr_path = (Join-Path $PSScriptRoot ".."),
|
||||
[string]$pr_giturl = "https://github.com/stratadecision/continuousimprovement/pull/",
|
||||
[string]$pr_codeFreeze,
|
||||
[string]$pr_qaRelease,
|
||||
[bool]$pr_withPRLinks = $false
|
||||
)
|
||||
|
||||
if ($pr_minor_start -eq 0) { $pr_minor_start = 587 }
|
||||
if ($pr_minor_end -eq 0) { $pr_minor_end = 630 }
|
||||
|
||||
#region Classes
|
||||
class gitGroup {
|
||||
[string]$tag
|
||||
[string]$path
|
||||
[int]$idx
|
||||
[int[]]$range
|
||||
[string]$codeFreeze
|
||||
[string]$qaRelease
|
||||
[string]$docName
|
||||
[int]$end
|
||||
|
||||
# Default constructor
|
||||
gitGroup() { $this.Init(@{}) }
|
||||
|
||||
# Convenience constructor
|
||||
gitGroup([hashtable]$properties) { $this.Init($properties) }
|
||||
|
||||
gitGroup([string]$pth, [int]$id, [int[]]$rng)
|
||||
{
|
||||
$this.Init(@{ path = $pth; idx = $id; range = $rng})
|
||||
}
|
||||
|
||||
# Shared initializer method
|
||||
[void] Init([hashtable]$Properties) {
|
||||
foreach ($Property in $Properties.Keys) {
|
||||
$this.$Property = $Properties.$Property
|
||||
}
|
||||
$this.end = $this.range[-1];
|
||||
}
|
||||
|
||||
updateEnd([int]$end)
|
||||
{
|
||||
$this.end = $end;
|
||||
}
|
||||
}
|
||||
|
||||
class gitInfo {
|
||||
[string]$Release
|
||||
[string]$PR
|
||||
[string]$Team
|
||||
[string]$Name
|
||||
[int]$Feature
|
||||
[int]$Bugfix
|
||||
[string]$Pipe
|
||||
|
||||
# Default constructor
|
||||
gitInfo() { $this.Init(@{}) }
|
||||
# Convenience constructor from hashtable
|
||||
gitInfo([hashtable]$Properties) { $this.Init($Properties) }
|
||||
|
||||
gitInfo([string]$id, [string]$pr, [string]$name, [string]$team)
|
||||
{
|
||||
$this.Init(@{ Release = $id; PR = $pr; Name = $name; Team = $team; })
|
||||
}
|
||||
|
||||
gitInfo([string]$vars, [int]$feature, [int]$bugfix, [bool]$withPRLink, [string]$giturl)
|
||||
{
|
||||
try{
|
||||
if ($vars -match '(?s)^([\d.]*)\s{2,}(.*)$')
|
||||
{
|
||||
$vars = $vars -creplace '(?s)^([\d.]*)\s{2,}(.*)$', '$1|||$2'
|
||||
}
|
||||
$vars += "||||"
|
||||
Write-Output $vars
|
||||
$vars
|
||||
$arr = $vars.Split("|")
|
||||
$arr | ForEach-Object { Write-Output '{0}' -f $_ }
|
||||
$arr
|
||||
$prTag = $arr[1];
|
||||
if (![string]::IsNullOrEmpty($prTag) -and $withPRLink) {
|
||||
$prTag = "[{0}]({1}{2})" -f $arr[1], $giturl, $arr[1];
|
||||
}
|
||||
$this.Init(@{
|
||||
Release = $arr[0];
|
||||
PR = $prTag;
|
||||
Team = if ($arr[2].Trim() -eq 'snowflake') { 'the-outliers' } else { $arr[2] };
|
||||
Name = $arr[3];
|
||||
Feature = $feature;
|
||||
Bugfix = $bugfix;
|
||||
Pipe = "|";
|
||||
})
|
||||
} catch {
|
||||
$msg = "`n`nInput ERROR`n`n{0}" -f $_.Exception.Message
|
||||
Write-Host -ForegroundColor Red $msg
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Shared initializer method
|
||||
[void] Init([hashtable]$Properties) {
|
||||
foreach ($Property in $Properties.Keys) {
|
||||
if ($Properties.$Property -is [string]) {
|
||||
$this.$Property = $Properties.$Property.Trim()
|
||||
} else {
|
||||
$this.$Property = $Properties.$Property
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
$date = Get-Date
|
||||
$dateTag = $date.ToString('yyyyMMdd')
|
||||
$gitGroups = @(
|
||||
[gitGroup]::new(@{
|
||||
tag = $pr_tag # "- CI -"
|
||||
path = $pr_path # "C:\Git\continuousimprovement"
|
||||
idx = $pr_patch # 7
|
||||
range = $pr_minor_start..$pr_minor_end # 587..630
|
||||
codeFreeze = $pr_codeFreeze # "0.614.0"
|
||||
qaRelease = $pr_qaRelease # "0.602.1"
|
||||
docName = "PR_Report_"+$dateTag+".md"
|
||||
})
|
||||
)
|
||||
|
||||
$gitGroups | `
|
||||
#Sort-Object -Property Team, Release | `
|
||||
Sort-Object -Property path | `
|
||||
Format-Table tag, path, range, idx, codeFreeze -Autosize
|
||||
|
||||
$regExp = '^([\d.]*)[^#]*#([\d]*).*\/(.*)\/(.*)$'
|
||||
|
||||
$currDir = (Get-Location).Path
|
||||
$gitGroups | ForEach-Object {
|
||||
Clear-Host
|
||||
$gitGrp = $_
|
||||
$_ | `
|
||||
Sort-Object -Property path | `
|
||||
Format-Table tag, path, range, idx, codeFreeze -Autosize
|
||||
|
||||
$path = $_.path
|
||||
Set-Location $path
|
||||
$idx = $_.idx
|
||||
$range = $_.range
|
||||
$codeFreeze = if ($_.codeFreeze -ne "") {
|
||||
"Code Freeze: {0}\`n" -f $_.codeFreeze
|
||||
} else {""};
|
||||
Write-Host $codeFreeze.Replace("\`n", "`n")
|
||||
$qaRelease = $_.qaRelease
|
||||
$end = $_.end
|
||||
|
||||
$gitList = @()
|
||||
|
||||
$range | ForEach-Object {
|
||||
$feature = $_
|
||||
$idx..9 | ForEach-Object {
|
||||
$bugfix = $_
|
||||
$x = (git tag 0.$feature.$bugfix -n)
|
||||
if ($x -ne $null -and $x -ne "")
|
||||
{
|
||||
$vars = $x -replace $regExp,'$1|$2|$3|$4|'
|
||||
$arr = $vars.Split('|')
|
||||
if ($arr.Length -lt 5)
|
||||
{
|
||||
$vars = $x -replace '^([\d.]*)\s*(JAZZ.*)$','$1|||$2|'
|
||||
}
|
||||
if ($vars -ne $null)
|
||||
{
|
||||
$gitList += [gitInfo]::new($vars, $feature, $bugfix, $pr_withPRLinks, $pr_giturl)
|
||||
}
|
||||
$end = $feature
|
||||
$gitGrp.updateEnd($end)
|
||||
}
|
||||
}
|
||||
$idx = 0
|
||||
}
|
||||
|
||||
$myDocuments = [Environment]::GetFolderPath("MyDocuments")
|
||||
$tempText = Join-Path $myDocuments $_.docName
|
||||
$tempText = Join-Path "./" $_.docName
|
||||
$report = "# " + $_.tag +" " + (Get-Date).ToString("yyyy-MM-dd")+ "`n## Summary`nFrom: {0} -> {1} ({2}){5}\`n{3}QA Release: {4}`n## Details" -f `
|
||||
$range[0], `
|
||||
$end, `
|
||||
($gitList | Measure-Object -Property Release).Count, `
|
||||
$codeFreeze, $qaRelease,
|
||||
($pr_withPRLinks ? "\`n*PR links included*" : "")
|
||||
|
||||
$report += $gitList | `
|
||||
Sort-Object -Property Feature, Bugfix | `
|
||||
Format-Table Release, @{Name="| PR"; Expression={"| " + $_.PR}}, @{Name="| Name"; Expression={"| " + $_.Name}} -Autosize | Out-String
|
||||
|
||||
# Clean up the report formatting for markdown
|
||||
while ($report -match '--\s+--') {
|
||||
$report = $report -replace '--\s+--', '-- | --' # Replace spaces with pipe
|
||||
}
|
||||
Set-Content -Path $tempText -Value $report
|
||||
|
||||
$report = $report.Replace('-- | --','-- + --') # Replace pipe with plus
|
||||
Write-Output $report
|
||||
|
||||
Write-Host -ForegroundColor DarkRed $tempText
|
||||
}
|
||||
|
||||
Set-Location $currDir
|
||||
@@ -0,0 +1,49 @@
|
||||
using ClosedXML.Excel;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api
|
||||
{
|
||||
public class ExcelContentResult : ActionResult
|
||||
{
|
||||
public IXLWorkbook Workbook { get; set; }
|
||||
public string FileName { get; set; }
|
||||
|
||||
public ExcelContentResult()
|
||||
{
|
||||
}
|
||||
|
||||
public ExcelContentResult(IXLWorkbook workbook, string fileName = "Export") : base()
|
||||
{
|
||||
Workbook = workbook;
|
||||
FileName = fileName;
|
||||
}
|
||||
|
||||
public override void ExecuteResult(ActionContext context)
|
||||
{
|
||||
GetFileContentResult().ExecuteResult(context);
|
||||
}
|
||||
|
||||
public override Task ExecuteResultAsync(ActionContext context)
|
||||
{
|
||||
return GetFileContentResult().ExecuteResultAsync(context);
|
||||
}
|
||||
|
||||
protected FileContentResult GetFileContentResult()
|
||||
{
|
||||
// Flush the workbook to the Response.OutputStream
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
Workbook.SaveAs(memoryStream);
|
||||
|
||||
return new FileContentResult(memoryStream.ToArray(),
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
{
|
||||
FileDownloadName = $"{FileName}_{DateTime.UtcNow:MM-dd-yyyy}.xlsx"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using System;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to disable model binding to prevent buffering the entire request body before entering the controller action.
|
||||
/// We need this to stream the file as it is being uploaded to the server.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
|
||||
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
|
||||
{
|
||||
public void OnResourceExecuting(ResourceExecutingContext context)
|
||||
{
|
||||
var factories = context.ValueProviderFactories;
|
||||
factories.RemoveType<FormValueProviderFactory>();
|
||||
factories.RemoveType<FormFileValueProviderFactory>();
|
||||
factories.RemoveType<JQueryFormValueProviderFactory>();
|
||||
}
|
||||
|
||||
public void OnResourceExecuted(ResourceExecutedContext context)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Strata.ApiLib.Standard.Models;
|
||||
using Strata.ContinuousImprovement.Api.Generics;
|
||||
using Strata.ContinuousImprovement.Biz.All.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.All.Opportunities.Filters;
|
||||
using Strata.ContinuousImprovement.Biz.Generics;
|
||||
using Strata.ContinuousImprovement.Biz.Pagination;
|
||||
using Strata.ContinuousImprovement.Biz.Utilities;
|
||||
using Strata.CoreLib.Claims;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using Strata.FeatureFlags.Client;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class AllOpportunitiesController : OpportunityController<AllOpportunity>
|
||||
{
|
||||
private readonly IFeatureFlagServiceClient _featureFlagServiceClient;
|
||||
private readonly ILogger<AllOpportunitiesController> _logger;
|
||||
private readonly ClaimsPrincipal _user;
|
||||
|
||||
public AllOpportunitiesController(IAllOpportunityService opportunityService,
|
||||
IClaimsPrincipalAccessor claimsPrincipalAccessor,
|
||||
IFeatureFlagServiceClient featureFlagServiceClient,
|
||||
ILogger<AllOpportunitiesController> logger)
|
||||
: base(opportunityService)
|
||||
{
|
||||
_featureFlagServiceClient = featureFlagServiceClient;
|
||||
_logger = logger;
|
||||
_user = claimsPrincipalAccessor.GetCurrentClaimsPrincipal();
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
public async Task<PagedApiResponse<AllOpportunity>> GetOpportunities(AllOpportunityLoadOptions loadOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
var databaseGuid = _user.GetDatabaseGuid();
|
||||
var hasFlexibleExplorartion = _user.IsSdtEmployee()
|
||||
|| (await _featureFlagServiceClient.IsEnabledAsync("ciflexibleexplorationenabled", databaseGuid, cancellationToken: cancellationToken));
|
||||
var dict = new Dictionary<string, object> { { "isSdtEmployee", _user.IsSdtEmployee() }, { "hasFlexibleExploration", hasFlexibleExplorartion } };
|
||||
_logger.LogServiceInformation("CI Flexible Exploration Enabled", dict);
|
||||
loadOptions.Filters.HasFlexibleExploration = hasFlexibleExplorartion;
|
||||
return await OpportunityService.GetPagedAsync(loadOptions.PagingOptions, loadOptions.Filters, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
public async Task<bool> IsNameValid(ValidateName name, CancellationToken cancellationToken)
|
||||
{
|
||||
return await ((IAllOpportunityService)OpportunityService).NameIsValid(name.ConfigurationGuid, name.OpportunityType, name.Name, name?.OpportunityGuid, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
public async Task<IActionResult> Export(AllOpportunityExportOptions exportOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
var workbook = await ((IAllOpportunityService)OpportunityService).ExportAsync(exportOptions.SortOptions, exportOptions.Filters, cancellationToken);
|
||||
return new ExcelContentResult(workbook, "EN_Opportunities");
|
||||
|
||||
}
|
||||
|
||||
[HttpGet("{configurationGuid}/[action]")]
|
||||
public async Task<AllFilterOptions> GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
var isStrataUser = _user.IsSdtEmployee();
|
||||
var filterOptions = await ((IAllOpportunityService)OpportunityService).GetFilterOptionsAsync(configurationGuid, cancellationToken);
|
||||
if (!isStrataUser)
|
||||
{
|
||||
filterOptions.OpportunityTypes = filterOptions.OpportunityTypes.Where(type => Int32.Parse(type.Id) != (int)OpportunityTypes.Blank);
|
||||
}
|
||||
return filterOptions;
|
||||
}
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class ValidateName
|
||||
{
|
||||
public Guid ConfigurationGuid { get; set; }
|
||||
public int OpportunityType { get; set; }
|
||||
public string Name { get; set; }
|
||||
public Guid? OpportunityGuid { get; set; }
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class AllOpportunityLoadOptions
|
||||
{
|
||||
public PagingOptions PagingOptions { get; set; }
|
||||
public AllFilters Filters { get; set; }
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class AllOpportunityExportOptions
|
||||
{
|
||||
public SortOptions SortOptions { get; set; }
|
||||
public AllFilters Filters { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Biz.BackgroundClientJob;
|
||||
using System.Threading;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class BackgroundClientJobController(IBackgroundClientJobService backgroundClientJobService) : ControllerBase
|
||||
{
|
||||
[HttpGet("{jobId}/status")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public ActionResult<string> GetJobStatus(string jobId, CancellationToken cancellationToken)
|
||||
{
|
||||
var status = backgroundClientJobService.GetJobStatus(jobId, cancellationToken);
|
||||
if (status == "NotFound")
|
||||
{
|
||||
return NotFound(status);
|
||||
}
|
||||
return Ok(status);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Biz.ChargeCode.Initiatives;
|
||||
using Strata.ContinuousImprovement.Biz.Initiatives;
|
||||
using Strata.ContinuousImprovement.Biz.Mappers;
|
||||
using Strata.CoreLib.Claims;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class ChargeCodeInitiativesController : ControllerBase
|
||||
{
|
||||
private readonly IClaimsPrincipalAccessor _claimsPrincipalAccessor;
|
||||
private readonly ICCInitiativeService _chargeCodeInitiativeService;
|
||||
private readonly IStakeholderUser _stakeholderUser;
|
||||
|
||||
public ChargeCodeInitiativesController(
|
||||
IClaimsPrincipalAccessor claimsPrincipalAccessor,
|
||||
ICCInitiativeService chargeCodeInitiativeService,
|
||||
IStakeholderUser stakeholderUser)
|
||||
{
|
||||
_claimsPrincipalAccessor = claimsPrincipalAccessor;
|
||||
_chargeCodeInitiativeService = chargeCodeInitiativeService;
|
||||
_stakeholderUser = stakeholderUser;
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<Guid>> Create(InitiativeDto initiative, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = _claimsPrincipalAccessor.GetCurrentClaimsPrincipal();
|
||||
var newInitiative = initiative.MapToInitiative(_stakeholderUser);
|
||||
var dbGuid = user.GetDatabaseGuid();
|
||||
return await _chargeCodeInitiativeService.CreateChargeCodeInitiative(dbGuid, newInitiative, initiative.OpportunityGuid, cancellationToken);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Guid.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+264
@@ -0,0 +1,264 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Api.Generics;
|
||||
using Strata.ContinuousImprovement.Biz.All.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.ChargeCode.Initiatives;
|
||||
using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.Configuration;
|
||||
using Strata.ContinuousImprovement.Biz.Dtos;
|
||||
using Strata.ContinuousImprovement.Biz.Encounter.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.Generics;
|
||||
using Strata.ContinuousImprovement.Biz.Initiatives;
|
||||
using Strata.ContinuousImprovement.Biz.OrgDefined;
|
||||
using Strata.ContinuousImprovement.Biz.OrgDefined.Queries;
|
||||
using Strata.ContinuousImprovement.Biz.Shared;
|
||||
using Strata.CoreLib.Claims;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using Strata.FeatureFlags.Client;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class ChargeCodeOpportunitiesController : OpportunityController<CCOpportunityDataModel>
|
||||
{
|
||||
private readonly IClaimsPrincipalAccessor _claimsPrincipalAccessor;
|
||||
private readonly IConfigurationService _configurationService;
|
||||
private readonly ICCOpportunityService _opportunityService;
|
||||
private readonly ICCInitiativeService _initiativeService;
|
||||
private readonly IOpportunityMetricCohortService _opportunityMetricCohortService;
|
||||
private readonly IFeatureFlagServiceClient _featureFlagServiceClient;
|
||||
private readonly IAllOpportunityService _allOpportunityService;
|
||||
|
||||
public ChargeCodeOpportunitiesController(
|
||||
IClaimsPrincipalAccessor claimsPrincipalAccessor,
|
||||
IConfigurationService configurationService,
|
||||
ICCOpportunityService opportunityService,
|
||||
ICCInitiativeService initiativeService,
|
||||
IOpportunityMetricCohortService opportunityMetricCohortService,
|
||||
IFeatureFlagServiceClient featureFlagServiceClient,
|
||||
IAllOpportunityService allOpportunityService) : base(opportunityService)
|
||||
{
|
||||
_configurationService = configurationService;
|
||||
_claimsPrincipalAccessor = claimsPrincipalAccessor;
|
||||
_opportunityService = opportunityService;
|
||||
_initiativeService = initiativeService;
|
||||
_opportunityMetricCohortService = opportunityMetricCohortService;
|
||||
_featureFlagServiceClient = featureFlagServiceClient;
|
||||
_allOpportunityService = allOpportunityService;
|
||||
}
|
||||
|
||||
[HttpPut("{opportunityGuid}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<Guid>> UpdateOpportunity(Guid opportunityGuid, [FromBody] CCOpportunitySaveModel opportunity, CancellationToken cancellationToken)
|
||||
{
|
||||
var isNameValid = await _allOpportunityService.NameIsValid(opportunity.ConfigurationGuid, (int)OpportunityTypes.ChargeCode, opportunity.Name,
|
||||
opportunity?.OpportunityGuid, cancellationToken);
|
||||
if (!isNameValid)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
var user = _claimsPrincipalAccessor.GetCurrentClaimsPrincipal();
|
||||
var updatedOpportunity = opportunity.MapToDataModel();
|
||||
var dbGuid = user?.GetDatabaseGuid() ?? Guid.Empty;
|
||||
var result = await _opportunityService.UpdateAsync(dbGuid, updatedOpportunity, cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<bool>> ValidateChargeCodesAsync([FromBody] ValidateChargeCodeDto dto, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _opportunityService.ValidateChargeCodesAsync(dto.ChargeCodes, cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<ChargeCodePickerModel>>> GetChargeCodeMembers([FromBody] List<string> hPaths, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _opportunityService.GetChargeCodePickerModelMembers(hPaths, cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<bool>> ValidateOpportunityLevelsAsync([FromBody] OpportunityLevelSelectionInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _opportunityService.ValidateOpportunityLevelsAsync(info.OpportunityLevel, info.OpportunityLevels, info.Configuration, cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpDelete("{id}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<bool> Delete(Guid id, CancellationToken cancellationToken)
|
||||
=> await OpportunityService.DeleteAsync<CCOpportunityDataModel>(id, cancellationToken);
|
||||
|
||||
[HttpGet("{opportunityGuid}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<CCOpportunityDto>> GetById(Guid opportunityGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
var opportunity = await _opportunityService.GetById(opportunityGuid, cancellationToken);
|
||||
if (opportunity == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
var opportunityDto = opportunity.MapToDto();
|
||||
return Ok(opportunityDto);
|
||||
}
|
||||
|
||||
[HttpGet("{opportunityGuid}/[action]")]
|
||||
public async Task<CCOpportunitySaveModel> GetEditModel(Guid opportunityGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
var opportunity = await _opportunityService.GetById(opportunityGuid, cancellationToken);
|
||||
return opportunity.MapToSaveModel();
|
||||
}
|
||||
|
||||
[HttpPut("{opportunityGuid}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<bool> UpdateGoalTotalUnitsOfService(Guid opportunityGuid, [FromBody] GoalSetDto goalSetDto, CancellationToken cancellationToken)
|
||||
{
|
||||
var hasUpdatedUOS = await _opportunityService.UpdateGoalTotalUnitsOfServiceAsync(opportunityGuid, goalSetDto.Key, goalSetDto.GoalValue, cancellationToken);
|
||||
if (hasUpdatedUOS)
|
||||
{
|
||||
return await _initiativeService.UpdateSavings(opportunityGuid, cancellationToken);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[HttpPut("{opportunityGuid}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<bool> UpdateGoalCostPerUnit(Guid opportunityGuid, [FromBody] GoalSetDto goalSetDto, CancellationToken cancellationToken)
|
||||
{
|
||||
var hasUpdatedCostPerUnit = await _opportunityService.UpdateGoalCostPerUnitAsync(opportunityGuid, goalSetDto.Key, goalSetDto.GoalValue, cancellationToken);
|
||||
if (hasUpdatedCostPerUnit)
|
||||
{
|
||||
return await _initiativeService.UpdateSavings(opportunityGuid, cancellationToken);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[HttpPut("{opportunityGuid}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<bool>> UpdateGoals(Guid opportunityGuid, [FromBody] IEnumerable<ChargeCodeOpportunityDetailChanges> changes, CancellationToken cancellationToken)
|
||||
{
|
||||
var existsOpportunity = await _opportunityService.Contains(opportunityGuid, cancellationToken);
|
||||
if (!existsOpportunity)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var hasUpdatedCostPerUnit = await _opportunityService.UpdateGoalsAsync(opportunityGuid, changes, cancellationToken);
|
||||
|
||||
if (hasUpdatedCostPerUnit)
|
||||
{
|
||||
return await _initiativeService.UpdateSavings(opportunityGuid, cancellationToken);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[HttpPost("{opportunityGuid}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status202Accepted)]
|
||||
public async Task<IActionResult> Export(
|
||||
[FromRoute] Guid opportunityGuid,
|
||||
[FromBody] ChargeCodeItems chargeCodes, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = _claimsPrincipalAccessor.GetCurrentClaimsPrincipal();
|
||||
var workbook = await _opportunityService.ExportAsync(opportunityGuid, chargeCodes.ChargeCodes, user.Identity?.Name, cancellationToken);
|
||||
return new ExcelContentResult(workbook, "ChargeCodeTemplate");
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public async Task<bool> OpportunityCreateEnabled(CancellationToken cancellationToken)
|
||||
{
|
||||
var user = _claimsPrincipalAccessor.GetCurrentClaimsPrincipal();
|
||||
return await _featureFlagServiceClient.IsEnabledAsync("chargecodebeta", user.GetDatabaseGuid(), cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public class GoalSetDto
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public double GoalValue { get; set; }
|
||||
}
|
||||
|
||||
[HttpGet("{opportunityGuid}/[action]")]
|
||||
public async Task<InitiativeInfo> GetInitiativeInfo(Guid opportunityGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _opportunityService.GetInitiativeInfoAsync(opportunityGuid, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
public async Task<string> Refresh(RefreshData refreshData, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _opportunityService.RefreshOpportunityTrackingData(refreshData.DatabaseGuid, refreshData.Guid, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<OpportunityMetricCohortData>>> OpportunityMetricCohortData([FromBody] OpportunityMetricCohortOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
var config = await _configurationService.GetConfigurationAsync(options.ConfigurationGuid, cancellationToken);
|
||||
if (config == null)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
var result = Enumerable.Empty<OpportunityMetricCohortData>();
|
||||
result = await _opportunityMetricCohortService.GetChargeCodeOpportunityMetricCohortData(config, options.Data, options.InnerJoin, cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
public class RefreshData
|
||||
{
|
||||
public Guid DatabaseGuid { get; set; }
|
||||
public Guid Guid { get; set; }
|
||||
}
|
||||
|
||||
public class ChargeCodeItems
|
||||
{
|
||||
public IEnumerable<string> ChargeCodes { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class OpportunityLevelSelectionInfo
|
||||
{
|
||||
public OpportunityLevel OpportunityLevel { get; set; }
|
||||
public List<string> OpportunityLevels { get; set; }
|
||||
public Guid Configuration { get; set; }
|
||||
}
|
||||
|
||||
public class ValidateChargeCodeDto
|
||||
{
|
||||
public List<string> ChargeCodes { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
using Hangfire;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Strata.ContinuousImprovement.Biz.ChargeCode.Jobs;
|
||||
using Strata.ContinuousImprovement.Biz.Utilities;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class ChargeCodeOpportunityDetailsController : ControllerBase
|
||||
{
|
||||
private readonly IBackgroundJobClient _backgroundJobClient;
|
||||
private readonly ICCOpportunityClientJob _ccOpportunityClientJob;
|
||||
private readonly ILogger<ChargeCodeOpportunityDetailsController> _logger;
|
||||
|
||||
public ChargeCodeOpportunityDetailsController(IBackgroundJobClient backgroundJobClient, ICCOpportunityClientJob ccOpportunityClientJob, ILogger<ChargeCodeOpportunityDetailsController> logger)
|
||||
{
|
||||
_backgroundJobClient = backgroundJobClient;
|
||||
_ccOpportunityClientJob = ccOpportunityClientJob;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[Obsolete("Replacing w/ explicitly named endpoints")]
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public void Create([FromBody] ChargeCodeDetailInfo detailInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
_backgroundJobClient.Enqueue<CCOpportunityDetailJob>(job
|
||||
=> job.PopulateOpportunityDetailsAsync(detailInfo.StrataId, string.Empty, detailInfo.OpportunityGuid, detailInfo.IsBaseline, detailInfo.IsUpdate, false, cancellationToken));
|
||||
}
|
||||
|
||||
|
||||
[HttpPost("refresh-baseline-details")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> RefreshBaselineDetails(
|
||||
[FromBody] ChargeCodeOpportunityJobRequestInfo request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var jobId = await _ccOpportunityClientJob.RefreshClientOpportunityBaselineDetails(
|
||||
request.StrataId, request.OpportunityGuid, cancellationToken);
|
||||
return Ok(jobId);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.LogServiceError(ex);
|
||||
return BadRequest();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("refresh-tracking-details")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> RefreshTrackingDetails(
|
||||
[FromBody] ChargeCodeOpportunityJobRequestInfo request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var jobId = await _ccOpportunityClientJob.RefreshClientOpportunityTrackingDetails(
|
||||
request.StrataId, request.OpportunityGuid, cancellationToken);
|
||||
return Ok(jobId);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.LogServiceError(ex);
|
||||
return BadRequest();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains information required to request a charge code opportunity job.
|
||||
/// </summary>
|
||||
public class ChargeCodeOpportunityJobRequestInfo
|
||||
{
|
||||
public int StrataId { get; set; }
|
||||
public Guid OpportunityGuid { get; set; }
|
||||
}
|
||||
|
||||
public class ChargeCodeDetailInfo
|
||||
{
|
||||
public int StrataId { get; set; }
|
||||
public Guid OpportunityGuid { get; set; }
|
||||
public bool IsBaseline { get; set; }
|
||||
public bool IsUpdate { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ApiLib.Standard.Models;
|
||||
using Strata.ContinuousImprovement.Api.Generics;
|
||||
using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters;
|
||||
using Strata.ContinuousImprovement.Biz.Pagination;
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class ChargeCodeOppsController : OpportunityController<CCOpportunity>
|
||||
{
|
||||
|
||||
public ChargeCodeOppsController(ICCOpportunityViewService opportunityService) : base(opportunityService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
public async Task<PagedApiResponse<CCOpportunity>> GetOpportunities(CCOpportunityLoadOptions loadOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
return await OpportunityService.GetPagedAsync(loadOptions.PagingOptions, loadOptions.Filters, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
public async Task<IActionResult> Export(CCOpportunityExportOptions exportOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
var workbook = await ((ICCOpportunityViewService)OpportunityService).ExportAsync(exportOptions.SortOptions, exportOptions.Filters, cancellationToken);
|
||||
return new ExcelContentResult(workbook, "CC_Opportunities");
|
||||
|
||||
}
|
||||
|
||||
[HttpGet("{configurationGuid}/[action]")]
|
||||
public async Task<CCFilterOptions> GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
return await ((ICCOpportunityViewService)OpportunityService).GetFilterOptionsAsync(configurationGuid, cancellationToken);
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class CCOpportunityLoadOptions
|
||||
{
|
||||
public PagingOptions PagingOptions { get; set; }
|
||||
public CCFilters Filters { get; set; }
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class CCOpportunityExportOptions
|
||||
{
|
||||
public SortOptions SortOptions { get; set; }
|
||||
public CCFilters Filters { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Strata.ApiLib.Standard.Models;
|
||||
using Strata.ContinuousImprovement.Biz.Dtos;
|
||||
using Strata.ContinuousImprovement.Biz.Network;
|
||||
using Strata.ContinuousImprovement.Biz.Network.Filters;
|
||||
using Strata.ContinuousImprovement.Biz.Shared;
|
||||
using Strata.ContinuousImprovement.Biz.Utilities;
|
||||
using Strata.CoreLib.Claims;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class ClientNetworkOpportunitiesController : ControllerBase
|
||||
{
|
||||
private readonly IClientNetworkOpportunityService _clientNetworkOpportunityService;
|
||||
private readonly ILogger<ClientNetworkOpportunitiesController> _logger;
|
||||
private readonly ClaimsPrincipal _user;
|
||||
|
||||
public ClientNetworkOpportunitiesController(
|
||||
IClaimsPrincipalAccessor claimsPrincipalAccessor,
|
||||
IClientNetworkOpportunityService clientNetworkOpportunityService,
|
||||
ILogger<ClientNetworkOpportunitiesController> logger)
|
||||
{
|
||||
_clientNetworkOpportunityService = clientNetworkOpportunityService;
|
||||
_logger = logger;
|
||||
_user = claimsPrincipalAccessor.GetCurrentClaimsPrincipal();
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<PagedApiResponse<NetworkOpportunitySummary>>> GetOpportunities(NetworkOpportunityLoadOptions loadOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
var databaseGuid = _user.GetDatabaseGuid();
|
||||
if (databaseGuid != Guid.Empty)
|
||||
{
|
||||
return await _clientNetworkOpportunityService.GetOpportunitiesAsync(databaseGuid, loadOptions.PagingOptions, loadOptions.Filters, cancellationToken);
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpGet("{networkOpportunityId}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<ClientNetworkOpportunityDto>> GetById(int networkOpportunityId, CancellationToken cancellationToken)
|
||||
{
|
||||
var databaseGuid = _user.GetDatabaseGuid();
|
||||
if (databaseGuid != Guid.Empty)
|
||||
{
|
||||
try
|
||||
{
|
||||
var clientNetworkOpportunity = await _clientNetworkOpportunityService.GetByIdAsync(databaseGuid, networkOpportunityId, cancellationToken);
|
||||
return new ClientNetworkOpportunityDto(clientNetworkOpportunity);
|
||||
}
|
||||
catch (CreateWorkbookFromTemplateException ex)
|
||||
{
|
||||
_logger.LogServiceError(ex);
|
||||
return StatusCode(500, new { message = "Error creating client workbook. Verify template data source is a published standard data source with client permissions set." });
|
||||
}
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPut("{networkOpportunityId}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<IActionResult> Save(int networkOpportunityId, ClientNetworkOpportunityDto dto, CancellationToken cancellationToken)
|
||||
{
|
||||
if (networkOpportunityId != dto.NetworkOpportunityId)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
var databaseGuid = _user.GetDatabaseGuid();
|
||||
if (databaseGuid != Guid.Empty)
|
||||
{
|
||||
ClientNetworkOpportunity clientOpportunity = dto.MappedtoClientOpportunity();
|
||||
NetworkOpportunitySaveModel networkOpportunity = dto.MappedtoNetworkOpportunitySaveModel();
|
||||
await _clientNetworkOpportunityService.ConfigureAsync(databaseGuid, networkOpportunityId, clientOpportunity, networkOpportunity, cancellationToken);
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpDelete("{networkOpportunityId}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<IActionResult> Delete(int networkOpportunityId, CancellationToken cancellationToken)
|
||||
{
|
||||
var databaseGuid = _user.GetDatabaseGuid();
|
||||
if (databaseGuid != Guid.Empty)
|
||||
{
|
||||
await _clientNetworkOpportunityService.DeleteAsync(databaseGuid, networkOpportunityId, CancellationToken.None);
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPut("{networkOpportunityId}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<bool>> Publish(int networkOpportunityId, bool isPublished, CancellationToken cancellationToken)
|
||||
{
|
||||
var databaseGuid = _user.GetDatabaseGuid();
|
||||
if (databaseGuid == Guid.Empty)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
var opportunity = await _clientNetworkOpportunityService.GetByIdAsync(databaseGuid, networkOpportunityId, cancellationToken);
|
||||
if (opportunity is null)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
return Ok(await _clientNetworkOpportunityService.PublishAsync(databaseGuid, opportunity, isPublished, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("[action]/{networkOpportunityId}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<IActionResult> UpdateInitiativeStatus(int networkOpportunityId, [FromBody] InitiativeStatuses status, CancellationToken cancellationToken)
|
||||
{
|
||||
var databaseGuid = _user.GetDatabaseGuid();
|
||||
if (databaseGuid != Guid.Empty)
|
||||
{
|
||||
await _clientNetworkOpportunityService.UpdateInitiativeStatusAsync(databaseGuid, networkOpportunityId, status, cancellationToken);
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPut("{networkOpportunityId}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<bool>> RevertWorkbookToTemplate(int networkOpportunityId, CancellationToken cancellationToken)
|
||||
{
|
||||
var databaseGuid = _user.GetDatabaseGuid();
|
||||
if (databaseGuid == Guid.Empty)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
var opportunity = await _clientNetworkOpportunityService.GetByIdAsync(databaseGuid, networkOpportunityId, cancellationToken);
|
||||
if (opportunity is null)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
return Ok(await _clientNetworkOpportunityService.RevertWorkbookToTemplateAsync(databaseGuid, opportunity, cancellationToken));
|
||||
}
|
||||
}
|
||||
}
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Strata.ContinuousImprovement.Biz.All.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.Initiatives;
|
||||
using Strata.ContinuousImprovement.Biz.Network;
|
||||
using Strata.ContinuousImprovement.Biz.Utilities;
|
||||
using Strata.CoreLib.Claims;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class ClientNetworkOpportunityLinkController : Controller
|
||||
{
|
||||
private readonly IClientNetworkOpportunityService _clientNetworkOpportunityService;
|
||||
private readonly IClientNetworkOpportunityLinkService _clientNetworkOpportunityLinkService;
|
||||
private readonly IAllOpportunityService _allOpportunityService;
|
||||
private readonly IInitiativeService _initiativeService;
|
||||
private readonly ILogger<ClientNetworkOpportunityLinkController> _logger;
|
||||
private readonly ClaimsPrincipal _user;
|
||||
|
||||
public ClientNetworkOpportunityLinkController(
|
||||
IClaimsPrincipalAccessor claimsPrincipalAccessor,
|
||||
IClientNetworkOpportunityService clientNetworkOpportunityService,
|
||||
IClientNetworkOpportunityLinkService clientNetworkOpportunityLinkService,
|
||||
IAllOpportunityService allOpportunityService,
|
||||
IInitiativeService initiativeService,
|
||||
ILogger<ClientNetworkOpportunityLinkController> logger)
|
||||
{
|
||||
_clientNetworkOpportunityService = clientNetworkOpportunityService;
|
||||
_clientNetworkOpportunityLinkService = clientNetworkOpportunityLinkService;
|
||||
_allOpportunityService = allOpportunityService;
|
||||
_initiativeService = initiativeService;
|
||||
_logger = logger;
|
||||
_user = claimsPrincipalAccessor.GetCurrentClaimsPrincipal();
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<ClientNetworkOpportunityResultDto>> AddOpportunityLink([FromBody] CreateNetworkOpportunityLinkDto createNetworkOpportunityLink, CancellationToken cancellationToken)
|
||||
{
|
||||
if (createNetworkOpportunityLink.OpportunityGuid == Guid.Empty) { return BadRequest(); }
|
||||
var databaseGuid = _user.GetDatabaseGuid();
|
||||
if (databaseGuid == Guid.Empty) { return BadRequest(); }
|
||||
var cnoExists = await _clientNetworkOpportunityService.ContainsAsync(databaseGuid, createNetworkOpportunityLink.NetworkOpportunityId, cancellationToken);
|
||||
if (!cnoExists) { return NotFound(); }
|
||||
var opportunityExists = await _allOpportunityService.ContainsAsync(createNetworkOpportunityLink.OpportunityGuid, cancellationToken);
|
||||
if (!opportunityExists) { return NotFound(); }
|
||||
var link = await _clientNetworkOpportunityLinkService.AddOpportunityLinkAsync(databaseGuid, createNetworkOpportunityLink.NetworkOpportunityId, createNetworkOpportunityLink.OpportunityGuid, cancellationToken);
|
||||
return Ok(new ClientNetworkOpportunityResultDto(link));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<ClientNetworkOpportunityResultDto>> RolloverOpportunityLink([FromBody] RolloverOpportunityDto rolloverOpportunityInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
var databaseGuid = rolloverOpportunityInfo.DatabaseGuid ?? _user.GetDatabaseGuid();
|
||||
if (databaseGuid == Guid.Empty) { return BadRequest(); }
|
||||
try
|
||||
{
|
||||
var link = await _clientNetworkOpportunityLinkService.RolloverOpportunityLinkAsync(databaseGuid,
|
||||
rolloverOpportunityInfo.SourceConfigurationGuid, rolloverOpportunityInfo.SourceOpportunityGuid,
|
||||
rolloverOpportunityInfo.RolloverConfigurationGuid, rolloverOpportunityInfo.RolloverOpportunityGuid, rolloverOpportunityInfo.RolloverInitiativeGuid, cancellationToken);
|
||||
return Ok(new ClientNetworkOpportunityResultDto(link));
|
||||
}
|
||||
catch (OpportunityNotFoundException nfe)
|
||||
{
|
||||
_logger.LogServiceError(nfe);
|
||||
return NotFound(nfe.OpportunityGuid);
|
||||
}
|
||||
catch (ClientNetworkOpportunityLinkNotFoundException lnfe)
|
||||
{
|
||||
_logger.LogServiceError(lnfe);
|
||||
return NotFound(new { lnfe.StrataId, lnfe.ConfigurationGuid, lnfe.OpportunityGuid });
|
||||
}
|
||||
catch (ClientNetworkOpportunityLinkExistsException linkExists)
|
||||
{
|
||||
_logger.LogServiceError(linkExists);
|
||||
return Ok(new ClientNetworkOpportunityResultDto(linkExists.ClientNetworkOpportunityLink));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogServiceError(ex);
|
||||
return BadRequest();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<bool>> DeleteOpportunityLink([FromBody] ClientNetworkOpportunityLinkDto opportunity, CancellationToken cancellationToken)
|
||||
{
|
||||
var databaseGuid = opportunity.DatabaseGuid ?? _user.GetDatabaseGuid();
|
||||
if (databaseGuid == Guid.Empty) { return BadRequest(); }
|
||||
var cnoLinkExists = await _clientNetworkOpportunityLinkService.ContainsOpportunityLinkAsync(databaseGuid, opportunity.ConfigurationGuid, opportunity.Id, cancellationToken);
|
||||
if (!cnoLinkExists) { return NotFound(); }
|
||||
await _clientNetworkOpportunityLinkService.DeleteOpportunityLinkAsync(databaseGuid, opportunity.ConfigurationGuid, opportunity.Id, cancellationToken);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpDelete("{opportunityGuid}/{configurationGuid}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<bool>> DeleteOpportunityLinkIfExists(Guid opportunityGuid, Guid configurationGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
var databaseGuid = _user.GetDatabaseGuid();
|
||||
if (databaseGuid == Guid.Empty) { return BadRequest(); }
|
||||
|
||||
var cnoLinkExists = await _clientNetworkOpportunityLinkService.ContainsOpportunityLinkAsync(databaseGuid, configurationGuid, opportunityGuid, cancellationToken);
|
||||
if (!cnoLinkExists) { return Ok(); }
|
||||
await _clientNetworkOpportunityLinkService.DeleteOpportunityLinkAsync(databaseGuid, configurationGuid, opportunityGuid, cancellationToken);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpDelete("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<bool>> DeleteInitiativeLink([FromBody] ClientNetworkOpportunityLinkDto initiative, CancellationToken cancellationToken)
|
||||
{
|
||||
var databaseGuid = _user.GetDatabaseGuid();
|
||||
if (databaseGuid == Guid.Empty) { return BadRequest(); }
|
||||
var cnoLinkExists = await _clientNetworkOpportunityLinkService.ContainsInitiativeLinkAsync(databaseGuid, initiative.ConfigurationGuid, initiative.Id, cancellationToken);
|
||||
if (!cnoLinkExists) { return NotFound(); }
|
||||
await _clientNetworkOpportunityLinkService.DeleteInitiativeLinkAsync(databaseGuid, initiative.ConfigurationGuid, initiative.Id, cancellationToken);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpDelete("{initiativeGuid}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<bool>> DeleteInitiativeLink(Guid initiativeGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
var databaseGuid = _user.GetDatabaseGuid();
|
||||
if (databaseGuid == Guid.Empty) { return BadRequest(); }
|
||||
|
||||
var initiative = await _initiativeService.GetById(initiativeGuid, cancellationToken);
|
||||
var cnoLinkExists = await _clientNetworkOpportunityLinkService.ContainsInitiativeLinkAsync(databaseGuid, initiative.ConfigurationGUID, initiative.InitiativeGUID, cancellationToken);
|
||||
if (!cnoLinkExists) { return NotFound(); }
|
||||
await _clientNetworkOpportunityLinkService.DeleteInitiativeLinkAsync(databaseGuid, initiative.ConfigurationGUID, initiative.InitiativeGUID, cancellationToken);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
public class RolloverOpportunityDto
|
||||
{
|
||||
public Guid? DatabaseGuid { get; set; }
|
||||
public Guid SourceConfigurationGuid { get; set; }
|
||||
public Guid SourceOpportunityGuid { get; set; }
|
||||
public Guid RolloverConfigurationGuid { get; set; }
|
||||
public Guid RolloverOpportunityGuid { get; set; }
|
||||
public Guid RolloverInitiativeGuid { get; set; }
|
||||
}
|
||||
|
||||
public class ClientNetworkOpportunityLinkDto
|
||||
{
|
||||
public Guid? DatabaseGuid { get; set; }
|
||||
public Guid ConfigurationGuid { get; set; }
|
||||
public Guid Id { get; set; }
|
||||
}
|
||||
|
||||
public class CreateNetworkOpportunityLinkDto
|
||||
{
|
||||
public int NetworkOpportunityId { get; set; }
|
||||
public Guid OpportunityGuid { get; set; }
|
||||
}
|
||||
|
||||
public class ClientNetworkOpportunityResultDto
|
||||
{
|
||||
public int NetworkOpportunityId { get; set; }
|
||||
public Guid ConfigurationGuid { get; set; }
|
||||
public Guid OpportunityGuid { get; set; }
|
||||
public Guid InitiativeGuid { get; set; }
|
||||
public string Name { get; set; }
|
||||
public ClientNetworkOpportunityResultDto(ClientNetworkOpportunityLink link, NetworkOpportunity networkOpportunity = default)
|
||||
{
|
||||
if (link == null) return;
|
||||
NetworkOpportunityId = link.ClientNetworkOpportunity.NetworkOpportunityId;
|
||||
ConfigurationGuid = link.ConfigurationGuid;
|
||||
OpportunityGuid = link.OpportunityGuid;
|
||||
InitiativeGuid = link.InitiativeGuid;
|
||||
Name = link.ClientNetworkOpportunity.NetworkOpportunity?.Name ?? networkOpportunity?.Name ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.Analytics.Models.Utils;
|
||||
using Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks;
|
||||
using Strata.ContinuousImprovement.Biz.WorkbookClient;
|
||||
using Strata.DecisionSupport.Models.Jazz;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class ClientOpportunityTypeWorkbooksController : ControllerBase
|
||||
{
|
||||
private readonly IOpportunityTypeWorkbookService _opportunityTypeWorkbookService;
|
||||
|
||||
public ClientOpportunityTypeWorkbooksController(IOpportunityTypeWorkbookService opportunityTypeWorkbookService)
|
||||
{
|
||||
_opportunityTypeWorkbookService = opportunityTypeWorkbookService;
|
||||
|
||||
}
|
||||
|
||||
[HttpGet("{opportunityType}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<OpportunityTypeWorkbookClientInfo>>> GetOpportunityTypeWorkbooks([FromRoute] int opportunityType, CancellationToken cancellationToken)
|
||||
{
|
||||
var oppTypeWorkbooks = await _opportunityTypeWorkbookService.GetOpportunityTypeWorkbooksAsync(opportunityType, cancellationToken);
|
||||
return Ok(oppTypeWorkbooks.Select(workbook => workbook.Mapped()));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
public async Task<ActionResult<IEnumerable<OpportunityTypeWorkbook>>> CreateOpportunityTypeWorkbooks([FromBody] IEnumerable<OpportunityTypeWorkbook> opportunityTypeWorkbooks, CancellationToken cancellationToken)
|
||||
{
|
||||
var createdOppTypeWorkbooks = await _opportunityTypeWorkbookService.CreateOpportunityTypeWorkbooksAsync(opportunityTypeWorkbooks, cancellationToken);
|
||||
return CreatedAtAction(nameof(CreateOpportunityTypeWorkbooks), createdOppTypeWorkbooks);
|
||||
}
|
||||
|
||||
[HttpDelete("{opportunityType}/workbooks/{workbookId}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<IActionResult> Delete([FromRoute] int opportunityType, [FromRoute] int workbookId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await _opportunityTypeWorkbookService.ContainsAsync(opportunityType, workbookId, cancellationToken))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
await _opportunityTypeWorkbookService.DeleteAsync(opportunityType, workbookId, cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.Analytics.Models.Utils;
|
||||
using Strata.ContinuousImprovement.Biz.OpportunityWorkbooks;
|
||||
using Strata.ContinuousImprovement.Biz.WorkbookClient;
|
||||
using Strata.DecisionSupport.Models.Jazz;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class ClientOpportunityWorkbooksController : ControllerBase
|
||||
{
|
||||
private readonly IOpportunityWorkbookService _opportunityWorkbookService;
|
||||
|
||||
public ClientOpportunityWorkbooksController(IOpportunityWorkbookService opportunityWorkbookService)
|
||||
{
|
||||
_opportunityWorkbookService = opportunityWorkbookService;
|
||||
}
|
||||
|
||||
[HttpGet("workbooks")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<WorkbookClientInfo>>> GetWorkbooks(CancellationToken cancellationToken)
|
||||
{
|
||||
var workbooks = await _opportunityWorkbookService.GetAllOpportunityWorkbooksAsync(false, cancellationToken);
|
||||
return Ok(workbooks.Select(workbook => workbook.Mapped()));
|
||||
}
|
||||
|
||||
[HttpGet("{opportunityGuid}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<OpportunityWorkbookClientInfo>>> GetOpportunityWorkbooks([FromRoute] Guid opportunityGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
var oppWorkbooks = await _opportunityWorkbookService.GetOpportunityWorkbooksAsync(opportunityGuid, cancellationToken);
|
||||
return Ok(oppWorkbooks.Select(workbook => workbook.Mapped()));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
public async Task<ActionResult<IEnumerable<OpportunityWorkbook>>> CreateOpportunityWorkbooks([FromBody] IEnumerable<OpportunityWorkbook> opportunityWorkbooks, CancellationToken cancellationToken)
|
||||
{
|
||||
var createdOppWorkbooks = await _opportunityWorkbookService.CreateOpportunityWorkbooksAsync(opportunityWorkbooks, cancellationToken);
|
||||
return CreatedAtAction(nameof(CreateOpportunityWorkbooks), createdOppWorkbooks);
|
||||
}
|
||||
|
||||
[HttpDelete("{opportunityGuid}/workbooks/{workbookId}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<IActionResult> Delete([FromRoute] Guid opportunityGuid, [FromRoute] int workbookId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await _opportunityWorkbookService.ContainsAsync(opportunityGuid, workbookId, cancellationToken))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
await _opportunityWorkbookService.DeleteAsync(opportunityGuid, workbookId, cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Api.Generics;
|
||||
using Strata.ContinuousImprovement.Biz.ClientWarehouses;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
[ApiController]
|
||||
public class ClientWarehousesController(IClientWarehouseService clientWarehouseService) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
[Authorize(Policy = Policies.StrataOnly)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<List<ClientWarehouse>>> GetClientWarehouses(CancellationToken cancellationToken)
|
||||
{
|
||||
var clientWarehouses = await clientWarehouseService.GetAll(cancellationToken);
|
||||
if (clientWarehouses.Count != 0)
|
||||
{
|
||||
return Ok(clientWarehouses);
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = Policies.StrataOnly)]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<int>> AddClientWarehouse([FromBody] ClientWarehouse clientWarehouse, CancellationToken cancellationToken)
|
||||
{
|
||||
return CreatedAtAction(nameof(AddClientWarehouse), await clientWarehouseService.Add(clientWarehouse, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpDelete("{databaseGuid}")]
|
||||
[Authorize(Policy = Policies.StrataOnly)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
public async Task<ActionResult> DeleteClientWarehouse([FromRoute] Guid databaseGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
await clientWarehouseService.Delete(databaseGuid, cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Biz.OpportunityComments;
|
||||
using Strata.CoreLib.Claims;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class CommentsController : ControllerBase
|
||||
{
|
||||
private readonly ICommentService _commentService;
|
||||
private readonly IClaimsPrincipalAccessor _claimsPrincipalAccessor;
|
||||
|
||||
public CommentsController(ICommentService commentService,
|
||||
IClaimsPrincipalAccessor claimsPrincipalAccessor)
|
||||
{
|
||||
_commentService = commentService;
|
||||
_claimsPrincipalAccessor = claimsPrincipalAccessor;
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
public async Task<IEnumerable<Comment>> GetComments(CommentOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = _claimsPrincipalAccessor.GetCurrentClaimsPrincipal();
|
||||
var databaseGuid = options.IsGlobal ? Guid.Empty : user?.GetDatabaseGuid() ?? Guid.Empty;
|
||||
var opportunityType = int.Parse(options.OpportunityType);
|
||||
return await _commentService.GetCommentsAsync(databaseGuid, opportunityType, options.OpportunityId, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
public async Task<Comment> Create(NewComment comment, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = _claimsPrincipalAccessor.GetCurrentClaimsPrincipal();
|
||||
if (user == null || string.IsNullOrEmpty(comment.Comment)) { return default; }
|
||||
var databaseGuid = comment.IsGlobal ? Guid.Empty : user.GetDatabaseGuid();
|
||||
var author = $"{user.GetUserLastName()}, {user.GetUserFirstName()}";
|
||||
var opportunityType = int.Parse(comment.OpportunityType);
|
||||
return await _commentService.AddCommentAsync(databaseGuid, opportunityType, comment.OpportunityId, comment.Comment, comment.CommentDate, author, cancellationToken);
|
||||
}
|
||||
|
||||
public class CommentOptions
|
||||
{
|
||||
public bool IsGlobal { get; set; }
|
||||
public string OpportunityType { get; set; }
|
||||
public long OpportunityId { get; set; }
|
||||
}
|
||||
|
||||
public class NewComment : CommentOptions
|
||||
{
|
||||
public string Comment { get; set; }
|
||||
public DateTimeOffset? CommentDate { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MoreLinq;
|
||||
using Pluralize.NET;
|
||||
using Strata.ContinuousImprovement.Biz.Configuration;
|
||||
using Strata.Schema.Client;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class ConfigurationsController : ControllerBase
|
||||
{
|
||||
private readonly IConfigurationService _configurationService;
|
||||
private readonly ISchemaServiceClient _schemaServiceClient;
|
||||
|
||||
public ConfigurationsController(IConfigurationService configurationService, ISchemaServiceClient schemaServiceClient)
|
||||
|
||||
{
|
||||
_configurationService = configurationService;
|
||||
_schemaServiceClient = schemaServiceClient;
|
||||
}
|
||||
|
||||
[HttpGet("")]
|
||||
public async Task<IEnumerable<Biz.Configuration.Configuration>> GetAllConfigurations(CancellationToken cancellationToken)
|
||||
{
|
||||
var configurations = await _configurationService.GetConfigurationsAsync(cancellationToken);
|
||||
return configurations.OrderBy(c => c.DisplayOrder);
|
||||
}
|
||||
|
||||
[HttpGet("{configurationGuid}/[action]")]
|
||||
public async Task<IEnumerable<Member>> GetConfigurationEntities(Guid configurationGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
var configuration = await _configurationService.GetConfigurationAsync(configurationGuid, cancellationToken);
|
||||
|
||||
var globalId = configuration.IsUsingCustomEntityDimension ? configuration.DepartmentRollupGlobalId : "Entity";
|
||||
var members = await _schemaServiceClient.GetDimensionMembersByGlobalIdAsync<Member>(globalId);
|
||||
|
||||
var configuredEntitiesLookup = new HashSet<int>(configuration.ConfiguredEntityIds);
|
||||
var configuredMembers = members.Where(member => configuredEntitiesLookup.Contains(member.Id));
|
||||
|
||||
return configuredMembers;
|
||||
}
|
||||
|
||||
[HttpGet("{configurationGuid}/[action]")]
|
||||
public async Task<EntityInfo> GetConfigurationEntityInfo(Guid configurationGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
var configuration = await _configurationService.GetConfigurationAsync(configurationGuid, cancellationToken);
|
||||
|
||||
var globalId = configuration.IsUsingCustomEntityDimension ? configuration.DepartmentRollupGlobalId : "Entity";
|
||||
var entity = await _schemaServiceClient.GetDimensionByGlobalIdAsync(globalId);
|
||||
var pluralize = new Pluralizer();
|
||||
var entityInfo = new EntityInfo
|
||||
{
|
||||
EntityTitle = pluralize.Pluralize(entity.FriendlyName),
|
||||
EntityLabel = entity.FriendlyName,
|
||||
EntityText = entity.FriendlyName,
|
||||
DepartmentRollupGlobalId = globalId,
|
||||
HierarchicalGuid = entity.DefaultHierarchyGuid.ToString()
|
||||
};
|
||||
return entityInfo;
|
||||
}
|
||||
|
||||
[HttpGet("{configurationGuid}/[action]")]
|
||||
public async Task<ServiceLineInfo> GetConfigurationServiceLineInfo(Guid configurationGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
var configuration = await _configurationService.GetConfigurationAsync(configurationGuid, cancellationToken);
|
||||
var dimensionGuid = configuration.ServiceLineDimensionGuid;
|
||||
Schema.Client.Dtos.Info.ScoreDimensionInfoDto serviceLine = null;
|
||||
|
||||
if (dimensionGuid == Guid.Empty)
|
||||
{
|
||||
serviceLine = await _schemaServiceClient.GetDimensionByGlobalIdAsync("Service Line");
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
serviceLine = await _schemaServiceClient.GetDimensionByIdAsync(dimensionGuid);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// empty to ensure code will fall thru to next block
|
||||
}
|
||||
if (serviceLine is null)
|
||||
{
|
||||
serviceLine = await _schemaServiceClient.GetDimensionByGlobalIdAsync("Service Line");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var pluralize = new Pluralizer();
|
||||
var serviceLineInfo = new ServiceLineInfo
|
||||
{
|
||||
ServiceLineGlobalId = string.Empty,
|
||||
ServiceLineDimensionGuid = dimensionGuid.ToString(),
|
||||
ServiceLineHierarchicalGuid = serviceLine.DefaultHierarchyGuid.ToString(),
|
||||
ServiceLineName = serviceLine.FriendlyName,
|
||||
ServiceLineTitle = pluralize.Pluralize(serviceLine.FriendlyName)
|
||||
};
|
||||
return serviceLineInfo;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class EntityInfo
|
||||
{
|
||||
public string EntityTitle { get; set; }
|
||||
public string EntityLabel { get; set; }
|
||||
public string EntityText { get; set; }
|
||||
public string DepartmentRollupGlobalId { get; set; }
|
||||
public string HierarchicalGuid { get; set; }
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class ServiceLineInfo
|
||||
{
|
||||
public string ServiceLineGlobalId { get; set; }
|
||||
public string ServiceLineTitle { get; set; }
|
||||
public string ServiceLineName { get; set; }
|
||||
public string ServiceLineDimensionGuid { get; set; }
|
||||
public string ServiceLineHierarchicalGuid { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Biz.Databases;
|
||||
using Strata.ContinuousImprovement.Biz.Replication;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
[ApiController]
|
||||
public class DatabasesController : ControllerBase
|
||||
{
|
||||
private readonly IDatabaseService _databaseService;
|
||||
|
||||
public DatabasesController(IDatabaseService databaseService)
|
||||
{
|
||||
_databaseService = databaseService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<List<Database>>> Get(CancellationToken cancellationToken)
|
||||
{
|
||||
var databases = await _databaseService.GetDatabasesAsync(cancellationToken);
|
||||
if (databases.Count != 0)
|
||||
{
|
||||
return Ok(databases);
|
||||
}
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Hangfire;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Api.Generics;
|
||||
using Strata.ContinuousImprovement.Biz.GMDN.Jobs;
|
||||
using System.Threading;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
[Authorize(Policy = Policies.StrataOnly)]
|
||||
public class DeviceIdentifiersController(IBackgroundJobClient backgroundJobClient) : ControllerBase
|
||||
{
|
||||
private readonly IBackgroundJobClient _backgroundJobClient = backgroundJobClient;
|
||||
|
||||
/// <summary>
|
||||
/// Clears device identifier data from all non-licensed CI clients
|
||||
/// </summary>
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
public ActionResult<string> ClearDeviceIdentifiers(CancellationToken cancellationToken)
|
||||
{
|
||||
var jobId = _backgroundJobClient.Enqueue<DeviceIdentifierClearingJob>(job
|
||||
=> job.ClearDimDeviceIdentifierFromJazz(cancellationToken));
|
||||
|
||||
return Ok(jobId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ApiLib.Standard.Models;
|
||||
using Strata.ContinuousImprovement.Api.Generics;
|
||||
using Strata.ContinuousImprovement.Biz.All.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.Configuration;
|
||||
using Strata.ContinuousImprovement.Biz.Dtos;
|
||||
using Strata.ContinuousImprovement.Biz.Encounter.Initiatives;
|
||||
using Strata.ContinuousImprovement.Biz.Encounter.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters;
|
||||
using Strata.ContinuousImprovement.Biz.Generics;
|
||||
using Strata.ContinuousImprovement.Biz.Initiatives;
|
||||
using Strata.ContinuousImprovement.Biz.OrgDefined;
|
||||
using Strata.ContinuousImprovement.Biz.OrgDefined.Queries;
|
||||
using Strata.ContinuousImprovement.Biz.Pagination;
|
||||
using Strata.CoreLib.Claims;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using Strata.FeatureFlags.Client;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class EncounterOpportunitiesController : OpportunityController<ENOpportunity>
|
||||
{
|
||||
private readonly IClaimsPrincipalAccessor _claimsPrincipalAccessor;
|
||||
private readonly IConfigurationService _configurationService;
|
||||
private readonly IENOpportunityService _opportunityService;
|
||||
private readonly IENInitiativeService _initiativeService;
|
||||
private readonly IOpportunityMetricCohortService _opportunityMetricCohortService;
|
||||
private readonly IFeatureFlagServiceClient _featureFlagServiceClient;
|
||||
private readonly IAllOpportunityService _allOpportunityService;
|
||||
|
||||
public EncounterOpportunitiesController(
|
||||
IConfigurationService configurationService,
|
||||
IENOpportunityService opportunityService,
|
||||
IENInitiativeService initiativeService,
|
||||
IOpportunityMetricCohortService opportunityMetricCohortService,
|
||||
IFeatureFlagServiceClient featureFlagServiceClient,
|
||||
IClaimsPrincipalAccessor claimsPrincipalAccessor,
|
||||
IAllOpportunityService allOpportunityService)
|
||||
: base(opportunityService)
|
||||
{
|
||||
_configurationService = configurationService;
|
||||
_opportunityService = opportunityService;
|
||||
_initiativeService = initiativeService;
|
||||
_opportunityMetricCohortService = opportunityMetricCohortService;
|
||||
_featureFlagServiceClient = featureFlagServiceClient;
|
||||
_claimsPrincipalAccessor = claimsPrincipalAccessor;
|
||||
_allOpportunityService = allOpportunityService;
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
public async Task<PagedApiResponse<ENOpportunity>> GetOpportunities(ENOpportunityLoadOptions loadOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
return await OpportunityService.GetPagedAsync(loadOptions.PagingOptions, loadOptions.Filters, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<OpportunityJobResultsDto>> Create(ENOpportunityDto opportunity, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
var opportunityWithFilters = opportunity.MapToDataModel(User);
|
||||
await ((IENOpportunityService)OpportunityService).SetOpportunityFilters(opportunityWithFilters, cancellationToken);
|
||||
var newOpportunity = opportunityWithFilters.MapToDataModel(User);
|
||||
var dbGuid = User?.GetDatabaseGuid() ?? Guid.Empty;
|
||||
if (dbGuid == Guid.Empty)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
var isTempoPage = await _featureFlagServiceClient.IsEnabledAsync("enableencounteropportunityintempo", dbGuid, cancellationToken: cancellationToken);
|
||||
|
||||
var result = await ((IENOpportunityService)OpportunityService).CreateAsync(newOpportunity, dbGuid, isTempoPage, cancellationToken);
|
||||
return Ok(new OpportunityJobResultsDto(result, newOpportunity.OpportunityGuid));
|
||||
}
|
||||
|
||||
[HttpDelete("{id}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<bool> Delete(Guid id, CancellationToken cancellationToken)
|
||||
=> await OpportunityService.DeleteAsync<ENOpportunityDataModel>(id, cancellationToken);
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
public async Task<IActionResult> Export(ENOpportunityExportOptions exportOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
var workbook = await ((IENOpportunityService)OpportunityService).ExportAsync(exportOptions.SortOptions, exportOptions.Filters, cancellationToken);
|
||||
return new ExcelContentResult(workbook, "EN_Opportunities");
|
||||
|
||||
}
|
||||
|
||||
[HttpGet("{opportunityGuid}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<ENOpportunityDto>> GetById(Guid opportunityGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
var opportunityDto = await _opportunityService.GetENOpportunityById(opportunityGuid, cancellationToken);
|
||||
if (opportunityDto == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(opportunityDto);
|
||||
}
|
||||
|
||||
[HttpPut("{opportunityGuid}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<bool>> UpdateGoals(Guid opportunityGuid, [FromBody] IEnumerable<ENOpportunityRowDataChanges> changes, CancellationToken cancellationToken)
|
||||
{
|
||||
var existsOpportunity = await _opportunityService.Contains(opportunityGuid, cancellationToken);
|
||||
if (!existsOpportunity)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var hasUpdatedCostPerUnit = await _opportunityService.UpdateGoalsAsync(opportunityGuid, changes, cancellationToken);
|
||||
|
||||
if (hasUpdatedCostPerUnit)
|
||||
{
|
||||
return await _initiativeService.UpdateSavings(opportunityGuid, cancellationToken);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[HttpGet("{opportunityGuid}/[action]")]
|
||||
public async Task<ENOpportunitySaveModel> GetEditModel(Guid opportunityGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
var opportunity = await _opportunityService.GetENOpportunityById(opportunityGuid, cancellationToken);
|
||||
return opportunity.MapToSaveModel();
|
||||
}
|
||||
|
||||
[HttpGet("{opportunityGuid}/[action]")]
|
||||
public async Task<InitiativeInfo> GetInitiativeInfo(Guid opportunityGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _opportunityService.GetInitiativeInfoAsync(opportunityGuid, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("{configurationGuid}/[action]")]
|
||||
public async Task<ENFilterOptions> GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
return await ((IENOpportunityService)OpportunityService).GetFilterOptionsAsync(configurationGuid, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public async Task<bool> TempoPageEnabled(CancellationToken cancellationToken)
|
||||
=> await _featureFlagServiceClient.IsEnabledAsync("enableencounteropportunityintempo", User?.GetDatabaseGuid() ?? Guid.Empty, cancellationToken: cancellationToken);
|
||||
|
||||
[HttpPut("{opportunityGuid}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<Guid>> UpdateOpportunity(Guid opportunityGuid, [FromBody] ENOpportunityDto opportunityDto, CancellationToken cancellationToken)
|
||||
{
|
||||
var dbGuid = User?.GetDatabaseGuid() ?? Guid.Empty;
|
||||
var isNameValid = await _allOpportunityService.NameIsValid(opportunityDto.ConfigurationGuid, (int)OpportunityTypes.Encounter, opportunityDto.Name,
|
||||
opportunityDto?.OpportunityGuid, cancellationToken);
|
||||
if (dbGuid == Guid.Empty || !isNameValid)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
var opportunityWithFilters = opportunityDto.MapToDataModel(User, false);
|
||||
var result = await ((IENOpportunityService)OpportunityService).UpdateAsync(dbGuid, opportunityWithFilters, cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<OpportunityMetricCohortData>>> OpportunityMetricCohortData([FromBody] OpportunityMetricCohortOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
var config = await _configurationService.GetConfigurationAsync(options.ConfigurationGuid, cancellationToken);
|
||||
if (config == null)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
var result = Enumerable.Empty<OpportunityMetricCohortData>();
|
||||
result = await _opportunityMetricCohortService.GetEncounterOpportunityMetricCohortData(config, options.Data, options.InnerJoin, cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class ENOpportunityLoadOptions
|
||||
{
|
||||
public PagingOptions PagingOptions { get; set; }
|
||||
public ENFilters Filters { get; set; }
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class ENOpportunityExportOptions
|
||||
{
|
||||
public SortOptions SortOptions { get; set; }
|
||||
public ENFilters Filters { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Strata.ContinuousImprovement.Biz.Configuration;
|
||||
using Strata.ContinuousImprovement.Biz.Exploration;
|
||||
using Strata.ContinuousImprovement.Biz.Exploration.Benchmark;
|
||||
using Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies;
|
||||
using Strata.ContinuousImprovement.Biz.Utilities;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using Strata.FeatureFlags.Client;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class ExplorationController : ControllerBase
|
||||
{
|
||||
private readonly IExplorationService _explorationService;
|
||||
private readonly IConfigurationService _configurationService;
|
||||
private readonly IFeatureFlagServiceClient _featureFlagServiceClient;
|
||||
private readonly ILogger<ExplorationController> _logger;
|
||||
|
||||
public ExplorationController(IExplorationService explorationService,
|
||||
IConfigurationService configurationService,
|
||||
IFeatureFlagServiceClient featureFlagServiceClient,
|
||||
ILogger<ExplorationController> logger)
|
||||
{
|
||||
_explorationService = explorationService;
|
||||
_configurationService = configurationService;
|
||||
_featureFlagServiceClient = featureFlagServiceClient;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpPost("case-type-family-summary")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<List<CaseTypeFamilySummaryData>>> GetCaseTypeFamilySummaries([FromBody] ExplorationInfo explorationInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
Stopwatch performanceTimer = null;
|
||||
var isFlexExPerfLoggingEnabled = await _explorationService.IsFlexExPerfLogging(cancellationToken);
|
||||
|
||||
if (isFlexExPerfLoggingEnabled)
|
||||
{
|
||||
_logger.LogPerformanceInformation($"CI FlexEx Perf * GetCaseTypeFamilySummaries Controller Entered");
|
||||
performanceTimer = Stopwatch.StartNew();
|
||||
}
|
||||
|
||||
if (!await _configurationService.IsConfigurationValid(explorationInfo.ConfigurationGuid, cancellationToken)) { return BadRequest(); }
|
||||
var summaries = await _explorationService.GetCaseTypeFamilySummariesAsync(explorationInfo, cancellationToken);
|
||||
if (isFlexExPerfLoggingEnabled) _logger.LogPerformanceInformation($"CI FlexEx Perf * GetCaseTypeFamilySummaries Controller * Total Elapsed time: {performanceTimer.Elapsed}");
|
||||
|
||||
return Ok(summaries);
|
||||
}
|
||||
|
||||
[HttpPost("case-type-family-cost-driver-info")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<List<CaseTypeFamilySummaryData>>> GetCaseTypeFamilyCostDriverInfo([FromBody] ExplorationCostDriverInfo explorationCostDriverInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
Stopwatch performanceTimer = null;
|
||||
var isFlexExPerfLoggingEnabled = await _explorationService.IsFlexExPerfLogging(cancellationToken);
|
||||
|
||||
if (isFlexExPerfLoggingEnabled)
|
||||
{
|
||||
_logger.LogPerformanceInformation($"CI FlexEx Perf * GetCaseTypeFamilyCostDriverInfo Controller Entered");
|
||||
performanceTimer = Stopwatch.StartNew();
|
||||
}
|
||||
|
||||
if (!await _configurationService.IsConfigurationValid(explorationCostDriverInfo.ConfigurationGuid, cancellationToken)) { return BadRequest(); }
|
||||
var summaries = await _explorationService.GetCaseTypeFamilySummaryDetailsAsync(explorationCostDriverInfo, cancellationToken);
|
||||
if (isFlexExPerfLoggingEnabled) _logger.LogPerformanceInformation($"CI FlexEx Perf * GetCaseTypeFamilyCostDriverInfo Controller * Total Elapsed time: {performanceTimer.Elapsed}");
|
||||
|
||||
return Ok(summaries);
|
||||
}
|
||||
|
||||
[HttpPost("case-type-family-details")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<CaseTypeFamilyDetailInfo>> GetCaseTypeFamilyDetails([FromBody] ExplorationDetailInfo explorationInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
Stopwatch performanceTimer = null;
|
||||
var isFlexExPerfLoggingEnabled = await _explorationService.IsFlexExPerfLogging(cancellationToken);
|
||||
|
||||
if (isFlexExPerfLoggingEnabled)
|
||||
{
|
||||
_logger.LogPerformanceInformation($"CI FlexEx Perf * GetCaseTypeFamilyDetails Controller Entered");
|
||||
performanceTimer = Stopwatch.StartNew();
|
||||
}
|
||||
|
||||
if (!await _configurationService.IsConfigurationValid(explorationInfo.ConfigurationGuid, cancellationToken)) { return BadRequest(); }
|
||||
await _configurationService.GetQualityVariationAprdrgFlagAsync(explorationInfo.ConfigurationGuid, cancellationToken);
|
||||
var ctfEntityDetails = await _explorationService.GetCaseTypeFamilyDetailsAsync(explorationInfo, cancellationToken);
|
||||
if (isFlexExPerfLoggingEnabled) _logger.LogPerformanceInformation($"CI FlexEx Perf * GetCaseTypeFamilyDetails Controller * Total Elapsed time: {performanceTimer.Elapsed}");
|
||||
|
||||
return Ok(ctfEntityDetails);
|
||||
}
|
||||
|
||||
[HttpPost("case-type-family-summary-heatmap")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<List<HeatMapData>>> GetHeatMapSummary([FromBody] ExplorationInfo explorationInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await _configurationService.IsConfigurationValid(explorationInfo.ConfigurationGuid, cancellationToken)) { return BadRequest(); }
|
||||
var summaries = await _explorationService.GetBenchmarkingDataForHeatMapAsync(explorationInfo, explorationInfo.Granularity, cancellationToken);
|
||||
return Ok(summaries);
|
||||
}
|
||||
|
||||
[HttpGet("flexible-exploration-information-page")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<bool>> IsFlexibleExplorationInformationPageEnabled(CancellationToken cancellationToken)
|
||||
{
|
||||
var databaseGuid = User?.GetDatabaseGuid() ?? Guid.Empty;
|
||||
var isExplorationInformationPageEnable = await _featureFlagServiceClient.IsEnabledAsync("ciflexibleexplorationpage3enabled", databaseGuid, cancellationToken: cancellationToken);
|
||||
return Ok(isExplorationInformationPageEnable);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Strata.ApiLib.Standard.Models;
|
||||
using Strata.ContinuousImprovement.Api.Generics;
|
||||
using Strata.ContinuousImprovement.Biz.Exploration.Filters;
|
||||
using Strata.ContinuousImprovement.Biz.Exploration.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.Generics;
|
||||
using Strata.ContinuousImprovement.Biz.OpportunityComments;
|
||||
using Strata.ContinuousImprovement.Biz.Pagination;
|
||||
using Strata.ContinuousImprovement.Biz.Shared;
|
||||
using Strata.ContinuousImprovement.Biz.Utilities;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using Strata.FeatureFlags.Client;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class ExplorationOpportunitiesController : OpportunityController<EXOpportunity>
|
||||
{
|
||||
private readonly IEXOpportunityService _opportunityService;
|
||||
private readonly ICommentService _commentService;
|
||||
private readonly IFeatureFlagServiceClient _featureFlagServiceClient;
|
||||
private readonly ILogger<ExplorationOpportunitiesController> _logger;
|
||||
|
||||
public ExplorationOpportunitiesController(IEXOpportunityService opportunityService,
|
||||
ICommentService commentService,
|
||||
IFeatureFlagServiceClient featureFlagServiceClient,
|
||||
ILogger<ExplorationOpportunitiesController> logger) : base(opportunityService)
|
||||
{
|
||||
_opportunityService = opportunityService;
|
||||
_commentService = commentService;
|
||||
_featureFlagServiceClient = featureFlagServiceClient;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public async Task<bool> GetEnabled(CancellationToken cancellationToken)
|
||||
{
|
||||
var databaseGuid = User?.GetDatabaseGuid() ?? Guid.Empty;
|
||||
var hasFlexibleExplorartion = User.IsSdtEmployee()
|
||||
|| (await _featureFlagServiceClient.IsEnabledAsync("ciflexibleexplorationenabled", databaseGuid, cancellationToken: cancellationToken));
|
||||
var dict = new Dictionary<string, object> { { "isSdtEmployee", User.IsSdtEmployee() }, { "hasFlexibleExploration", hasFlexibleExplorartion } };
|
||||
_logger.LogServiceInformation("CI Flexible Exploration Enabled", dict);
|
||||
return hasFlexibleExplorartion;
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
public async Task<PagedApiResponse<EXOpportunity>> GetOpportunities(EXOpportunityLoadOptions loadOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
return await OpportunityService.GetPagedAsync(loadOptions.PagingOptions, loadOptions.Filters, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("{opportunityGuid}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<EXOpportunity>> GetById(Guid opportunityGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await _opportunityService.GetById(opportunityGuid, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("{opportunityGuid}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task CheckWorkbook(Guid opportunityGuid, CancellationToken cancellationToken)
|
||||
=> await _opportunityService.CheckWorkbook(opportunityGuid, cancellationToken);
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<EXOpportunity>> Create(EXOpportunityDto savedOpportunity, CancellationToken cancellationToken)
|
||||
{
|
||||
var newOpportunity = new EXOpportunity
|
||||
{
|
||||
ConfigurationGuid = savedOpportunity.ConfigurationGuid,
|
||||
Name = savedOpportunity.Name,
|
||||
Description = savedOpportunity.Description
|
||||
};
|
||||
|
||||
return Ok(await _opportunityService.CreateAsync(newOpportunity, User?.Identity?.Name, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
public async Task<IActionResult> Export(EXOpportunityExportOptions exportOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
var workbook = await ((IEXOpportunityService)OpportunityService).ExportAsync(exportOptions.SortOptions, exportOptions.Filters, cancellationToken);
|
||||
return new ExcelContentResult(workbook, "EX_Opportunities");
|
||||
|
||||
}
|
||||
|
||||
[HttpPut("{opportunityGuid}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<EXOpportunity>> Update(Guid opportunityGuid, EXOpportunity opportunity, CancellationToken cancellationToken)
|
||||
{
|
||||
var opp = await _opportunityService.GetById(opportunityGuid, cancellationToken);
|
||||
if (opp == null)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
return Ok(await _opportunityService.UpdateAsync(opportunity, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("{opportunityGuid}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<EXOpportunity>> SetStatus(Guid opportunityGuid, [FromBody] InitiativeStatuses status, CancellationToken cancellationToken)
|
||||
{
|
||||
var opp = await _opportunityService.GetById(opportunityGuid, cancellationToken);
|
||||
if (opp == null)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
opp.InitiativeStatus = status;
|
||||
return Ok(await _opportunityService.UpdateAsync(opp, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpDelete("{opportunityGuid}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult> Delete(Guid opportunityGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
var opp = await _opportunityService.GetById(opportunityGuid, cancellationToken);
|
||||
if (opp == null)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
await ((IEXOpportunityService)_opportunityService).DeleteAsync(opportunityGuid, cancellationToken);
|
||||
var databaseGuid = User?.GetDatabaseGuid() ?? Guid.Empty;
|
||||
await _commentService.DeleteCommentsAsync(databaseGuid, (int)OpportunityTypes.Blank, opp.OpportunityId, cancellationToken);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpGet("{configurationGuid}/[action]")]
|
||||
public async Task<EXFilterOptions> GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
return await ((IEXOpportunityService)OpportunityService).GetFilterOptionsAsync(configurationGuid, cancellationToken);
|
||||
}
|
||||
|
||||
public class EXOpportunityDto
|
||||
{
|
||||
[Required]
|
||||
[StringLength(200)]
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public Guid ConfigurationGuid { get; set; }
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class EXOpportunityLoadOptions
|
||||
{
|
||||
public PagingOptions PagingOptions { get; set; }
|
||||
public EXFilters Filters { get; set; }
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class EXOpportunityExportOptions
|
||||
{
|
||||
public SortOptions SortOptions { get; set; }
|
||||
public EXFilters Filters { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Strata.ContinuousImprovement.Api.Generics;
|
||||
using Strata.ContinuousImprovement.Biz.Dtos;
|
||||
using Strata.ContinuousImprovement.Biz.Exploration;
|
||||
using Strata.ContinuousImprovement.Biz.Mappers;
|
||||
using Strata.ContinuousImprovement.Biz.Population;
|
||||
using Strata.ContinuousImprovement.Biz.UserSetting;
|
||||
using Strata.ContinuousImprovement.Biz.Utilities;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using Strata.DecisionSupport.Models.Encounters;
|
||||
using Strata.DecisionSupport.Models.Jazz;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class ExplorationPopulationsController : ControllerBase
|
||||
{
|
||||
private readonly IUserSettingService _userSettingService;
|
||||
private readonly IExplorationPopulationService _populationService;
|
||||
private readonly IExplorationService _explorationService;
|
||||
private readonly ILogger<ExplorationPopulationsController> _logger;
|
||||
public ExplorationPopulationsController(IUserSettingService userSettingService, IExplorationPopulationService populationService, IExplorationService explorationService,
|
||||
ILogger<ExplorationPopulationsController> logger)
|
||||
{
|
||||
_userSettingService = userSettingService;
|
||||
_populationService = populationService;
|
||||
_explorationService = explorationService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<bool> IsNameUnique([FromBody] string name, CancellationToken cancellationToken)
|
||||
{
|
||||
var isValid = await _populationService.IsNameUnique(name, cancellationToken);
|
||||
return isValid;
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<bool>> AddPopulation([FromBody] ExplorationPopulationDto populationDto, CancellationToken cancellationToken)
|
||||
{
|
||||
var isAdded = await _populationService.AddPopulationAsync(populationDto, cancellationToken);
|
||||
return Ok(isAdded);
|
||||
}
|
||||
|
||||
[HttpGet("{populationId}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<Tuple<DateTime, DateTime>>> GetDischargeDates(long populationId, CancellationToken cancellationToken)
|
||||
{
|
||||
var population = await _populationService.ContainsPopulation(populationId, cancellationToken);
|
||||
if (!population) return NotFound();
|
||||
|
||||
var result = await _populationService.GetDischargeDates(populationId, cancellationToken);
|
||||
return Ok(new Tuple<DateTime, DateTime>(result.Item1, result.Item2));
|
||||
}
|
||||
|
||||
[HttpPut("{populationId}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult> UpdatePopulation(long populationId, ExplorationPopulationDto populationDto, CancellationToken cancellationToken)
|
||||
{
|
||||
var population = await _populationService.ContainsPopulation(populationId, cancellationToken);
|
||||
if (!population) return NotFound();
|
||||
|
||||
var isUpdated = await _populationService.UpdatePopulationAsync(populationDto, cancellationToken);
|
||||
|
||||
if (isUpdated)
|
||||
{
|
||||
var counts = await _populationService.GetPopulationCountOpportunitiesAsync([populationId], cancellationToken);
|
||||
if (counts.TryGetValue(populationId, out int opportunitiesCount) && opportunitiesCount > 0)
|
||||
{
|
||||
var userGuid = User?.GetUserGuid() ?? Guid.Empty;
|
||||
var userSetting = await _userSettingService.GetById(userGuid, cancellationToken);
|
||||
if (userSetting != null)
|
||||
{
|
||||
await _populationService.CreateExplorationPopulationOpportunity(userSetting.ConfigurationGuid, populationId, false, cancellationToken);
|
||||
}
|
||||
}
|
||||
return NoContent();
|
||||
}
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
[HttpGet("")]
|
||||
public async Task<ActionResult<IEnumerable<ExplorationPopulationDataDto>>> GetPopulations(CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _populationService.GetPopulationsAsync(cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPut("{populationId}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<bool>> UpdatePopulationName(long populationId,[FromBody] string name, CancellationToken cancellationToken)
|
||||
{
|
||||
var isUpdated = await _populationService.UpdatePopulationName(populationId, name, cancellationToken);
|
||||
return Ok(isUpdated);
|
||||
}
|
||||
|
||||
[HttpGet("{populationId}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<ExplorationPopulationDto>> GetPopulationData(long populationId, CancellationToken cancellationToken)
|
||||
{
|
||||
var population = await _populationService.ContainsPopulation(populationId, cancellationToken);
|
||||
if (!population) return NotFound();
|
||||
|
||||
var result = await _populationService.GetPopulationDataAsync(populationId, cancellationToken);
|
||||
return result != null ? Ok(result) : BadRequest();
|
||||
}
|
||||
|
||||
[HttpGet("{populationId}/details")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<ExplorationPopulationDetailsDto>> GetPopulationDetails(long populationId, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _populationService.GetPopulationDetailsAsync(populationId, cancellationToken);
|
||||
if (result == null)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("info/{populationId}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IExplorationPopulationInfo>> GetPopulationInfo(long populationId, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _populationService.GetPopulationInfoAsync(populationId, cancellationToken);
|
||||
if (result == null)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("{populationId}/encounters")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<IJazzExplorationEncounterInfo>>> GetEncounters(long populationId, Guid configurationGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogServiceInformation($"Performing {nameof(GetEncounters)} for configuration: {configurationGuid}, populationid: {populationId}");
|
||||
|
||||
var populationData = await _populationService.GetPopulationDataAsync(populationId, cancellationToken);
|
||||
if (populationData != null)
|
||||
{
|
||||
_logger.LogServiceInformation($"{nameof(GetEncounters)} populationData is not null, totalEncounters:{populationData.TotalEncounters}");
|
||||
|
||||
var encounterPhysicians = await _explorationService.GetEncountersAsync(populationData.MapToExplorationDetailInfo(), configurationGuid, cancellationToken);
|
||||
if (encounterPhysicians != null)
|
||||
{
|
||||
if (configurationGuid == Guid.Empty)
|
||||
{
|
||||
var newIncludedEncounterIds = encounterPhysicians.Where(e => !e.IsExcluded).Select(x => x.EncounterId);
|
||||
var newExcludedEncounterIds = encounterPhysicians.Where(e => e.IsExcluded).Select(x => x.EncounterId);
|
||||
var updated = await _populationService.UpdatePopulationMappingsAsync(populationId, newIncludedEncounterIds, newExcludedEncounterIds, cancellationToken);
|
||||
}
|
||||
|
||||
var result = encounterPhysicians.Where(enc => !enc.IsExcluded).Select(x => x.MapToExplorationEncounterInfo());
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogServiceInformation($"{nameof(GetEncounters)} populationData is null");
|
||||
}
|
||||
|
||||
_logger.LogServiceInformation($"{nameof(GetEncounters)} returned a BadRequest()");
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
[HttpDelete("{populationId}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
[Authorize(Policy = Policies.IsAdmin)]
|
||||
public async Task<IActionResult> DeletePopulation(long populationId, CancellationToken cancellationToken)
|
||||
{
|
||||
var population = await _populationService.ContainsPopulation(populationId, cancellationToken);
|
||||
if (!population) return NotFound();
|
||||
|
||||
var result = await _populationService.DeletePopulation(populationId, cancellationToken);
|
||||
return result ? NoContent() : BadRequest();
|
||||
}
|
||||
|
||||
[HttpPost("{populationId}/duplicate")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<ExplorationPopulationDataDto>> DuplicatePopulation(long populationId, [FromBody] string name, CancellationToken cancellationToken)
|
||||
{
|
||||
var populationData = await _populationService.DuplicatePopulation(populationId, name, cancellationToken);
|
||||
return populationData != null ? Ok(populationData) : BadRequest();
|
||||
}
|
||||
|
||||
[HttpPost("{populationId}/opportunities")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<ExplorationPopulationOpportunityDto>> CreateOpportunity(long populationId, CancellationToken cancellationToken)
|
||||
{
|
||||
var userGuid = User?.GetUserGuid() ?? Guid.Empty;
|
||||
var userSetting = await _userSettingService.GetById(userGuid, cancellationToken);
|
||||
if (userSetting != null)
|
||||
{
|
||||
var result = await _populationService.CreateExplorationPopulationOpportunity(userSetting.ConfigurationGuid, populationId, true, cancellationToken);
|
||||
if (result)
|
||||
{
|
||||
var opportunity = (await _populationService.GetPopulationOpportunitiesAsync([populationId], cancellationToken)).FirstOrDefault()?.FirstOrDefault();
|
||||
return Ok(opportunity);
|
||||
}
|
||||
}
|
||||
|
||||
return BadRequest();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Biz.ChargeCode;
|
||||
using Strata.ContinuousImprovement.Biz.Exploration.Filters;
|
||||
using Strata.ContinuousImprovement.Biz.Filter;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
[ApiController]
|
||||
public class FiltersController : ControllerBase
|
||||
{
|
||||
private readonly IChargeCodeService _chargeCodeService;
|
||||
private readonly IFilterService _filterService;
|
||||
|
||||
public FiltersController(IChargeCodeService chargeCodeService, IFilterService filterService)
|
||||
{
|
||||
_chargeCodeService = chargeCodeService;
|
||||
_filterService = filterService;
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<List<FilterListItem>> GetCostDriverFilters(CancellationToken cancellationToken)
|
||||
{
|
||||
var costDrivers = await _chargeCodeService.GetChargeCodeCostDrivers(cancellationToken);
|
||||
return costDrivers.Select(costDriver => new FilterListItem { Key = costDriver, Title = costDriver }).ToList();
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<List<FilterListItem>> GetSupplyFilters(CancellationToken cancellationToken)
|
||||
{
|
||||
return await _filterService.GetSupplyList(cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<List<FilterListItem>> GetCostComponentFilters(CancellationToken cancellationToken)
|
||||
{
|
||||
return await _filterService.GetCostComponentList(cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("[action]/{chipKey}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<List<FilterListItem>> GetSphCriteriaFilters([FromRoute] ChipKey chipKey, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _filterService.GetSphCriteriaList(chipKey, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ApiLib.Standard.Models;
|
||||
using Strata.ContinuousImprovement.Api.Generics;
|
||||
using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.Dtos;
|
||||
using Strata.ContinuousImprovement.Biz.FreeForm.Initiatives;
|
||||
using Strata.ContinuousImprovement.Biz.FreeForm.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.FreeForm.Opportunities.Filters;
|
||||
using Strata.ContinuousImprovement.Biz.Initiatives;
|
||||
using Strata.ContinuousImprovement.Biz.Pagination;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class FreeFormOpportunitiesController : OpportunityController<FFOpportunity>
|
||||
{
|
||||
private readonly IInitiativeService _initiativeService;
|
||||
private readonly IFFOpportunityService _ffOpportunityService;
|
||||
|
||||
public FreeFormOpportunitiesController(IFFOpportunityService opportunityService,
|
||||
IInitiativeService initiativeService) : base(opportunityService)
|
||||
{
|
||||
_initiativeService = initiativeService;
|
||||
_ffOpportunityService = opportunityService;
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
public async Task<PagedApiResponse<FFOpportunity>> GetOpportunities(FFOpportunityLoadOptions loadOptions, CancellationToken cancellationToken)
|
||||
=> await OpportunityService.GetPagedAsync(loadOptions.PagingOptions, loadOptions.Filters, cancellationToken);
|
||||
|
||||
[HttpGet("{opportunityGuid}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<FFOpportunityDetailModel>> GetById(Guid opportunityGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
var dataModel = await _ffOpportunityService.GetDataModelById(opportunityGuid, cancellationToken);
|
||||
var detailModel = dataModel.MapToDetailModel(dataModel.Configuration.FiscalMonthResolver);
|
||||
return Ok(detailModel);
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<Guid>> Create(FFOpportunityDto opportunity, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
var newOpportunity = opportunity.MapToDataModel();
|
||||
newOpportunity.AuthorGuid = User.GetUserGuid();
|
||||
newOpportunity.AuthorName = User.Identity.Name;
|
||||
|
||||
var opportunityGuid = await ((IFFOpportunityService)OpportunityService).CreateAsync(newOpportunity, cancellationToken);
|
||||
return Ok(opportunityGuid);
|
||||
}
|
||||
|
||||
[HttpDelete("{id}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<bool> Delete(Guid id, CancellationToken cancellationToken)
|
||||
=> await OpportunityService.DeleteAsync<FFOpportunityDataModel>(id, cancellationToken);
|
||||
|
||||
[HttpGet("{opportunityGuid}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<FreeFormInitiativeDto>> GetInitiative(Guid opportunityGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
var opportunity = await ((IFFOpportunityService)OpportunityService).GetDataModelById(opportunityGuid, cancellationToken);
|
||||
if (opportunity == null) { return BadRequest(); }
|
||||
var initiative = await _initiativeService.GetById(opportunity.InitiativeGuid, cancellationToken);
|
||||
return initiative.MapToFreeFormInitiative(opportunity);
|
||||
}
|
||||
|
||||
[HttpPost("{opportunityGuid}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task SetGoalsAsync(Guid opportunityGuid, [FromBody] FFOpportunityMonthValuesDto dto, CancellationToken cancellationToken)
|
||||
=> await ((IFFOpportunityService)OpportunityService).SetGoalsAsync(opportunityGuid, dto.OpportunityDetail, dto.MonthValues, cancellationToken);
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
public async Task<IActionResult> Export(FFOpportunityExportOptions exportOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
var workbook = await ((IFFOpportunityService)OpportunityService).ExportAsync(exportOptions.SortOptions, exportOptions.Filters, cancellationToken);
|
||||
return new ExcelContentResult(workbook, "FF_Opportunities");
|
||||
|
||||
}
|
||||
|
||||
[HttpGet("{configurationGuid}/[action]")]
|
||||
public async Task<FFFilterOptions> GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken)
|
||||
=> await ((IFFOpportunityService)OpportunityService).GetFilterOptionsAsync(configurationGuid, cancellationToken);
|
||||
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class FFOpportunityLoadOptions
|
||||
{
|
||||
public PagingOptions PagingOptions { get; set; }
|
||||
public FFFilters Filters { get; set; }
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class FFOpportunityExportOptions
|
||||
{
|
||||
public SortOptions SortOptions { get; set; }
|
||||
public FFFilters Filters { get; set; }
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class FFOpportunityMonthValuesDto
|
||||
{
|
||||
public FFOpportunityDetailModel OpportunityDetail { get; set; }
|
||||
public IList<FFOpportunityDetailRowData> MonthValues { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Biz.GMDN;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class GMDNFileUploadController : ControllerBase
|
||||
{
|
||||
private readonly IGMDNService _gmdnService;
|
||||
|
||||
public GMDNFileUploadController(IGMDNService gmdnService)
|
||||
{
|
||||
_gmdnService = gmdnService;
|
||||
}
|
||||
|
||||
[HttpPost("{fileType}/[action]")]
|
||||
[DisableRequestSizeLimit, RequestFormLimits(MultipartBodyLengthLimit = int.MaxValue, ValueLengthLimit = int.MaxValue)]
|
||||
public async Task<ActionResult> UploadGMDNFile(GMDNFileType fileType, IFormFile file, CancellationToken cancellationToken)
|
||||
{
|
||||
var userName = User?.Identity?.Name;
|
||||
switch (fileType)
|
||||
{
|
||||
case GMDNFileType.Term:
|
||||
await _gmdnService.SeedGMDNTerms(file, userName, cancellationToken);
|
||||
break;
|
||||
case GMDNFileType.TermCategory:
|
||||
await _gmdnService.SeedGMDNTermCategories(file, userName, cancellationToken);
|
||||
break;
|
||||
case GMDNFileType.Category:
|
||||
await _gmdnService.SeedGMDNCategories(file, userName, cancellationToken);
|
||||
break;
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
||||
public enum GMDNFileType
|
||||
{
|
||||
Term,
|
||||
TermCategory,
|
||||
Category
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Biz.GeneralLedger.Details;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class GeneralLedgerDetailsController : ControllerBase
|
||||
{
|
||||
private readonly IGLDetailService _glDetailService;
|
||||
|
||||
public GeneralLedgerDetailsController(IGLDetailService glDetailService)
|
||||
{
|
||||
_glDetailService = glDetailService;
|
||||
}
|
||||
|
||||
[HttpPost("{configurationGuid}/[action]")]
|
||||
public async Task<GLDetail> GetDepartmentData(Guid configurationGuid, GLDepartmentAccountData departmentAccountData, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _glDetailService.GetOverallDetailAsync(configurationGuid, departmentAccountData, cancellationToken);
|
||||
return result;
|
||||
}
|
||||
|
||||
[HttpPost("{configurationGuid}/[action]")]
|
||||
public async Task<GLDetail> GetStatisticData(Guid configurationGuid, GLDepartmentAccountData departmentAccountData, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _glDetailService.GetStatisticDetailAsync(configurationGuid, departmentAccountData, cancellationToken);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ApiLib.Standard.Models;
|
||||
using Strata.ContinuousImprovement.Api.Generics;
|
||||
using Strata.ContinuousImprovement.Biz.GeneralLedger.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.GeneralLedger.Opportunities.Filters;
|
||||
using Strata.ContinuousImprovement.Biz.Pagination;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class GeneralLedgerOpportunitiesController : OpportunityController<GLOpportunity>
|
||||
{
|
||||
|
||||
public GeneralLedgerOpportunitiesController(IGLOpportunityService opportunityService)
|
||||
: base(opportunityService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
public async Task<PagedApiResponse<GLOpportunity>> GetOpportunities(GLOpportunityLoadOptions loadOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
return await OpportunityService.GetPagedAsync(loadOptions.PagingOptions, loadOptions.Filters, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
public async Task<IActionResult> Export(GLOpportunityExportOptions exportOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
var workbook = await ((IGLOpportunityService)OpportunityService).ExportAsync(exportOptions.SortOptions, exportOptions.Filters, cancellationToken);
|
||||
return new ExcelContentResult(workbook, "GL_Opportunities");
|
||||
|
||||
}
|
||||
|
||||
[HttpGet("{configurationGuid}/[action]")]
|
||||
public async Task<GLFilterOptions> GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
return await ((IGLOpportunityService)OpportunityService).GetFilterOptionsAsync(configurationGuid, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<Guid>> Create(GLOpportunitySaveModel opportunity, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
opportunity.AuthorGuid = User.GetUserGuid();
|
||||
opportunity.AuthorName = User.Identity.Name;
|
||||
|
||||
var newOpportunityId = await ((IGLOpportunityService)OpportunityService).CreateAsync(opportunity, cancellationToken);
|
||||
return Ok(newOpportunityId);
|
||||
}
|
||||
|
||||
[HttpDelete("{id}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<bool> Delete(Guid id, CancellationToken cancellationToken)
|
||||
=> await OpportunityService.DeleteAsync<GLOpportunityDataModel>(id, cancellationToken);
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class GLOpportunityLoadOptions
|
||||
{
|
||||
public PagingOptions PagingOptions { get; set; }
|
||||
public GLFilters Filters { get; set; }
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class GLOpportunityExportOptions
|
||||
{
|
||||
public SortOptions SortOptions { get; set; }
|
||||
public GLFilters Filters { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using Hangfire;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Strata.ContinuousImprovement.Biz.GMDN;
|
||||
using Strata.ContinuousImprovement.Api.Extensions;
|
||||
using Strata.ContinuousImprovement.Api.Attributes;
|
||||
using Strata.ContinuousImprovement.Biz.GMDN.Jobs;
|
||||
using System;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Strata.ContinuousImprovement.Biz.Hangfire;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
|
||||
public class GudidFileController : ControllerBase
|
||||
{
|
||||
private readonly IGUDIDService _gudidService;
|
||||
private readonly IBatchJobClient _batchJobClient;
|
||||
private readonly string _datadogUrl;
|
||||
private readonly string _hangfireUrl;
|
||||
|
||||
|
||||
public GudidFileController(
|
||||
IBatchJobClient batchJobClient,
|
||||
IGUDIDService gudidService,
|
||||
IOptions<AccessGudidSection> accessGudidAccessor,
|
||||
IOptions<DatadogSection> datadogAccessor,
|
||||
IOptions<HangfireSection> sectionAccessor)
|
||||
{
|
||||
_gudidService = gudidService;
|
||||
_batchJobClient = batchJobClient;
|
||||
var datadog = datadogAccessor.Value ?? throw new ArgumentNullException(nameof(datadogAccessor));
|
||||
_datadogUrl = datadog.SeedGMDNJob;
|
||||
var hangfire = sectionAccessor.Value ?? throw new ArgumentNullException(nameof(sectionAccessor));
|
||||
_hangfireUrl = hangfire.Url;
|
||||
}
|
||||
|
||||
[HttpPost("{dataGroup}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status415UnsupportedMediaType)]
|
||||
[DisableFormValueModelBinding]
|
||||
[DisableRequestSizeLimit, RequestFormLimits(MultipartBodyLengthLimit = int.MaxValue, ValueLengthLimit = int.MaxValue)]
|
||||
public async Task<ActionResult<string>> SeedGUDID(DataGroupGudId dataGroup, [BindNever] IFormFile file, CancellationToken cancellationToken)
|
||||
{
|
||||
var userName = User?.Identity?.Name;
|
||||
var fileSection = await HttpContext.Request.GetFileFromMultipartUpload(cancellationToken);
|
||||
if (fileSection?.FileStream == null)
|
||||
{
|
||||
// prob should return something to indicate exactly what went wrong.
|
||||
return new UnsupportedMediaTypeResult();
|
||||
}
|
||||
|
||||
switch (dataGroup)
|
||||
{
|
||||
case DataGroupGudId.Identifiers:
|
||||
await _gudidService.SeedGUDID("tmp", "gudid_identifiers", fileSection.FileStream, fileSection.FileName, userName, cancellationToken);
|
||||
var jobId = _batchJobClient.StartNew(batch => { batch.Enqueue<SeedGUDIDIdentifierDataJob>(job => job.MergeGUDIDIdentifierData(cancellationToken)); });
|
||||
return $"{_hangfireUrl}/jobs/details/{jobId}";
|
||||
case DataGroupGudId.Terms:
|
||||
await _gudidService.SeedGUDID("public", "gudid_terms", fileSection.FileStream, fileSection.FileName, userName, cancellationToken);
|
||||
break;
|
||||
case DataGroupGudId.Devices:
|
||||
await _gudidService.SeedGUDID("public", "gudid_devices", fileSection.FileStream, fileSection.FileName, userName, cancellationToken);
|
||||
break;
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public string DatadogUrl()
|
||||
{ return _datadogUrl; }
|
||||
|
||||
public enum DataGroupGudId
|
||||
{
|
||||
Terms,
|
||||
Devices,
|
||||
Identifiers
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using Hangfire;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Strata.ContinuousImprovement.Biz.GMDN;
|
||||
using Strata.ContinuousImprovement.Biz.Hangfire;
|
||||
using Strata.Hangfire.Jazz.Client;
|
||||
using Strata.Hangfire.Jazz.Client.Models;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class HangfireController : ControllerBase
|
||||
{
|
||||
private readonly IJazzHangfireServiceClient _hangfireService;
|
||||
private readonly string _accessGudidUrl;
|
||||
private readonly string _datadogUrl;
|
||||
|
||||
public HangfireController(
|
||||
IJazzHangfireServiceClient jazzHangfireServiceClient,
|
||||
IOptions<AccessGudidSection> accessGudidAccessor,
|
||||
IOptions<DatadogSection> datadogAccessor,
|
||||
IOptions<HangfireSection> sectionAccessor)
|
||||
{
|
||||
_hangfireService = jazzHangfireServiceClient;
|
||||
var accessGudid = accessGudidAccessor.Value ?? throw new ArgumentNullException(nameof(accessGudidAccessor));
|
||||
_accessGudidUrl = accessGudid.Url;
|
||||
var datadog = datadogAccessor.Value ?? throw new ArgumentNullException(nameof(datadogAccessor));
|
||||
_datadogUrl = datadog.SeedGMDNJob;
|
||||
var hangfire = sectionAccessor.Value ?? throw new ArgumentNullException(nameof(sectionAccessor));
|
||||
|
||||
}
|
||||
|
||||
[HttpGet("{jobId}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<JobInfoDto>> JobStatus(Guid jobId, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _hangfireService.GetJobInfoAsync(jobId, cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public string DatadogUrl()
|
||||
{ return _datadogUrl; }
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public string AccessGudidUrl()
|
||||
{ return _accessGudidUrl; }
|
||||
|
||||
public enum DataGroup
|
||||
{
|
||||
Terms,
|
||||
Devices,
|
||||
Identifiers
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MoreLinq;
|
||||
using Strata.ContinuousImprovement.Biz.InitiativeDetailColumns;
|
||||
using Strata.ContinuousImprovement.Biz.InitiativeRollupColumns;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class InitiativeDetailColumnsController : ControllerBase
|
||||
{
|
||||
private readonly IInitiativeDetailColumnService _initiativeDetailColumnService;
|
||||
private readonly IInitiativeRollupColumnService _initiativeRollupColumnService;
|
||||
|
||||
public InitiativeDetailColumnsController(IInitiativeDetailColumnService initiativeDetailColumnService, IInitiativeRollupColumnService initiativeRollupColumnService)
|
||||
{
|
||||
_initiativeDetailColumnService = initiativeDetailColumnService;
|
||||
_initiativeRollupColumnService = initiativeRollupColumnService;
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public async Task<IEnumerable<InitiativeDetailColumn>> GetColumnList(CancellationToken cancellationToken)
|
||||
{
|
||||
var userGuid = User?.GetUserGuid() ?? Guid.Empty;
|
||||
return await ManageRollupColumns((await _initiativeDetailColumnService.GetColumnList(userGuid, cancellationToken)), cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public async Task<IEnumerable<InitiativeDetailColumn>> GetDefaultColumnList(CancellationToken cancellationToken)
|
||||
{
|
||||
return await ManageRollupColumns(_initiativeDetailColumnService.GetDefaultColumnList(), cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<InitiativeDetailColumn>> ManageRollupColumns(IEnumerable<InitiativeDetailColumn> columns, CancellationToken cancellationToken)
|
||||
{
|
||||
var columnList = columns.ToList();
|
||||
// Add any custom rollups to default list
|
||||
var rollupColumns = (await _initiativeRollupColumnService.GetAllAsync(cancellationToken))
|
||||
|
||||
.OrderBy(x => x.InitiativeRollupColumnId).ToList();
|
||||
if (rollupColumns.Any())
|
||||
{
|
||||
Enumerable.Range(1, 3).ForEach(id =>
|
||||
{
|
||||
var column = columnList.Find(dflt => rollupColumns.Any(rc => dflt.Name.StartsWith($"InitiativeRollup{id}", StringComparison.CurrentCultureIgnoreCase)));
|
||||
if (column != null)
|
||||
{
|
||||
var rollupColumn = rollupColumns.Find(rc => rc.InitiativeRollupColumnId == id);
|
||||
if (rollupColumn != null)
|
||||
{
|
||||
column.Heading = rollupColumn.Name;
|
||||
column.InitiativeRollupColumnId = rollupColumn.InitiativeRollupColumnId;
|
||||
}
|
||||
else
|
||||
{
|
||||
columnList.Remove(column);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// no rollups configured - remove rollup columns
|
||||
columnList.RemoveAll(x => x.Name.StartsWith("InitiativeRollup"));
|
||||
}
|
||||
return columnList;
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
public async Task SetColumnList(InitiativeDetailColumnSaveInfo columnInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
var userGuid = User?.GetUserGuid() ?? Guid.Empty;
|
||||
await _initiativeDetailColumnService.SetColumnList(userGuid, columnInfo.Columns, cancellationToken);
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class InitiativeDetailColumnSaveInfo
|
||||
{
|
||||
public IEnumerable<InitiativeDetailColumn> Columns { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Api.Generics;
|
||||
using Strata.ContinuousImprovement.Biz.InitiativeRollupColumns;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class InitiativeRollupColumnsController : ControllerBase
|
||||
{
|
||||
private readonly IInitiativeRollupColumnService _initiativeRollupColumnService;
|
||||
|
||||
public InitiativeRollupColumnsController(IInitiativeRollupColumnService initiativeRollupColumnService)
|
||||
{
|
||||
_initiativeRollupColumnService = initiativeRollupColumnService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IEnumerable<InitiativeRollupColumn>> GetInitiativeRollupColumnsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return await _initiativeRollupColumnService.GetAllAsync(cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("{initiativeRollupColumnId}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<InitiativeRollupColumn>> GetByIdAsync([FromRoute] int initiativeRollupColumnId, CancellationToken cancellationToken)
|
||||
{
|
||||
var rollupColumn = await _initiativeRollupColumnService.GetByIdAsync(initiativeRollupColumnId, cancellationToken);
|
||||
if (rollupColumn != null)
|
||||
{
|
||||
return Ok(rollupColumn);
|
||||
}
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
[HttpPost("[action]/{initiativeRollupColumnId}")]
|
||||
public async Task<bool> IsNameValid([FromRoute] int initiativeRollupColumnId, [FromBody] string name, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _initiativeRollupColumnService.IsNameValid(name, cancellationToken, initiativeRollupColumnId);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[Authorize(Policy = Policies.IsAdmin)]
|
||||
public async Task<ActionResult<InitiativeRollupColumn>> Create(InitiativeRollupColumnCreateInfo createInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
if (await _initiativeRollupColumnService.ContainsNameAsync(createInfo.Name, cancellationToken))
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
return await _initiativeRollupColumnService.CreateAsync(createInfo, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPut("{initiativeRollupColumnId}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
[Authorize(Policy = Policies.IsAdmin)]
|
||||
public async Task<ActionResult<InitiativeRollupColumn>> Update([FromRoute] int initiativeRollupColumnId, [FromBody] InitiativeRollupColumnSaveInfo initiativeRollupColumnSaveInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
if (await _initiativeRollupColumnService.ContainsNameAsync(initiativeRollupColumnSaveInfo.Name, cancellationToken, initiativeRollupColumnId))
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
if (!await _initiativeRollupColumnService.ContainsAsync(initiativeRollupColumnId, cancellationToken))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return await _initiativeRollupColumnService.UpdateAsync(initiativeRollupColumnId, initiativeRollupColumnSaveInfo, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpDelete("{initiativeRollupColumnId}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
[Authorize(Policy = Policies.IsAdmin)]
|
||||
public async Task<IActionResult> Delete([FromRoute] int initiativeRollupColumnId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await _initiativeRollupColumnService.ContainsAsync(initiativeRollupColumnId, cancellationToken))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
await _initiativeRollupColumnService.DeleteAsync(initiativeRollupColumnId, cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpGet("{initiativeRollupColumnId}/values")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<IEnumerable<InitiativeRollupValue>>> GetInitiativeRollupValuesAsync([FromRoute] int initiativeRollupColumnId, CancellationToken cancellationToken)
|
||||
{
|
||||
var values = await _initiativeRollupColumnService.GetByRollupValuesById(initiativeRollupColumnId, cancellationToken);
|
||||
if (values != null)
|
||||
{
|
||||
return Ok(values);
|
||||
}
|
||||
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
[HttpPut("{initiativeRollupColumnId}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[Authorize(Policy = Policies.IsAdmin)]
|
||||
public async Task<ActionResult<InitiativeRollupValue>> AddRollupValueAsync([FromRoute] int initiativeRollupColumnId, [FromBody] string value, CancellationToken cancellationToken)
|
||||
{
|
||||
var values = await _initiativeRollupColumnService.GetByRollupValuesById(initiativeRollupColumnId, cancellationToken);
|
||||
if (values == null) { return NotFound(); }
|
||||
if (values.Select(v => v.Value).Contains(value)) { return BadRequest(); }
|
||||
return await _initiativeRollupColumnService.AddRollupValue(initiativeRollupColumnId, value, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,643 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.OpenApi.Extensions;
|
||||
using Strata.ContinuousImprovement.Api.Generics;
|
||||
using Strata.ContinuousImprovement.Biz.All.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.ChargeCode.Initiatives;
|
||||
using Strata.ContinuousImprovement.Biz.ChargeCode.Jobs;
|
||||
using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.Encounter.Initiatives;
|
||||
using Strata.ContinuousImprovement.Biz.Encounter.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.FreeForm.Initiatives;
|
||||
using Strata.ContinuousImprovement.Biz.FreeForm.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.GeneralLedger.Initiatives;
|
||||
using Strata.ContinuousImprovement.Biz.GeneralLedger.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.Generics;
|
||||
using Strata.ContinuousImprovement.Biz.InitiativeRollupColumns;
|
||||
using Strata.ContinuousImprovement.Biz.Initiatives;
|
||||
using Strata.ContinuousImprovement.Biz.Initiatives.Filters;
|
||||
using Strata.ContinuousImprovement.Biz.Mappers;
|
||||
using Strata.ContinuousImprovement.Biz.Notification;
|
||||
using Strata.ContinuousImprovement.Biz.Pagination;
|
||||
using Strata.ContinuousImprovement.Biz.Payroll.Initiatives;
|
||||
using Strata.ContinuousImprovement.Biz.Payroll.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.Utilities;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using Strata.FeatureFlags.Client;
|
||||
using Strata.Hangfire.Jazz.Client;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class InitiativesController : ControllerBase
|
||||
{
|
||||
private readonly IInitiativeService _initiativeService;
|
||||
private readonly ICCOpportunityService _ccOpportunityService;
|
||||
private readonly ICCInitiativeService _ccInitiativeService;
|
||||
private readonly IENOpportunityService _enOpportunityService;
|
||||
private readonly IENInitiativeService _enInitiativeService;
|
||||
private readonly IFFOpportunityService _ffOpportunityService;
|
||||
private readonly IFFInitiativeService _ffInitiativeService;
|
||||
private readonly IGLOpportunityService _glOpportunityService;
|
||||
private readonly IGLInitiativeService _glInitiativeService;
|
||||
private readonly IPROpportunityService _prOpportunityService;
|
||||
private readonly IPRInitiativeService _prInitiativeService;
|
||||
private readonly IStakeholderUser _stakeholderUser;
|
||||
private readonly IHubContext<NotificationHub, INotificationHub> _notificationHubContext;
|
||||
private readonly ILogger<InitiativesController> _logger;
|
||||
private readonly ICCInitiativeJob _ccInitiativeJob;
|
||||
private readonly IJazzHangfireServiceClient _hangfireService;
|
||||
private readonly IInitiativeRollupColumnService _initiativeRollupColumnService;
|
||||
private readonly IFeatureFlagServiceClient _featureFlagServiceClient;
|
||||
private readonly IAllOpportunityService _allOpportunityService;
|
||||
|
||||
#pragma warning disable S107 // Constructor has NN parameters, which is greater than the 7 authorized
|
||||
public InitiativesController(IInitiativeService initiativeService,
|
||||
ICCOpportunityService ccOpportunityService,
|
||||
ICCInitiativeService ccInitiativeService,
|
||||
IENOpportunityService enOpportunityService,
|
||||
IENInitiativeService enInitiativeService,
|
||||
IFFOpportunityService ffOpportunityService,
|
||||
IFFInitiativeService ffInitiativeService,
|
||||
IGLOpportunityService glOpportunityService,
|
||||
IGLInitiativeService glInitiativeService,
|
||||
IPROpportunityService prOpportunityService,
|
||||
IPRInitiativeService prInitiativeService,
|
||||
IStakeholderUser stakeholderUser,
|
||||
IHubContext<NotificationHub, INotificationHub> notificationHubContext,
|
||||
ILogger<InitiativesController> logger,
|
||||
ICCInitiativeJob ccInitiativeJob,
|
||||
IJazzHangfireServiceClient hangfireService,
|
||||
IInitiativeRollupColumnService initiativeRollupColumnService,
|
||||
IFeatureFlagServiceClient featureFlagServiceClient,
|
||||
IAllOpportunityService allOpportunityService)
|
||||
#pragma warning restore S107 // Constructor has NN parameters, which is greater than the 7 authorized
|
||||
{
|
||||
_initiativeService = initiativeService;
|
||||
_ccOpportunityService = ccOpportunityService;
|
||||
_ccInitiativeService = ccInitiativeService;
|
||||
_enOpportunityService = enOpportunityService;
|
||||
_enInitiativeService = enInitiativeService;
|
||||
_ffOpportunityService = ffOpportunityService;
|
||||
_ffInitiativeService = ffInitiativeService;
|
||||
_glOpportunityService = glOpportunityService;
|
||||
_glInitiativeService = glInitiativeService;
|
||||
_prOpportunityService = prOpportunityService;
|
||||
_prInitiativeService = prInitiativeService;
|
||||
_stakeholderUser = stakeholderUser;
|
||||
_notificationHubContext = notificationHubContext;
|
||||
_logger = logger;
|
||||
_ccInitiativeJob = ccInitiativeJob;
|
||||
_hangfireService = hangfireService;
|
||||
_initiativeRollupColumnService = initiativeRollupColumnService;
|
||||
_featureFlagServiceClient = featureFlagServiceClient;
|
||||
_allOpportunityService = allOpportunityService;
|
||||
}
|
||||
|
||||
[HttpPost()]
|
||||
public async Task<IEnumerable<InitiativeDetailTreeGridModel>> GetInitiatives(InitiativeFilters filters, CancellationToken cancellationToken)
|
||||
{
|
||||
var userGuid = User?.GetUserGuid() ?? Guid.Empty;
|
||||
var initiatives = await _initiativeService.GetInitiatives(filters, cancellationToken);
|
||||
await _initiativeService.SetFilterSettings(userGuid, filters, cancellationToken);
|
||||
return initiatives;
|
||||
}
|
||||
|
||||
[HttpGet("{initiativeGuid}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<InitiativeDetail>> GetById(Guid initiativeGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
var initiative = await _initiativeService.GetById(initiativeGuid, cancellationToken);
|
||||
return initiative.MapToDetail();
|
||||
}
|
||||
|
||||
[HttpGet("{initiativeGuid}/[action]")]
|
||||
public async Task<InitiativeInfo> GetInitiativeInfo(Guid initiativeGuid, CancellationToken cancellationToken)
|
||||
=> await _initiativeService.GetInitiativeInfoAsync(initiativeGuid, cancellationToken);
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status202Accepted)]
|
||||
public async Task<IActionResult> Export(bool exportAll, [FromBody] InitiativeExportOptions filters, CancellationToken cancellationToken)
|
||||
{
|
||||
var workbook = await _initiativeService.ExportAsync(exportAll, User.GetUserGuid(), filters.SortOptions, filters.Filters, cancellationToken);
|
||||
return new ExcelContentResult(workbook, "Initiatives");
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public async Task<IEnumerable<WorkstreamDto>> GetWorkstreams(CancellationToken cancellationToken)
|
||||
{
|
||||
return await _initiativeService.GetWorkstreams(cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("{configurationGuid}/[action]")]
|
||||
public async Task<InitiativeFilterOptions> GetFilterOptions(Guid configurationGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _initiativeService.GetFilterOptions(configurationGuid, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public async Task<InitiativeFilters> GetFilterSettings(CancellationToken cancellationToken)
|
||||
{
|
||||
var userGuid = User?.GetUserGuid() ?? Guid.Empty;
|
||||
return await _initiativeService.GetFilterSettings(userGuid, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
public async Task<InitiativeFilters> SetFilterSettings(InitiativeFilters filters, CancellationToken cancellationToken)
|
||||
{
|
||||
var userGuid = User?.GetUserGuid() ?? Guid.Empty;
|
||||
return await _initiativeService.SetFilterSettings(userGuid, filters, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPut("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> SetRampupInfo(InitiativeDto initiative, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var found = await _initiativeService.GetById(initiative.InitiativeGuid, cancellationToken);
|
||||
if (found == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
await _initiativeService.SetRampupInfo(initiative, cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogServiceError(ex, info: new Dictionary<string, object> { { nameof(initiative), initiative } });
|
||||
return BadRequest();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[Authorize(Policy = Policies.IsAdmin)]
|
||||
public async Task<IActionResult> SetInitiativeRollupValue(InitiativeRollupValueInfo initiativeRollupValue, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await _initiativeService.ContainsAsync(initiativeRollupValue.InitiativeGuid, cancellationToken))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
if (!await _initiativeRollupColumnService.ContainsAsync(initiativeRollupValue.RollupId, cancellationToken))
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
await _initiativeService.SetRollupValue(initiativeRollupValue.InitiativeGuid, initiativeRollupValue.RollupId, initiativeRollupValue.RollupValueId, cancellationToken);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class InitiativeRollupValueInfo
|
||||
{
|
||||
public Guid InitiativeGuid { get; set; }
|
||||
public int RollupId { get; set; }
|
||||
public int RollupValueId { get; set; }
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult> CreateChargeCode(CCInitiativeDto initiative, CancellationToken cancellationToken)
|
||||
{
|
||||
var hub = _notificationHubContext.Clients.Group(initiative.Initiative.Name);
|
||||
try
|
||||
{
|
||||
var dbGuid = User?.GetDatabaseGuid() ?? Guid.Empty;
|
||||
var isNameValid = await _allOpportunityService.NameIsValid(initiative.Initiative.ConfigurationGuid, (int)OpportunityTypes.ChargeCode, initiative.Initiative.Name,
|
||||
initiative.Initiative.OpportunityGuid, cancellationToken);
|
||||
if (dbGuid == Guid.Empty || !isNameValid)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
await hub.SendOpportunityCreate(initiative.Initiative.Name);
|
||||
if (initiative.Initiative.OpportunityGuid == Guid.Empty)
|
||||
{
|
||||
await _ccInitiativeJob.CreateChargeCodeInitiative(initiative.Initiative, initiative.NetworkOpportunityId, dbGuid, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
var opportunity = await _ccOpportunityService.GetById(initiative.Initiative.OpportunityGuid, cancellationToken);
|
||||
|
||||
// Save Initiative
|
||||
var newInitiative = initiative.Initiative.MapToInitiative(_stakeholderUser);
|
||||
var initiativeGuid = await _ccInitiativeService.CreateChargeCodeInitiative(dbGuid, newInitiative, opportunity.OpportunityGuid, cancellationToken);
|
||||
return Ok(initiativeGuid);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogServiceError(ex, info: new Dictionary<string, object> { { "initiativeName", initiative.Initiative.Name } });
|
||||
await hub.SendOpportunityCreateError(initiative.Initiative.Name, ex.Message);
|
||||
return BadRequest();
|
||||
}
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class CCInitiativeDto
|
||||
{
|
||||
public ChargeCodeInitiativeDto Initiative { get; set; }
|
||||
public int? NetworkOpportunityId { get; set; }
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<Guid>> UpdateChargeCode(CCInitiativeDto initiativeOpportunity, CancellationToken cancellationToken)
|
||||
{
|
||||
var hub = _notificationHubContext.Clients.Group(initiativeOpportunity.Initiative.Name);
|
||||
|
||||
try
|
||||
{
|
||||
var dbGuid = User?.GetDatabaseGuid() ?? Guid.Empty;
|
||||
var isNameValid = await _allOpportunityService.NameIsValid(initiativeOpportunity.Initiative.ConfigurationGuid, (int)OpportunityTypes.ChargeCode, initiativeOpportunity.Initiative.Name,
|
||||
initiativeOpportunity.Initiative.OpportunityGuid, cancellationToken);
|
||||
if (dbGuid == Guid.Empty || !isNameValid)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
await hub.SendOpportunityUpdate(initiativeOpportunity.Initiative.Name);
|
||||
await _ccInitiativeJob.UpdateChargeCodeInitiative(initiativeOpportunity.Initiative, initiativeOpportunity.NetworkOpportunityId, dbGuid, cancellationToken);
|
||||
return Ok();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogServiceError(ex, info: new Dictionary<string, object> { { "initiativeName", initiativeOpportunity.Initiative.Name } });
|
||||
await hub.SendOpportunityCreateError(initiativeOpportunity.Initiative.Name, ex.Message);
|
||||
return BadRequest();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<Guid>> CreateEncounter(EncounterInitiativeDto initiative, CancellationToken cancellationToken)
|
||||
{
|
||||
var hub = _notificationHubContext.Clients.Group(initiative.Name);
|
||||
try
|
||||
{
|
||||
var dbGuid = User?.GetDatabaseGuid() ?? Guid.Empty;
|
||||
var isNameValid = await _allOpportunityService.NameIsValid(initiative.ConfigurationGuid, (int)OpportunityTypes.Encounter, initiative.Name,
|
||||
Guid.Empty, cancellationToken);
|
||||
if (dbGuid == Guid.Empty || !isNameValid)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
await hub.SendOpportunityCreate(initiative.Name);
|
||||
var opportunityWithFilters = initiative.MapToDataModel(User);
|
||||
await _enOpportunityService.SetOpportunityFilters(opportunityWithFilters, cancellationToken);
|
||||
var newOpportunity = opportunityWithFilters.MapToDataModel(User);
|
||||
var isTempoPage = await _featureFlagServiceClient.IsEnabledAsync("enableencounteropportunityintempo", dbGuid, cancellationToken: cancellationToken);
|
||||
|
||||
var jobId = await _enOpportunityService.CreateAsync(newOpportunity, dbGuid, isTempoPage, cancellationToken);
|
||||
|
||||
await hub.SendInitiativeCreate(initiative.Name);
|
||||
Initiative newInitiative = initiative.MapToInitiative(_stakeholderUser);
|
||||
await _enInitiativeService.CreateInitiative(dbGuid, newInitiative, newOpportunity.OpportunityGuid, cancellationToken);
|
||||
return newOpportunity.OpportunityGuid;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogServiceError(ex, ex.Message);
|
||||
return Guid.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<EncounterInitiativeDto>> UpdateEncounter(EncounterInitiativeDto initiativeDto, CancellationToken cancellationToken)
|
||||
{
|
||||
var hub = _notificationHubContext.Clients.Group(initiativeDto.Name);
|
||||
|
||||
try
|
||||
{
|
||||
var dbGuid = User?.GetDatabaseGuid() ?? Guid.Empty;
|
||||
var isNameValid = await _allOpportunityService.NameIsValid(initiativeDto.ConfigurationGuid, (int)OpportunityTypes.Encounter, initiativeDto.Name,
|
||||
initiativeDto.OpportunityGuid, cancellationToken);
|
||||
if (dbGuid == Guid.Empty || !isNameValid)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
var initiative = initiativeDto.MapToInitiative(_stakeholderUser);
|
||||
|
||||
if (initiativeDto.InitiativeGuid == Guid.Empty)
|
||||
{
|
||||
await hub.SendInitiativeCreate(initiativeDto.Name);
|
||||
var newInitiativeGuid = await _enInitiativeService.CreateInitiative(dbGuid, initiative, initiativeDto.OpportunityGuid, cancellationToken);
|
||||
initiativeDto.InitiativeGuid = newInitiativeGuid;
|
||||
}
|
||||
else
|
||||
{
|
||||
var updatedOpportunity = initiativeDto.MapToDataModel(User, false);
|
||||
var jobId = await _enOpportunityService.UpdateAsync(dbGuid, updatedOpportunity, cancellationToken);
|
||||
|
||||
await _enInitiativeService.UpdateInitiative(initiative, updatedOpportunity.OpportunityGuid, cancellationToken);
|
||||
}
|
||||
|
||||
return Ok(initiativeDto);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogServiceError(ex, ex.Message);
|
||||
return BadRequest();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hangfire job helper to wait for job completion and check success.
|
||||
/// </summary>
|
||||
/// <param name="jobId"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<bool> JobFinishedSuccessfully(Guid jobId, CancellationToken cancellationToken)
|
||||
{
|
||||
// Wait for the job to finish
|
||||
var result = await _hangfireService.GetJobInfoAsync(jobId, cancellationToken);
|
||||
while (result.IsRunningStatus())
|
||||
{
|
||||
await Task.Delay(500, cancellationToken);
|
||||
result = await _hangfireService.GetJobInfoAsync(jobId, cancellationToken);
|
||||
}
|
||||
return result.IsSuccessful();
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<Guid>> CreateFreeForm(FreeFormInitiativeDto initiative, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dbGuid = User?.GetDatabaseGuid() ?? Guid.Empty;
|
||||
if (dbGuid == Guid.Empty)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
var newOpportunity = initiative.MapToDataModel(User);
|
||||
var opportunityGuid = await _ffOpportunityService.CreateAsync(newOpportunity, cancellationToken);
|
||||
|
||||
var newInitiative = initiative.MapToInitiative(_stakeholderUser);
|
||||
await _ffInitiativeService.CreateInitiative(dbGuid, newInitiative, opportunityGuid, cancellationToken);
|
||||
return newOpportunity.OpportunityGuid;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogServiceError(ex, ex.Message);
|
||||
return Guid.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<FreeFormInitiativeDto>> UpdateFreeForm(FreeFormInitiativeDto initiativeDto, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dbGuid = User?.GetDatabaseGuid() ?? Guid.Empty;
|
||||
if (dbGuid == Guid.Empty)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
var initiative = initiativeDto.MapToInitiative(_stakeholderUser);
|
||||
|
||||
if (initiativeDto.InitiativeGuid == Guid.Empty)
|
||||
{
|
||||
var newInitiativeGuid = await _ffInitiativeService.CreateInitiative(dbGuid, initiative, initiativeDto.OpportunityGuid, cancellationToken);
|
||||
initiativeDto.InitiativeGuid = newInitiativeGuid;
|
||||
}
|
||||
else
|
||||
{
|
||||
var updatedOpportunity = initiativeDto.MapToOpportunity();
|
||||
await _ffOpportunityService.UpdateAsync(updatedOpportunity, cancellationToken);
|
||||
await _ffInitiativeService.UpdateAsync(initiative, cancellationToken);
|
||||
}
|
||||
|
||||
return Ok(initiativeDto);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogServiceError(ex, ex.Message);
|
||||
return BadRequest(default(FreeFormInitiativeDto));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{initiativeGuid}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult> Finalize(Guid initiativeGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _initiativeService.FinalizeInitiative(initiativeGuid, cancellationToken);
|
||||
if (!(await RunTracking(initiativeGuid, cancellationToken)))
|
||||
{
|
||||
throw new Exception($"Initiative tracking job did not complete successfully for initiative {initiativeGuid}.");
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogServiceError(ex);
|
||||
return BadRequest();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(typeof(Guid), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<Guid>> CreateGeneralLedger(GeneralLedgerInitiativeDto initiative, CancellationToken cancellationToken)
|
||||
{
|
||||
var opportunityGuid = await CreateOpportunityAndInitiativeWithJob(
|
||||
initiative.MapToDataModel(User),
|
||||
initiative.MapToInitiative(_stakeholderUser),
|
||||
_glOpportunityService.CreateAsync,
|
||||
_glInitiativeService.CreateInitiative,
|
||||
OpportunityTypes.GeneralLedger.GetDisplayName(),
|
||||
cancellationToken);
|
||||
|
||||
return CreatedAtAction(nameof(CreateGeneralLedger), null, opportunityGuid);
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(typeof(Guid), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<Guid>> CreatePayroll(PayrollInitiativeDto initiative, CancellationToken cancellationToken)
|
||||
{
|
||||
var opportunityGuid = await CreateOpportunityAndInitiativeWithJob(
|
||||
initiative.MapToDataModel(User),
|
||||
initiative.MapToInitiative(_stakeholderUser),
|
||||
_prOpportunityService.CreateAsync,
|
||||
_prInitiativeService.CreateInitiative,
|
||||
OpportunityTypes.Payroll.GetDisplayName(),
|
||||
cancellationToken);
|
||||
|
||||
return CreatedAtAction(nameof(CreatePayroll), null, opportunityGuid);
|
||||
}
|
||||
|
||||
private async Task<ActionResult<Guid>> CreateOpportunityAndInitiativeWithJob<TOpportunityDataModel>(
|
||||
TOpportunityDataModel newOpportunity,
|
||||
Initiative initiative,
|
||||
Func<TOpportunityDataModel, CancellationToken, Task<Guid>> createOpportunityFunc,
|
||||
Func<Guid, Initiative, Guid, CancellationToken, Task<Guid>> createInitiativeFunc,
|
||||
string opportunityType,
|
||||
CancellationToken cancellationToken)
|
||||
where TOpportunityDataModel : IOpportunity
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(initiative);
|
||||
|
||||
var dbGuid = User.GetDatabaseGuid();
|
||||
if (dbGuid == Guid.Empty)
|
||||
{
|
||||
throw new Exception("Invalid database context.");
|
||||
}
|
||||
|
||||
// Use the opportunity GUID from the data model
|
||||
var opportunityGuid = newOpportunity.OpportunityGuid;
|
||||
var jobId = await createOpportunityFunc(newOpportunity, cancellationToken);
|
||||
|
||||
// Result is a job ID - wait for completion
|
||||
var jobFinishedSuccessfully = await JobFinishedSuccessfully(jobId, cancellationToken);
|
||||
if (!jobFinishedSuccessfully)
|
||||
{
|
||||
throw new Exception($"{opportunityType} Opportunity creation job did not complete successfully.");
|
||||
}
|
||||
|
||||
var initiativeGuid = await createInitiativeFunc(dbGuid, initiative, opportunityGuid, cancellationToken);
|
||||
if (initiativeGuid == Guid.Empty)
|
||||
{
|
||||
throw new Exception($"{opportunityType} Initiative creation failed for opportunity {opportunityGuid}.");
|
||||
}
|
||||
return opportunityGuid;
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<IEnumerable<InitiativeCriteriaDto>> GetCriteria(CancellationToken cancellationToken)
|
||||
{
|
||||
return await _initiativeService.GetCriteria(cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("[action]/{initiativeGuid}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<InitiativeScoresDto> GetScores(Guid initiativeGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _initiativeService.GetScores(initiativeGuid, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPut("set-scores")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<bool> SetScores(InitiativeScoresDto scores, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _initiativeService.SetScores(scores, cancellationToken);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs tracking for an initiative and waits for completion.
|
||||
/// </summary>
|
||||
/// <param name="initiativeGuid"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<bool> RunTracking(Guid initiativeGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
var jobId = await _initiativeService.RunTracking(initiativeGuid, cancellationToken);
|
||||
var jobFinishedSuccessfully = await JobFinishedSuccessfully(jobId, cancellationToken);
|
||||
if (!jobFinishedSuccessfully)
|
||||
{
|
||||
throw new Exception($"Initiative tracking job did not complete successfully for initiative {initiativeGuid}.");
|
||||
}
|
||||
return jobFinishedSuccessfully;
|
||||
}
|
||||
|
||||
private static readonly List<int> SupportedOpportunityTypesForNameRetrieval =
|
||||
[
|
||||
(int)OpportunityTypes.GeneralLedger,
|
||||
(int)OpportunityTypes.Payroll
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the opportunity ID for a given initiative or opportunity name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name to search for.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>The opportunity GUID if found.</returns>
|
||||
[HttpGet("[action]")]
|
||||
[ProducesResponseType(typeof(Guid), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Guid>> GetOpportunityGuidByName([FromQuery] Guid configurationGuid,
|
||||
[FromQuery] int opportunityType, [FromQuery] string name, CancellationToken cancellationToken)
|
||||
{
|
||||
if (configurationGuid == Guid.Empty) { return BadRequest("Invalid configuration GUID."); }
|
||||
if (!SupportedOpportunityTypesForNameRetrieval.Contains(opportunityType)) { return BadRequest("Invalid opportunity type."); }
|
||||
if (string.IsNullOrWhiteSpace(name)) { return BadRequest("Name parameter is required."); }
|
||||
|
||||
try
|
||||
{
|
||||
var initiative = await _initiativeService.FindInitiativeByPartialName(configurationGuid, name, cancellationToken);
|
||||
if (initiative == null)
|
||||
{
|
||||
var opp = await _allOpportunityService.GetOpportunityByNamePrefix(configurationGuid, (OpportunityTypes)opportunityType, name, cancellationToken);
|
||||
return opp == null
|
||||
? throw new Exception($"No Initiative/Opportunity found with name: {name}")
|
||||
: (ActionResult<Guid>)Ok(opp.OpportunityGuid);
|
||||
}
|
||||
var opportunity = await _allOpportunityService.GetByInitiativeId(initiative.InitiativeGUID, configurationGuid, (OpportunityTypes)opportunityType, cancellationToken);
|
||||
return opportunity == null
|
||||
? throw new Exception($"Opportunity not found for initiative: {initiative.InitiativeGUID}")
|
||||
: (ActionResult<Guid>)Ok(opportunity.OpportunityGuid);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogServiceError(ex, ex.Message);
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class InitiativeExportOptions
|
||||
{
|
||||
public SortOptions SortOptions { get; set; }
|
||||
public InitiativeFilters Filters { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Biz.QualityVariation.QVI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers.JazzQvi
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/quality-variation-savings/non-qvi")]
|
||||
public class NonQviSavingsController : ControllerBase
|
||||
{
|
||||
private readonly INonQviSavingsService _nonQviSavingsService;
|
||||
|
||||
public NonQviSavingsController(INonQviSavingsService nonQviSavingsService)
|
||||
{
|
||||
_nonQviSavingsService = nonQviSavingsService;
|
||||
}
|
||||
|
||||
[HttpGet("case-type-families")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<NonQviCaseTypeFamilyInfo>>> GetCaseTypeFamilies(
|
||||
[FromQuery] Guid configurationGuid,
|
||||
[FromQuery] bool isBaseline = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (configurationGuid == Guid.Empty)
|
||||
{
|
||||
return BadRequest("configurationGuid is required.");
|
||||
}
|
||||
|
||||
var result = await _nonQviSavingsService.GetCaseTypeFamilyInfoAsync(
|
||||
configurationGuid,
|
||||
isBaseline,
|
||||
cancellationToken);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("drg-types")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<NonQviDrgTypeInfo>>> GetDrgTypes(
|
||||
[FromQuery] Guid configurationGuid,
|
||||
[FromQuery] bool isBaseline = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (configurationGuid == Guid.Empty)
|
||||
{
|
||||
return BadRequest("configurationGuid is required.");
|
||||
}
|
||||
|
||||
var result = await _nonQviSavingsService.GetDrgTypeInfoAsync(
|
||||
configurationGuid,
|
||||
isBaseline,
|
||||
cancellationToken);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Biz.QualityVariation.QVI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers.JazzQvi
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/quality-variation-encounters")]
|
||||
public class QviEncountersController : ControllerBase
|
||||
{
|
||||
private readonly IQviEncounterService _qviEncounterService;
|
||||
|
||||
public QviEncountersController(IQviEncounterService qviEncounterService)
|
||||
{
|
||||
_qviEncounterService = qviEncounterService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<EncounterInfo>>> Get(
|
||||
[FromQuery] Guid configurationGuid,
|
||||
[FromQuery] DateTime startDate,
|
||||
[FromQuery] DateTime endDate,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (configurationGuid == Guid.Empty)
|
||||
{
|
||||
return BadRequest("configurationGuid is required.");
|
||||
}
|
||||
|
||||
var result = await _qviEncounterService.GetEncounterInfoAsync(
|
||||
configurationGuid,
|
||||
startDate,
|
||||
endDate,
|
||||
cancellationToken);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Biz.QualityVariation.QVI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers.JazzQvi
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/quality-variation-savings/line-items")]
|
||||
public class QviLineItemSavingsController : ControllerBase
|
||||
{
|
||||
private readonly IQviLineItemSavingsService _qviLineItemSavingsService;
|
||||
|
||||
public QviLineItemSavingsController(IQviLineItemSavingsService qviLineItemSavingsService)
|
||||
{
|
||||
_qviLineItemSavingsService = qviLineItemSavingsService;
|
||||
}
|
||||
|
||||
[HttpGet("case-type-families")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<QviLineItemCaseTypeFamilyInfo>>> GetCaseTypeFamilies(
|
||||
[FromQuery] Guid configurationGuid,
|
||||
[FromQuery] bool isBaseline = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (configurationGuid == Guid.Empty)
|
||||
{
|
||||
return BadRequest("configurationGuid is required.");
|
||||
}
|
||||
|
||||
var result = await _qviLineItemSavingsService.GetCaseTypeFamilyInfoAsync(
|
||||
configurationGuid,
|
||||
isBaseline,
|
||||
cancellationToken);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("drg-types")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<QviLineItemDrgTypeInfo>>> GetDrgTypes(
|
||||
[FromQuery] Guid configurationGuid,
|
||||
[FromQuery] bool isBaseline = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (configurationGuid == Guid.Empty)
|
||||
{
|
||||
return BadRequest("configurationGuid is required.");
|
||||
}
|
||||
|
||||
var result = await _qviLineItemSavingsService.GetDrgTypeInfoAsync(
|
||||
configurationGuid,
|
||||
isBaseline,
|
||||
cancellationToken);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("encounter-savings")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<QviLineItemEncounterSavingInfo>>> GetEncounterSavings(
|
||||
[FromQuery] Guid configurationGuid,
|
||||
[FromQuery] Guid? opportunityGuid = null,
|
||||
[FromQuery] bool? isBaseline = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (configurationGuid == Guid.Empty)
|
||||
{
|
||||
return BadRequest("configurationGuid is required.");
|
||||
}
|
||||
|
||||
var result = await _qviLineItemSavingsService.GetEncounterSavingInfoAsync(
|
||||
configurationGuid,
|
||||
opportunityGuid,
|
||||
isBaseline,
|
||||
cancellationToken);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Biz.MappedColumns;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class MappedColumnsController(
|
||||
IMappedColumnService mappedColumnService
|
||||
) : ControllerBase
|
||||
{
|
||||
[HttpGet("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<MappedColumn>>> GetColumnList(string name, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await mappedColumnService.GetColumnList(name, cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
public async Task<IActionResult> SetColumnList(ColumnListRequest columnListRequest, CancellationToken cancellationToken)
|
||||
{
|
||||
await mappedColumnService.SetColumnList(columnListRequest.Name, columnListRequest.Columns, cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
public class ColumnListRequest
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public IEnumerable<MappedColumn> Columns { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Strata.ApiLib.Standard.Models;
|
||||
using Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters;
|
||||
using Strata.ContinuousImprovement.Biz.Network;
|
||||
using Strata.ContinuousImprovement.Biz.Pagination;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class NetworkOpportunitiesController : ControllerBase
|
||||
{
|
||||
private readonly INetworkOpportunityService _networkOpportunityService;
|
||||
private readonly List<string> _creators;
|
||||
private readonly ILogger<NetworkOpportunitiesController> _logger;
|
||||
|
||||
public NetworkOpportunitiesController(INetworkOpportunityService networkOpportunityService,
|
||||
IOptions<NetworkOpportunitySection> sectionAccessor,
|
||||
ILogger<NetworkOpportunitiesController> logger)
|
||||
{
|
||||
_networkOpportunityService = networkOpportunityService;
|
||||
var networkOpportunity = sectionAccessor.Value ?? throw new ArgumentNullException(nameof(NetworkOpportunitySection));
|
||||
_creators = networkOpportunity.Creators?.Distinct().ToList() ?? new List<string>();
|
||||
_logger = logger;
|
||||
var dict = new Dictionary<string, object> { { "service", nameof(NetworkOpportunitiesController) }, { "creators", _creators } };
|
||||
using (_logger.BeginScope(dict))
|
||||
_logger.LogInformation("Network Opportunity Creators");
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public async Task<bool> GetEnabled(CancellationToken cancellationToken)
|
||||
{
|
||||
var dict = new Dictionary<string, object> {
|
||||
{ "service", nameof(NetworkOpportunitiesController) },
|
||||
{ "creators", _creators },
|
||||
{ "isSdtEmployee", User.IsSdtEmployee() }
|
||||
};
|
||||
using (_logger.BeginScope(dict))
|
||||
_logger.LogInformation("Network Opportunity Enabled");
|
||||
if (User.IsSdtEmployee()) { return true; }
|
||||
var databaseGuid = ClaimsPrincipalExtensions.GetDatabaseGuid(User);
|
||||
return await _networkOpportunityService.GetEnabledAsync(databaseGuid, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public async Task<bool> IsCreator()
|
||||
{
|
||||
var user = User?.Identity?.Name ?? "";
|
||||
var isCreator = _creators.Any() && (_creators.Contains(user) || _creators.Any(x => x == "*"));
|
||||
var dict = new Dictionary<string, object> {
|
||||
{ "service", nameof(NetworkOpportunitiesController) },
|
||||
{ "creators", _creators },
|
||||
{ "user", user },
|
||||
{ "isCreator", isCreator }
|
||||
};
|
||||
using (_logger.BeginScope(dict))
|
||||
_logger.LogInformation("Network Opportunity IsCreator");
|
||||
return isCreator;
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
public async Task<PagedApiResponse<NetworkOpportunitySummary>> GetOpportunities(NetworkOpportunityLoadOptions loadOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _networkOpportunityService.GetOpportunitiesAsync(User, loadOptions.PagingOptions, loadOptions.Filters, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<NetworkOpportunity>> Create([FromBody] NetworkOpportunitySaveModel opportunitySaveModel, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _networkOpportunityService.CreateAsync(opportunitySaveModel, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPut("{networkOpportunityId}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<IActionResult> Update(int networkOpportunityId, NetworkOpportunitySaveModel opportunitySaveModel, CancellationToken cancellationToken)
|
||||
{
|
||||
if (networkOpportunityId != opportunitySaveModel.NetworkOpportunityId)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
await _networkOpportunityService.UpdateAsync(opportunitySaveModel, cancellationToken);
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpDelete("{networkOpportunityId}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<bool>> Delete(int networkOpportunityId, CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await _networkOpportunityService.DeleteAsync(networkOpportunityId, cancellationToken));
|
||||
}
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class NetworkOpportunityLoadOptions
|
||||
{
|
||||
public PagingOptions PagingOptions { get; set; }
|
||||
public NetworkOpportunityFilters Filters { get; set; }
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class OpportunityTypeWorkbooksController : ControllerBase
|
||||
{
|
||||
private readonly IOpportunityTypeWorkbookService _opportunityTypeWorkbookService;
|
||||
|
||||
public OpportunityTypeWorkbooksController(IOpportunityTypeWorkbookService opportunityTypeWorkbook)
|
||||
{
|
||||
_opportunityTypeWorkbookService = opportunityTypeWorkbook;
|
||||
}
|
||||
|
||||
[HttpGet("{opportunityType}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<OpportunityTypeWorkbook>>> GetOpportunityTypeWorkbooks([FromRoute] int opportunityType, CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await _opportunityTypeWorkbookService.GetOpportunityTypeWorkbooksAsync(opportunityType, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<Guid>> CreateOpportunityTypeWorkbooks([FromBody] IEnumerable<OpportunityTypeWorkbook> opportunityTypeWorkbooks, CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await _opportunityTypeWorkbookService.CreateOpportunityTypeWorkbooksAsync(opportunityTypeWorkbooks, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpDelete("{opportunityType}/workbook/{workbookId}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<IActionResult> Delete([FromRoute] int opportunityType, [FromRoute] int workbookId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await _opportunityTypeWorkbookService.ContainsAsync(opportunityType, workbookId, cancellationToken))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
await _opportunityTypeWorkbookService.DeleteAsync(opportunityType, workbookId, cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Biz.OpportunityWorkbooks;
|
||||
using Strata.ContinuousImprovement.Biz.StrategicOpportunities;
|
||||
using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using Strata.FeatureFlags.Client;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class OpportunityWorkbooksController : ControllerBase
|
||||
{
|
||||
private readonly IOpportunityWorkbookService _opportunityWorkbookService;
|
||||
private readonly IFeatureFlagServiceClient _featureFlagServiceClient;
|
||||
|
||||
public OpportunityWorkbooksController(IOpportunityWorkbookService opportunityWorkbook, IFeatureFlagServiceClient featureFlagServiceClient)
|
||||
{
|
||||
_opportunityWorkbookService = opportunityWorkbook;
|
||||
_featureFlagServiceClient = featureFlagServiceClient;
|
||||
}
|
||||
|
||||
[HttpGet("{opportunityGuid}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<OpportunityWorkbook>>> GetOpportunityWorkbooks([FromRoute] Guid opportunityGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await _opportunityWorkbookService.GetOpportunityWorkbooksAsync(opportunityGuid, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<Guid>> CreateOpportunityWorkbooks([FromBody] IEnumerable<OpportunityWorkbook> opportunityWorkbooks, CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await _opportunityWorkbookService.CreateOpportunityWorkbooksAsync(opportunityWorkbooks, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpDelete("{opportunityGuid}/workbook/{workbookId}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<IActionResult> Delete([FromRoute] Guid opportunityGuid, [FromRoute] int workbookId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await _opportunityWorkbookService.ContainsAsync(opportunityGuid, workbookId, cancellationToken))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
await _opportunityWorkbookService.DeleteAsync(opportunityGuid, workbookId, cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("strategic/{opportunityId}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<StrategicOpportunityWorkbookDto>>> GetStrategicOpportunityWorkbooks([FromRoute] long opportunityId, CancellationToken cancellationToken)
|
||||
{
|
||||
var workbooks = await _opportunityWorkbookService.GetStrategicOpportunityWorkbooksAsync(opportunityId, cancellationToken);
|
||||
if (workbooks == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
return Ok(workbooks);
|
||||
}
|
||||
|
||||
[HttpPost("strategic")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<StrategicOpportunityWorkbookDto>>> CreateStrategicOpportunityWorkbooks([FromBody] IEnumerable<StrategicOpportunityWorkbook> opportunityWorkbooks, CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await _opportunityWorkbookService.CreateStrategicOpportunityWorkbooksAsync(opportunityWorkbooks, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpDelete("strategic/{opportunityId}/workbook/{workbookId}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<IActionResult> Delete([FromRoute] long opportunityId, [FromRoute] int workbookId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await _opportunityWorkbookService.ContainsStrategicAsync(opportunityId, workbookId, cancellationToken))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
await _opportunityWorkbookService.DeleteStrategicOpportunityAsync(opportunityId, workbookId, cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public async Task<bool> OpportunityWorkbooksEnabled(CancellationToken cancellationToken)
|
||||
=> await _featureFlagServiceClient.IsEnabledAsync("ciopportunityworkbooks", User?.GetDatabaseGuid() ?? Guid.Empty, cancellationToken: cancellationToken);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ApiLib.Standard.Models;
|
||||
using Strata.ContinuousImprovement.Api.Generics;
|
||||
using Strata.ContinuousImprovement.Biz.Dtos;
|
||||
using Strata.ContinuousImprovement.Biz.Initiatives;
|
||||
using Strata.ContinuousImprovement.Biz.Pagination;
|
||||
using Strata.ContinuousImprovement.Biz.Payroll.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.Payroll.Opportunities.Filters;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using Strata.FeatureFlags.Client;
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class PayrollOpportunitiesController : OpportunityController<PROpportunity>
|
||||
{
|
||||
private readonly IFeatureFlagServiceClient _featureFlagServiceClient;
|
||||
|
||||
public PayrollOpportunitiesController(IPROpportunityService opportunityService, IFeatureFlagServiceClient featureFlagServiceClient)
|
||||
: base(opportunityService)
|
||||
{
|
||||
_featureFlagServiceClient = featureFlagServiceClient;
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
public async Task<PagedApiResponse<PROpportunity>> GetOpportunities(PROpportunityLoadOptions loadOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
return await OpportunityService.GetPagedAsync(loadOptions.PagingOptions, loadOptions.Filters, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
public async Task<IActionResult> Export(PROpportunityExportOptions exportOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
var workbook = await ((IPROpportunityService)OpportunityService).ExportAsync(exportOptions.SortOptions, exportOptions.Filters, cancellationToken);
|
||||
return new ExcelContentResult(workbook, "PR_Opportunities");
|
||||
|
||||
}
|
||||
|
||||
[HttpGet("{configurationGuid}/[action]")]
|
||||
public async Task<PRFilterOptions> GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
return await ((IPROpportunityService)OpportunityService).GetFilterOptionsAsync(configurationGuid, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<Guid>> Create(PROpportunitySaveModel opportunity, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
opportunity.AuthorGuid = User.GetUserGuid();
|
||||
opportunity.AuthorName = User.Identity.Name;
|
||||
|
||||
var newOpportunityId = await ((IPROpportunityService)OpportunityService).CreateAsync(opportunity, cancellationToken);
|
||||
return Ok(newOpportunityId);
|
||||
}
|
||||
|
||||
[HttpDelete("{id}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<bool> Delete(Guid id, CancellationToken cancellationToken)
|
||||
=> await OpportunityService.DeleteAsync<PROpportunityDataModel>(id, cancellationToken);
|
||||
|
||||
[HttpGet("{opportunityGuid}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<PROpportunityDto>> GetById(Guid opportunityGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
var opportunity = await ((IPROpportunityService)OpportunityService).GetPROpportunityById(opportunityGuid, cancellationToken);
|
||||
if (opportunity == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
return Ok(opportunity.Mapped());
|
||||
}
|
||||
|
||||
[HttpGet("{opportunityGuid}/[action]")]
|
||||
public async Task<ActionResult<InitiativeInfo>> GetInitiativeInfo(Guid opportunityGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
return await ((IPROpportunityService)OpportunityService).GetInitiativeInfoAsync(opportunityGuid, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public async Task<bool> TempoPageEnabled(CancellationToken cancellationToken)
|
||||
{
|
||||
return await _featureFlagServiceClient.IsEnabledAsync("enablepayrollopportunityintempo", User?.GetDatabaseGuid() ?? Guid.Empty, cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class PROpportunityLoadOptions
|
||||
{
|
||||
public PagingOptions PagingOptions { get; set; }
|
||||
public PRFilters Filters { get; set; }
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class PROpportunityExportOptions
|
||||
{
|
||||
public SortOptions SortOptions { get; set; }
|
||||
public PRFilters Filters { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Api.Generics;
|
||||
using Strata.ContinuousImprovement.Biz.PeerGroups;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class PeerGroupController : ControllerBase
|
||||
{
|
||||
private readonly IPeerGroupService _peerGroupService;
|
||||
|
||||
public PeerGroupController(IPeerGroupService peerGroupService)
|
||||
{
|
||||
_peerGroupService = peerGroupService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IEnumerable<PeerGroupDto>> GetPeerGroupsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return await _peerGroupService.GetAllAsync(cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("{peerGroupId}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<PeerGroupDto>> GetByIdAsync([FromRoute] int peerGroupId, CancellationToken cancellationToken)
|
||||
{
|
||||
var peerGroup = await _peerGroupService.GetByIdAsync(peerGroupId, cancellationToken);
|
||||
if (peerGroup != null)
|
||||
{
|
||||
return Ok(peerGroup);
|
||||
}
|
||||
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public ActionResult<PeerGroupDropdownItems> GetDropdownItems()
|
||||
{
|
||||
var dropdownItems = _peerGroupService.GetDropdownItems();
|
||||
if (dropdownItems != null)
|
||||
{
|
||||
return Ok(dropdownItems);
|
||||
}
|
||||
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
[HttpPost("[action]/{peerGroupId}")]
|
||||
public async Task<bool> IsNameValid([FromRoute] int peerGroupId, [FromBody] string name, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _peerGroupService.IsNameValid(name, cancellationToken, peerGroupId);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[Authorize(Policy = Policies.IsAdmin)]
|
||||
public async Task<ActionResult<bool>> Create(PeerGroupDto createInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await _peerGroupService.IsNameValid(createInfo.Name, cancellationToken))
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
return await _peerGroupService.CreateAsync(createInfo, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPut("{peerGroupId}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
[Authorize(Policy = Policies.IsAdmin)]
|
||||
public async Task<ActionResult<bool>> Update([FromRoute] int peerGroupId, [FromBody] PeerGroupDto dto, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await _peerGroupService.IsNameValid(dto.Name, cancellationToken, peerGroupId))
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
if (!await _peerGroupService.ContainsAsync(peerGroupId, cancellationToken))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return await _peerGroupService.UpdateAsync(peerGroupId, dto, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpDelete("{peerGroupId}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
[Authorize(Policy = Policies.IsAdmin)]
|
||||
public async Task<IActionResult> Delete([FromRoute] int peerGroupId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await _peerGroupService.ContainsAsync(peerGroupId, cancellationToken))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
await _peerGroupService.DeleteAsync(peerGroupId, cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Biz.PickerItems;
|
||||
using Strata.ContinuousImprovement.Biz.Population;
|
||||
using Strata.ContinuousImprovement.Biz.Shared;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class PickerItemsController : ControllerBase
|
||||
{
|
||||
private readonly IPickerItemService _pickerItemService;
|
||||
|
||||
public PickerItemsController(IPickerItemService pickerItemService)
|
||||
{
|
||||
_pickerItemService = pickerItemService;
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<PickerItem>>> GetExplorations(CancellationToken cancellationToken)
|
||||
{
|
||||
var items = await _pickerItemService.GetItems<ExplorationPopulation>((item) => new PickerItem() { Text = item.Name, Value = item.ExplorationPopulationId }, cancellationToken);
|
||||
return Ok(items);
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ApiLib.Standard.Models;
|
||||
using Strata.ContinuousImprovement.Biz.Pagination;
|
||||
using Strata.ContinuousImprovement.Biz.QualityVariation.Breakdowns;
|
||||
using Strata.ContinuousImprovement.Biz.QualityVariation.Breakdowns.Filters;
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class QualityVariationItemBreakdownsController : ControllerBase
|
||||
{
|
||||
private readonly IQVIBreakdownService _qviBreakdownService;
|
||||
|
||||
public QualityVariationItemBreakdownsController(IQVIBreakdownService qviBreakdownService)
|
||||
{
|
||||
_qviBreakdownService = qviBreakdownService;
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
public async Task<PagedApiResponse<QVIBreakdown>> GetBreakdowns(QVIBreakdownsLoadOptions loadOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _qviBreakdownService.GetPagedAsync(loadOptions.PagingOptions, loadOptions.Filters, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("{configurationGuid}/[action]")]
|
||||
public async Task<QVIBreakdownFilterOptions> GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _qviBreakdownService.GetFilterOptionsAsync(configurationGuid, cancellationToken);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class QVIBreakdownsLoadOptions
|
||||
{
|
||||
public PagingOptions PagingOptions { get; set; }
|
||||
public QVIBreakdownFilters Filters { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ApiLib.Standard.Models;
|
||||
using Strata.ContinuousImprovement.Biz.Pagination;
|
||||
using Strata.ContinuousImprovement.Biz.QualityVariation.Events;
|
||||
using Strata.ContinuousImprovement.Biz.QualityVariation.Events.Filters;
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class QualityVariationItemEventsController : ControllerBase
|
||||
{
|
||||
private readonly IQVIEventService _qviEventService;
|
||||
|
||||
public QualityVariationItemEventsController(IQVIEventService qviEventService)
|
||||
{
|
||||
_qviEventService = qviEventService;
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
public async Task<PagedApiResponse<QVIEvent>> GetEvents(QVIEventsLoadOptions loadOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _qviEventService.GetPagedAsync(loadOptions.PagingOptions, loadOptions.Filters, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("{configurationGuid}/[action]")]
|
||||
public async Task<QVIEventFilterOptions> GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _qviEventService.GetFilterOptionsAsync(configurationGuid, cancellationToken);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class QVIEventsLoadOptions
|
||||
{
|
||||
public PagingOptions PagingOptions { get; set; }
|
||||
public QVIEventFilters Filters { get; set; }
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
using Hangfire;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ApiLib.Standard.Models;
|
||||
using Strata.ContinuousImprovement.Api.Generics;
|
||||
using Strata.ContinuousImprovement.Biz.Pagination;
|
||||
using Strata.ContinuousImprovement.Biz.QualityVariation.Jobs;
|
||||
using Strata.ContinuousImprovement.Biz.QualityVariation.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.QualityVariation.Opportunities.Filters;
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class QualityVariationItemOpportunitiesController : OpportunityController<QVIOpportunity>
|
||||
{
|
||||
private readonly IBackgroundJobClient _backgroundJobClient;
|
||||
|
||||
public QualityVariationItemOpportunitiesController(
|
||||
IQVIOpportunityService opportunityService,
|
||||
IBackgroundJobClient backgroundJobClient)
|
||||
: base(opportunityService)
|
||||
{
|
||||
_backgroundJobClient = backgroundJobClient;
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
public async Task<PagedApiResponse<QVIOpportunity>> GetOpportunities(QVIOpportunityLoadOptions loadOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
return await OpportunityService.GetPagedAsync(loadOptions.PagingOptions, loadOptions.Filters, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
public async Task<IActionResult> Export(QVIOpportunityExportOptions exportOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
var workbook = await ((IQVIOpportunityService)OpportunityService).ExportAsync(exportOptions.SortOptions, exportOptions.Filters, cancellationToken);
|
||||
return new ExcelContentResult(workbook, "QVI_Opportunities");
|
||||
}
|
||||
|
||||
[HttpGet("{configurationGuid}/[action]")]
|
||||
public async Task<QVIFilterOptions> GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
return await ((IQVIOpportunityService)OpportunityService).GetFilterOptionsAsync(configurationGuid, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public IActionResult PopulateQviMetrics(Guid configurationGuid, bool isBaseline, CancellationToken cancellationToken)
|
||||
{
|
||||
if (configurationGuid == Guid.Empty)
|
||||
{
|
||||
return BadRequest("Invalid request. ConfigurationGuid is required.");
|
||||
}
|
||||
|
||||
var jobId = _backgroundJobClient.Enqueue<QviOpportunityMetricsJob>(job =>
|
||||
job.PopulateQviMetricsAsync(configurationGuid, isBaseline, cancellationToken));
|
||||
|
||||
return Ok(jobId);
|
||||
}
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class QVIOpportunityLoadOptions
|
||||
{
|
||||
public PagingOptions PagingOptions { get; set; }
|
||||
public QVIFilters Filters { get; set; }
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class QVIOpportunityExportOptions
|
||||
{
|
||||
public SortOptions SortOptions { get; set; }
|
||||
public QVIFilters Filters { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Biz.DbContexts.Reports;
|
||||
using Strata.ContinuousImprovement.Biz.Reports;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using Strata.Stratasphere.Biz.DbContexts;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class ReportController : Controller
|
||||
{
|
||||
private readonly IReportService _reportService;
|
||||
|
||||
public ReportController(IReportService reportService)
|
||||
{
|
||||
_reportService = reportService;
|
||||
}
|
||||
|
||||
[HttpGet("{sourceOfSavings}/[action]")]
|
||||
public async Task<IEnumerable<REReport>> GetByModule(int sourceOfSavings, CancellationToken cancellationToken)
|
||||
{
|
||||
var userGuid = User?.GetUserGuid() ?? Guid.Empty;
|
||||
return await _reportService.GetReportsByModule(sourceOfSavings, userGuid, User.IsSdtEmployee(), cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("{opportunityGuid:guid}/[action]")]
|
||||
public async Task<IEnumerable<REReport>> GetByOpportunity(Guid opportunityGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
var userGuid = User?.GetUserGuid() ?? Guid.Empty;
|
||||
return await _reportService.GetReportsByOpportunity(opportunityGuid, userGuid, User.IsSdtEmployee(), cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
public async Task<IEnumerable<REReport>> GetReports(ReportRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var userGuid = User?.GetUserGuid() ?? Guid.Empty;
|
||||
var moduleRpts = await _reportService.GetReportsByModule(request.SourceOfSavings, userGuid, User.IsSdtEmployee(), cancellationToken);
|
||||
var opportunityRpts = await _reportService.GetReportsByOpportunity(request.OpportunityGuid, userGuid, User.IsSdtEmployee(), cancellationToken);
|
||||
return moduleRpts.Union(opportunityRpts);
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public async Task<IEnumerable<REReport>> GetReports(CancellationToken cancellationToken)
|
||||
{
|
||||
var userGuid = User?.GetUserGuid() ?? Guid.Empty;
|
||||
return await _reportService.GetReports(userGuid, User.IsSdtEmployee(), cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
public async Task AddByOpportunity(ReportByOpportunity reportRequest, CancellationToken cancellationToken)
|
||||
=> await _reportService.AddReportByOpportunity(reportRequest.OpportunityGuid, reportRequest.ReportGuid, cancellationToken);
|
||||
|
||||
[HttpPost("[action]")]
|
||||
public async Task AddByModule(ReportByModule reportRequest, CancellationToken cancellationToken)
|
||||
=> await _reportService.AddReportByModule(reportRequest.SourceOfSavings, reportRequest.ReportGuid, cancellationToken);
|
||||
|
||||
[HttpDelete("[action]")]
|
||||
public async Task RemoveByOpportunity(ReportByOpportunity reportRequest, CancellationToken cancellationToken)
|
||||
=> await _reportService.DeleteReportByOpportunity(reportRequest.OpportunityGuid, reportRequest.ReportGuid, cancellationToken);
|
||||
|
||||
[HttpDelete("[action]")]
|
||||
public async Task RemoveByModule(ReportByModule reportRequest, CancellationToken cancellationToken)
|
||||
=> await _reportService.DeleteReportByModule(reportRequest.SourceOfSavings, reportRequest.ReportGuid, cancellationToken);
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public async Task<FolderTree> GetFolders(CancellationToken cancellationToken)
|
||||
=> await _reportService.GetReportFolders(cancellationToken);
|
||||
|
||||
[HttpGet("{folderGuid:guid}[action]")]
|
||||
public async Task<IEnumerable<REReport>> GetFolderReports(Guid folderGuid, CancellationToken cancellationToken)
|
||||
=> await _reportService.GetFolderReports(folderGuid, cancellationToken);
|
||||
|
||||
public class ReportRequest
|
||||
{
|
||||
public Guid OpportunityGuid { get; set; }
|
||||
public int SourceOfSavings { get; set; }
|
||||
}
|
||||
|
||||
public class ReportByOpportunity
|
||||
{
|
||||
public Guid ReportGuid { get; set; }
|
||||
public Guid OpportunityGuid { get; set; }
|
||||
}
|
||||
|
||||
public class ReportByModule
|
||||
{
|
||||
public Guid ReportGuid { get; set; }
|
||||
public int SourceOfSavings { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.Authorization;
|
||||
using Strata.ContinuousImprovement.Biz.Security;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class SecurityController : ControllerBase
|
||||
{
|
||||
private readonly ISimpleSecurityService _securityService;
|
||||
private readonly IAuthorizationServiceClient _authorizationServiceClient;
|
||||
private readonly Guid CIGeneralAccessLootId = Guid.Parse("33F10831-E42E-4D48-A6FA-A110798B4F63");
|
||||
|
||||
public SecurityController(ISimpleSecurityService securityService,
|
||||
IAuthorizationServiceClient authorizationServiceClient)
|
||||
{
|
||||
_securityService = securityService;
|
||||
_authorizationServiceClient = authorizationServiceClient;
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public async Task<bool> GetGeneralAccessPermission(CancellationToken cancellationToken)
|
||||
=> (await _authorizationServiceClient.GetPermissionAsync(CIGeneralAccessLootId, cancellationToken)).CanRead;
|
||||
|
||||
[HttpGet("{lootId}")]
|
||||
public async Task<Permission> GetUserPermissions(Guid lootId, CancellationToken cancellationToken)
|
||||
=> await _authorizationServiceClient.GetPermissionAsync(lootId, cancellationToken);
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public async Task<IEnumerable<IdentityNode>> GetIdentities(CancellationToken cancellationToken)
|
||||
=> await _securityService.GetAllIdentities(cancellationToken);
|
||||
|
||||
[HttpGet("{lootId}/[action]")]
|
||||
public async Task<IEnumerable<Permission>> GetSimpleSecurities(Guid lootId, CancellationToken cancellationToken)
|
||||
=> await _authorizationServiceClient.GetUserPermissionsAsync(lootId, cancellationToken);
|
||||
|
||||
[HttpPut("{lootId}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<bool>> SetSimpleSecurities(Guid lootId, [FromBody] IEnumerable<Permission> permissionSettings, CancellationToken cancellationToken)
|
||||
{
|
||||
if (permissionSettings.Any(ps => !ps.LootId.HasValue || ps.LootId.Value != lootId)) { return BadRequest(); }
|
||||
var isSecurable = await _authorizationServiceClient.IsSecurable(lootId, cancellationToken);
|
||||
|
||||
var userGuid = User?.GetUserGuid() ?? Guid.NewGuid();
|
||||
if (!User.IsSdtEmployee() && !isSecurable) return Ok(false);
|
||||
await _authorizationServiceClient.ReplaceUserPermissionsAsync(lootId, permissionSettings, cancellationToken);
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Strata.ContinuousImprovement.Biz.ChargeCode;
|
||||
using Strata.ContinuousImprovement.Biz.FiscalMonths;
|
||||
using Strata.ContinuousImprovement.Biz.PerformanceEngine;
|
||||
using Strata.ContinuousImprovement.Biz.Settings;
|
||||
using Strata.ContinuousImprovement.Biz.Shared;
|
||||
using Strata.ContinuousImprovement.Biz.Utilities;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using Strata.DataSchema.Client;
|
||||
using Strata.FeatureFlags.Client;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class SettingsController : Controller
|
||||
{
|
||||
private readonly ISettingsService _settingsService;
|
||||
private ChargeCodeOpportunityValidationSection _validationSection;
|
||||
private readonly IDataSchemaService _dataSchemaService;
|
||||
private readonly IFeatureFlagServiceClient _featureFlagServiceClient;
|
||||
|
||||
public SettingsController(ISettingsService settingsService,
|
||||
IOptions<ChargeCodeOpportunityValidationSection> sectionAccessor,
|
||||
IDataSchemaService dataSchemaService,
|
||||
IFeatureFlagServiceClient featureFlagServiceClient)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_validationSection = sectionAccessor.Value ?? throw new ArgumentNullException(nameof(ChargeCodeOpportunityValidationSection));
|
||||
_dataSchemaService = dataSchemaService;
|
||||
_featureFlagServiceClient = featureFlagServiceClient;
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public async Task<Dictionary<string, bool>> GetFeatureFlagsAsync()
|
||||
{
|
||||
var databaseGuid = User?.GetDatabaseGuid() ?? Guid.Empty;
|
||||
var featureFlags = await _featureFlagServiceClient.GetFeatureFlagClientStatesByArea(databaseGuid, "CI", CancellationToken.None);
|
||||
return featureFlags.ToDictionary(key => key.Key.Replace("-", ""), val => val.Enabled);
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
public async Task<Dictionary<string, string>> GetSystemSettings([FromBody] List<string> settings, CancellationToken cancellationToken)
|
||||
{
|
||||
var systemSettings = await _settingsService.GetSystemSettings(settings, cancellationToken);
|
||||
return systemSettings;
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public int GetOpportunityLevelLimit()
|
||||
{
|
||||
return _validationSection.MaxOpportunityLevelSelections;
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public int GetChargeCodeLimit()
|
||||
{
|
||||
return _validationSection.MaxChargeCodeSelections;
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public PerformanceEngingeCalculationData GetPerformanceEngineCalculations()
|
||||
{
|
||||
// this is stubbed with test data taken from the Performance Engine tests to populate the calculations page
|
||||
// we will need to modify this to take inputs from the wizards and return a PerformanceTrackingItem
|
||||
|
||||
var currentYear = DateTime.Now.Year;
|
||||
|
||||
double[] goalUnits = { 13221, 17628, 22035, 22035, 8814, 26442, 8814, 13221, 22035, 17628, 30849, 44070 };
|
||||
double[] baselineUnits = { 12231, 16788, 21605, 21305, 8430, 24648, 8172, 11847, 24535, 16704, 26159, 45000 };
|
||||
double[] baselineStats = { 3, 4, 5, 5, 2, 6, 2, 3, 5, 4, 7, 10 };
|
||||
double[] trackingUnits = { 54314, 8798, 31680, 21415, 25895, 0, 0, 0, 0, 0, 0, 0 };
|
||||
double[] trackingStats = { 13, 2, 8, 5, 5, 0, 0, 0, 0, 0, 0, 0 };
|
||||
|
||||
MonthlyRampUp[] rampUps =
|
||||
{
|
||||
new MonthlyRampUp(1, currentYear, 100), new MonthlyRampUp(2, currentYear, 100), new MonthlyRampUp(3, currentYear, 100), new MonthlyRampUp(4, currentYear, 100),
|
||||
new MonthlyRampUp(5, currentYear, 100),new MonthlyRampUp(6, currentYear, 100),new MonthlyRampUp(7, currentYear, 100),new MonthlyRampUp(8, currentYear, 100),
|
||||
new MonthlyRampUp(9, currentYear, 100),new MonthlyRampUp(10, currentYear, 100),new MonthlyRampUp(11, currentYear, 100),new MonthlyRampUp(12, currentYear, 100)
|
||||
};
|
||||
|
||||
var trackingYearMonths = Enumerable.Range(1, 12).ToList()
|
||||
.ConvertAll(i =>
|
||||
{
|
||||
var ym = new TrackingYearMonth(currentYear, currentYear,
|
||||
new FiscalMonth(Guid.NewGuid(), (byte)i, (byte)i, $"{i}", "", "", 0, 0, $"Month{i:00}", 0, 0), true, true, true);
|
||||
return ym;
|
||||
}).ToList();
|
||||
|
||||
var performanceBaselineData = PerformanceComparisonData.SetupBaselineDataList(baselineUnits, baselineStats, currentYear - 1);
|
||||
var performanceTrackingData = PerformanceComparisonData.SetupTrackingDataList(goalUnits, trackingUnits, trackingStats, currentYear);
|
||||
var group = new PerformanceGroup(1, "testCalculations");
|
||||
var options = new PerformanceOptions(false, true, true);
|
||||
|
||||
var performanceTrackingItem = new PerformanceTrackingItem(group, rampUps, options, performanceBaselineData, performanceTrackingData, trackingYearMonths);
|
||||
var data = new PerformanceEngingeCalculationData(performanceTrackingItem);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
public class PerformanceEngingeCalculationData
|
||||
{
|
||||
// inputs
|
||||
public string entity { get; set; }
|
||||
public string caseTypeEntity { get; set; }
|
||||
public int serviceLineID { get; set; }
|
||||
public int physicianGroupingAffiliationId { get; set; }
|
||||
public string primaryPhysician { get; set; }
|
||||
public string chargeCodeRollup { get; set; }
|
||||
public string costDriver { get; set; }
|
||||
public string baselineStartDate { get; set; }
|
||||
public string baselineEndDate { get; set; }
|
||||
|
||||
// numbers fed into calcs
|
||||
public double totalVolume { get; set; }
|
||||
public double totalGrossCharges { get; set; }
|
||||
public double totalVariableDirectCost { get; set; }
|
||||
public double totalVariableCost { get; set; }
|
||||
|
||||
// calculated results
|
||||
public double goalUnitVariance { get; set; }
|
||||
public double actualUnitVariance { get; set; }
|
||||
public double committedUnits { get; set; }
|
||||
public double committedStats { get; set; }
|
||||
public double baselineAverage { get; set; }
|
||||
|
||||
public PerformanceEngingeCalculationData(PerformanceTrackingItem item)
|
||||
{
|
||||
entity = "n/a";
|
||||
caseTypeEntity = "n/a";
|
||||
serviceLineID = -1;
|
||||
physicianGroupingAffiliationId = -1;
|
||||
primaryPhysician = "n/a";
|
||||
chargeCodeRollup = "n/a";
|
||||
costDriver = "n/a";
|
||||
baselineStartDate = DateTime.Now.ToShortDateString();
|
||||
baselineEndDate = DateTime.Now.ToShortDateString();
|
||||
|
||||
totalVolume = -1;
|
||||
totalGrossCharges = -1;
|
||||
totalVariableDirectCost = -1;
|
||||
totalVariableCost = -1;
|
||||
|
||||
goalUnitVariance = item.MonthlyTrackingData.Sum(month => month.GoalUnitVariance);
|
||||
actualUnitVariance = item.MonthlyTrackingData.Sum(month => month.ActualUnitVariance);
|
||||
committedUnits = item.MonthlyTrackingData.Sum(month => month.CommittedUnits);
|
||||
committedStats = item.MonthlyTrackingData.Sum(month => month.CommittedStats);
|
||||
baselineAverage = item.MonthlyTrackingData.Average(month => month.BaselineAverage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Biz.StrategicOpportunities;
|
||||
using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class StrategicChargeCodeController : ControllerBase
|
||||
{
|
||||
private readonly IStrategicOpportunityService _strategicOpportunityService;
|
||||
private readonly IStrategicChargeCodeOpportunityDetailService _strategicChargeCodeOpportunityDetailService;
|
||||
|
||||
public StrategicChargeCodeController(IStrategicOpportunityService strategicOpportunityService, IStrategicChargeCodeOpportunityDetailService strategicChargeCodeOpportunityDetailService)
|
||||
{
|
||||
_strategicOpportunityService = strategicOpportunityService;
|
||||
_strategicChargeCodeOpportunityDetailService = strategicChargeCodeOpportunityDetailService;
|
||||
}
|
||||
|
||||
[HttpGet("{opportunityId}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<StrategicOpportunityChargeCodeEditData>> GetOpportunityChargeCode(long opportunityId, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _strategicOpportunityService.GetStrategicOpportunityChargeCode(opportunityId, cancellationToken);
|
||||
return result != null ? Ok(result) : NotFound();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<long>> CreateOpportunityChargeCode([FromBody] StrategicOpportunityChargeCodeDto opportunity, CancellationToken cancellationToken)
|
||||
{
|
||||
var opportunityId = await _strategicOpportunityService.CreateOpportunityChargeCodeAsync(opportunity, cancellationToken);
|
||||
if (opportunityId == 0) return BadRequest();
|
||||
return Ok(opportunityId);
|
||||
}
|
||||
|
||||
[HttpPut("{opportunityId}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<bool>> UpdateOpportunityChargeCode(long opportunityId, [FromBody] StrategicOpportunityChargeCodeUpdateDto opportunity, CancellationToken cancellationToken)
|
||||
{
|
||||
var strategicOpportunityId = await _strategicOpportunityService.UpdateOpportunityChargeCode(opportunityId, opportunity, cancellationToken);
|
||||
return strategicOpportunityId != 0 ? Ok(true) : NotFound();
|
||||
}
|
||||
|
||||
[HttpDelete("{opportunityId}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<IActionResult> DeleteOpportunityChargeCode(long opportunityId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await _strategicOpportunityService.ContainsAsync(opportunityId, cancellationToken))
|
||||
return NotFound();
|
||||
|
||||
var isDeleted = await _strategicOpportunityService.DeleteChargeCodeOpportunityAsync(opportunityId, cancellationToken);
|
||||
return isDeleted ? NoContent() : BadRequest();
|
||||
}
|
||||
|
||||
[HttpPost("{opportunityId}/goal-detail")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<StrategicOpportunityDetailData>> GetGoalDetail(long opportunityId, [FromBody] StrategicOpportunityGetDetailData getData, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _strategicChargeCodeOpportunityDetailService.GetStrategicOpportunityDetailData(opportunityId, false, getData, cancellationToken);
|
||||
return result != null ? Ok(result) : NotFound();
|
||||
}
|
||||
|
||||
[HttpPost("{opportunityId}/export")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status202Accepted)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> ExportInitiative(long opportunityId, [FromBody] StrategicOpportunityGetDetailData getData, CancellationToken cancellationToken)
|
||||
{
|
||||
var workbook = await _strategicChargeCodeOpportunityDetailService.ExportInitiativeAsync(opportunityId, true, getData, cancellationToken);
|
||||
if (workbook == null)
|
||||
return NotFound();
|
||||
return new ExcelContentResult(workbook, $"Strategic Initiative Charge Code {opportunityId} Export");
|
||||
}
|
||||
|
||||
[HttpPost("{opportunityId}/export-opportunity")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> ExportOpportunity(long opportunityId, [FromBody] StrategicOpportunityGetDetailData getData, CancellationToken cancellationToken)
|
||||
{
|
||||
var workbook = await _strategicChargeCodeOpportunityDetailService.ExportOpportunityAsync(opportunityId, getData, cancellationToken);
|
||||
if (workbook == null)
|
||||
return NotFound();
|
||||
return new ExcelContentResult(workbook, $"Strategic Opportunity {opportunityId} Export");
|
||||
}
|
||||
|
||||
[HttpPost("{opportunityId}/tracking-detail")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<StrategicOpportunityDetailData>> GetTrackingDetail(long opportunityId, [FromBody] StrategicOpportunityGetDetailData getData, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _strategicChargeCodeOpportunityDetailService.GetStrategicOpportunityDetailData(opportunityId, true, getData, cancellationToken);
|
||||
return result != null ? Ok(result) : NotFound();
|
||||
}
|
||||
|
||||
[HttpPut("{opportunityId}/details")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult> UpdateDetails(long opportunityId, [FromBody] UpdateDetailsDto dto, CancellationToken cancellationToken)
|
||||
{
|
||||
if (dto == null || dto.Adjustments?.Any() != true || dto.Rows?.Any() != true)
|
||||
return BadRequest();
|
||||
|
||||
await _strategicChargeCodeOpportunityDetailService.UpdateStrategicOpportunityDetailsData(opportunityId, dto.Adjustments, dto.Rows, cancellationToken);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
public class UpdateDetailsDto
|
||||
{
|
||||
public IEnumerable<StrategicOpportunityChargeCodeAdjustment> Adjustments { get; set; }
|
||||
public IEnumerable<string> Rows { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Biz.StrategicOpportunities;
|
||||
using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class StrategicGeneralLedgerController : ControllerBase
|
||||
{
|
||||
private readonly IStrategicOpportunityService _strategicOpportunityService;
|
||||
private readonly IStrategicGLOpportunityDetailService _strategicGLOpportunityDetailService;
|
||||
|
||||
public StrategicGeneralLedgerController(IStrategicOpportunityService strategicOpportunityService, IStrategicGLOpportunityDetailService strategicGLOpportunityDetailService)
|
||||
{
|
||||
_strategicOpportunityService = strategicOpportunityService;
|
||||
_strategicGLOpportunityDetailService = strategicGLOpportunityDetailService;
|
||||
}
|
||||
|
||||
[HttpGet("{opportunityId}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<StrategicOpportunityGLEditData>> GetOpportunityGL(long opportunityId, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _strategicOpportunityService.GetStrategicOpportunityGL(opportunityId, cancellationToken);
|
||||
return result != null ? Ok(result) : NotFound();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<long>> CreateOpportunityGeneralLedger([FromBody] StrategicOpportunityGLDto opportunity, CancellationToken cancellationToken)
|
||||
{
|
||||
var opportunityId = await _strategicOpportunityService.CreateOpportunityGeneralLedgerAsync(opportunity, cancellationToken);
|
||||
if (opportunityId == 0) return BadRequest();
|
||||
return Ok(opportunityId);
|
||||
}
|
||||
|
||||
[HttpPut("{opportunityId}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<bool>> UpdateOpportunityGeneralLedger(long opportunityId, [FromBody] StrategicOpportunityGLUpdateDto opportunity, CancellationToken cancellationToken)
|
||||
{
|
||||
var strategicOpportunityId = await _strategicOpportunityService.UpdateOpportunityGeneralLedger(opportunityId, opportunity, cancellationToken);
|
||||
return strategicOpportunityId != 0 ? Ok(true) : NotFound();
|
||||
}
|
||||
|
||||
[HttpDelete("{opportunityId}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<IActionResult> DeleteOpportunityGeneralLedger(long opportunityId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await _strategicOpportunityService.ContainsGLAsync(opportunityId, cancellationToken))
|
||||
return NotFound();
|
||||
|
||||
var isDeleted = await _strategicOpportunityService.DeleteGeneralLedgerOpportunityAsync(opportunityId, cancellationToken);
|
||||
return isDeleted ? NoContent() : BadRequest();
|
||||
}
|
||||
|
||||
[HttpPost("{opportunityId}/detail")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<StrategicOpportunityGLDetailData>> GetGLDetail(long opportunityId, [FromBody] string[] dimensions, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _strategicGLOpportunityDetailService.GetStrategicOpportunityGLDetailData(opportunityId, false, dimensions, cancellationToken);
|
||||
return result != null ? Ok(result) : NotFound();
|
||||
}
|
||||
|
||||
[HttpPost("{opportunityId}/tracking-detail")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<StrategicOpportunityGLDetailData>> GetGLTrackingDetail(long opportunityId, [FromBody] string[] dimensions, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _strategicGLOpportunityDetailService.GetStrategicOpportunityGLDetailData(opportunityId, true, dimensions, cancellationToken);
|
||||
return result != null ? Ok(result) : NotFound();
|
||||
}
|
||||
|
||||
[HttpPut("{opportunityId}/details")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult> UpdateGLDetails(long opportunityId, [FromBody] UpdateGLDetailsDto dto, CancellationToken cancellationToken)
|
||||
{
|
||||
if (dto == null || dto.Adjustments?.Any() != true || dto.Rows?.Any() != true)
|
||||
return BadRequest();
|
||||
|
||||
await _strategicGLOpportunityDetailService.UpdateStrategicOpportunityGLDetailsData(opportunityId, dto.Adjustments, dto.Rows, cancellationToken);
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Biz.StrategicOpportunities;
|
||||
using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class StrategicOpportunityController : ControllerBase
|
||||
{
|
||||
private readonly IStrategicOpportunityService _strategicOpportunityService;
|
||||
|
||||
public StrategicOpportunityController(IStrategicOpportunityService strategicOpportunityService)
|
||||
{
|
||||
_strategicOpportunityService = strategicOpportunityService;
|
||||
}
|
||||
|
||||
[HttpGet("")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<List<StrategicOpportunityItem>> GetOpportunities(CancellationToken cancellationToken)
|
||||
{
|
||||
return await _strategicOpportunityService.GetStrategicOpportunitiesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("{opportunityId}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<StrategicOpportunityGeneralInfo>> GetGeneralInfo(long opportunityId, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _strategicOpportunityService.GetStrategicOpportunityGeneralInfo(opportunityId, cancellationToken);
|
||||
return result != null ? Ok(result) : NotFound();
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
public async Task<bool> IsNameValid([FromBody] string name, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _strategicOpportunityService.NameIsValidAsync(name, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("{opportunityId}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<StrategicOpportunityLayout>> GetLayout([FromRoute] long opportunityId, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _strategicOpportunityService.GetStrategicOpportunityLayout(opportunityId, cancellationToken);
|
||||
return result;
|
||||
}
|
||||
|
||||
[HttpPut("{opportunityId}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<bool>> UpdateLayout(long opportunityId, [FromBody] StrategicOpportunityLayout layout, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _strategicOpportunityService.UpdateStrategicOpportunityLayout(opportunityId, layout, cancellationToken);
|
||||
return result ? Ok(result) : NotFound();
|
||||
}
|
||||
|
||||
[HttpPut("{opportunityId}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<bool>> SetStatus(long opportunityId, [FromBody] StrategicOpportunityUpdateStatusDto updateStatusDto, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _strategicOpportunityService.UpdateStrategicOpportunityStatus(opportunityId, updateStatusDto, cancellationToken);
|
||||
return result ? Ok(result) : NotFound();
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<List<StrategicInitiativeItem>> GetInitiatives(CancellationToken cancellationToken)
|
||||
{
|
||||
return await _strategicOpportunityService.GetStrategicInitiativesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPut("{opportunityId}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<bool>> UpdateInitiative(long opportunityId, [FromBody] StrategicInitiativeUpdateDto updateInitiativeDto, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _strategicOpportunityService.UpdateStrategicInitiative(opportunityId, updateInitiativeDto, cancellationToken);
|
||||
return result ? Ok(result) : NotFound();
|
||||
}
|
||||
|
||||
[HttpPut("{opportunityId}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<bool>> UpdateRoles(long opportunityId, [FromBody] StrategicRolesUpdate updateRolesDto, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _strategicOpportunityService.UpdateRoles(opportunityId, updateRolesDto, cancellationToken);
|
||||
return result ? Ok(result) : NotFound();
|
||||
}
|
||||
|
||||
[HttpPut("{opportunityId}/ramp-ups")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<bool>> UpdateRampUps(long opportunityId, [FromBody] List<StrategicOpportunityRampUpDto> rampUps, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _strategicOpportunityService.UpdateStrategicOpportunityRampUpsAsync(opportunityId, rampUps, cancellationToken);
|
||||
return result ? Ok(result) : NotFound();
|
||||
}
|
||||
|
||||
[HttpGet("{opportunityId}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<List<StrategicOpportunityRampUpDto>>> GetRampUps(long opportunityId, CancellationToken cancellationToken)
|
||||
{
|
||||
var rampUps = await _strategicOpportunityService.GetStrategicOpportunityRampUpsAsync(opportunityId, cancellationToken);
|
||||
return Ok(rampUps);
|
||||
}
|
||||
|
||||
[HttpGet("{opportunityId}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<bool>> RunTrackingTask(long opportunityId, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _strategicOpportunityService.RunTrackingTask(opportunityId, cancellationToken);
|
||||
return result ? Ok(result) : NotFound();
|
||||
}
|
||||
|
||||
[HttpPut("{opportunityId}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<bool>> CompleteInitiative(long opportunityId, [FromBody] StrategicOpportunityCompleteInitiativeData completeInitiativeData, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _strategicOpportunityService.CompleteInitiative(opportunityId, completeInitiativeData, cancellationToken);
|
||||
return result ? Ok(result) : NotFound();
|
||||
}
|
||||
|
||||
[HttpGet("{opportunityId}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<List<StrategicOpportunityMeasureDto>>> GetMeasures(long opportunityId, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _strategicOpportunityService.GetStrategicOpportunityMeasures(opportunityId, cancellationToken);
|
||||
return result.Any() ? Ok(result) : NotFound();
|
||||
}
|
||||
|
||||
[HttpGet("{opportunityId}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<StrategicOpportunityMeasureDto>> GetPrimaryMeasure(long opportunityId, CancellationToken cancellationToken)
|
||||
{
|
||||
var primaryMeasure = await _strategicOpportunityService.GetStrategicOpportunityPrimaryMeasure(opportunityId, cancellationToken);
|
||||
return Ok(primaryMeasure);
|
||||
}
|
||||
|
||||
[HttpGet("{measureId}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<List<StrategicOpportunityMeasureDetail>>> GetMeasureDetails(long measureId, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _strategicOpportunityService.GetStrategicOpportunityMeasureDetails(measureId, cancellationToken);
|
||||
return result != null ? Ok(result) : NotFound();
|
||||
}
|
||||
|
||||
[HttpGet("{opportunityId}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<StrategicOpportunityDrawerDetails>> GetDrawerDetails(long opportunityId, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _strategicOpportunityService.GetStrategicOpportunityDrawerDetails(opportunityId, cancellationToken);
|
||||
return result != null ? Ok(result) : NotFound();
|
||||
}
|
||||
|
||||
[HttpPost("{opportunityId}/copy")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<long>> Copy(long opportunityId, [FromBody] string opportunityName, CancellationToken cancellationToken)
|
||||
{
|
||||
var newOpportunityId = await _strategicOpportunityService.CopyStrategicOpportunity(opportunityId, opportunityName, cancellationToken);
|
||||
return newOpportunityId == 0 ? BadRequest() : Ok(newOpportunityId);
|
||||
}
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Biz.OpportunityRoleAssignment;
|
||||
using Strata.ContinuousImprovement.Biz.StrategicOpportunities;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class StrategicOpportunitySecurityController : ControllerBase
|
||||
{
|
||||
private readonly IStrategicOpportunityService _strategicOpportunityService;
|
||||
private readonly IOpportunityRoleAssignmentService _opportunityRoleAssignmentService;
|
||||
|
||||
public StrategicOpportunitySecurityController(IStrategicOpportunityService strategicOpportunityService, IOpportunityRoleAssignmentService opportunityRoleAssignmentService)
|
||||
{
|
||||
_strategicOpportunityService = strategicOpportunityService;
|
||||
_opportunityRoleAssignmentService = opportunityRoleAssignmentService;
|
||||
}
|
||||
|
||||
[HttpGet("{lootId}")]
|
||||
public async Task<RoleAssignmentView> GetUserPermissions(long lootId, CancellationToken cancellationToken)
|
||||
=> await _opportunityRoleAssignmentService.GetUserRoleAssignmentsAsync(lootId, cancellationToken);
|
||||
|
||||
[HttpGet("{lootId}/[action]")]
|
||||
public async Task<IEnumerable<OpportunityRoleAssignmentView>> GetSimpleSecurities(long lootId, CancellationToken cancellationToken)
|
||||
=> await _opportunityRoleAssignmentService.GetRoleAssignmentsAsync(lootId, cancellationToken);
|
||||
|
||||
[HttpPut("{lootId}/[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<bool>> SetSimpleSecurities(long lootId, [FromBody] List<OpportunityRoleAssignmentView> permissionSettings, CancellationToken cancellationToken)
|
||||
{
|
||||
if (permissionSettings.Any(ps => ps.LootId != lootId)) { return BadRequest(); }
|
||||
|
||||
var roleAssignments = await _opportunityRoleAssignmentService.GetUserRoleAssignmentsAsync(lootId, cancellationToken);
|
||||
var isSecurable = roleAssignments.CanSecure;
|
||||
var userGuid = User?.GetUserGuid() ?? Guid.NewGuid();
|
||||
if (!User.IsSdtEmployee() && !isSecurable) return Ok(false);
|
||||
await _opportunityRoleAssignmentService.SaveRoleAssignmentsAsync(lootId, permissionSettings, cancellationToken);
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Biz.UserSetting;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class UserSettingsController : ControllerBase
|
||||
{
|
||||
private readonly IUserSettingService _userSettingService;
|
||||
|
||||
public UserSettingsController(IUserSettingService userSettingService)
|
||||
{
|
||||
_userSettingService = userSettingService;
|
||||
}
|
||||
|
||||
[HttpGet("{userGuid}")]
|
||||
public async Task<CciUserSetting> GetUserSetting(Guid userGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _userSettingService.GetById(userGuid, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost()]
|
||||
public async Task<CciUserSetting> SetUserSetting([FromBody] CciUserSetting userSetting, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _userSettingService.SetUserSetting(userSetting, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Biz.UserService;
|
||||
using Strata.Users.Client.UserGroups.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class UsersController : ControllerBase
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
|
||||
public UsersController(IUserService users)
|
||||
{
|
||||
_userService = users;
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public async Task<IEnumerable<CIUser>> GetUsers(CancellationToken cancellationToken)
|
||||
=> await GetUsersByGlobalId("CCI - Basic User", cancellationToken);
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public async Task<IEnumerable<CIUser>> GetOwners(CancellationToken cancellationToken)
|
||||
=> await GetUsers(cancellationToken);
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public async Task<IEnumerable<CIUser>> GetExecutiveSponsors(CancellationToken cancellationToken)
|
||||
=> await GetUsersByGlobalId("CCI - Exec Sponsors", cancellationToken);
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public async Task<IEnumerable<CIUser>> GetCostLeaders(CancellationToken cancellationToken)
|
||||
=> await GetUsersByGlobalId("CCI - Cost Leader", cancellationToken);
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public async Task<IEnumerable<CIUser>> GetAdministrators(CancellationToken cancellationToken)
|
||||
=> await GetUsersByGlobalId("CCI - System Administrator", cancellationToken);
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public async Task<IEnumerable<CIUser>> GetJazzUsers(CancellationToken cancellationToken)
|
||||
=> await _userService.GetJazzUsers(cancellationToken);
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public async Task<IEnumerable<UserGroup>> GetUserGroups(CancellationToken cancellationToken)
|
||||
=> await _userService.GetUserGroups(cancellationToken);
|
||||
|
||||
[HttpGet("all-ci-users")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<IEnumerable<CIUser>> GetAllCIUsers(CancellationToken cancellationToken)
|
||||
=> await _userService.GetAllCIUsers(cancellationToken);
|
||||
|
||||
private async Task<IEnumerable<CIUser>> GetUsersByGlobalId(string globalId, CancellationToken cancellationToken)
|
||||
=> await _userService.GetUsersByGlobalId(globalId, cancellationToken);
|
||||
|
||||
[HttpGet("{userGuid}/[action]")]
|
||||
public async Task<CIUser> GetUser(Guid userGuid, CancellationToken cancellationToken)
|
||||
=> await _userService.GetUser(userGuid, cancellationToken);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ApiLib.Standard.Models;
|
||||
using Strata.ContinuousImprovement.Biz.Pagination;
|
||||
using Strata.ContinuousImprovement.Biz.UtilizationVariation.Opportunities.Filters;
|
||||
using Strata.ContinuousImprovement.Biz.UtilizationVariation.VariationCaseTypes;
|
||||
using Strata.ContinuousImprovement.Biz.UtilizationVariation.VariationCaseTypes.Filters;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class VariationCaseTypesController : ControllerBase
|
||||
{
|
||||
private readonly IUVCaseTypeService _variationCaseTypeService;
|
||||
|
||||
public VariationCaseTypesController(IUVCaseTypeService variationCaseTypeService)
|
||||
{
|
||||
_variationCaseTypeService = variationCaseTypeService;
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
public async Task<PagedApiResponse<UVCaseType>> GetCaseTypes(UVCaseTypeLoadOptions loadOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _variationCaseTypeService.GetPagedAsync(loadOptions.PagingOptions, loadOptions.Filters, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("{configurationGuid}/[action]")]
|
||||
public async Task<UVFilterOptions> GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _variationCaseTypeService.GetFilterOptionsAsync(configurationGuid, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("{configurationGuid}/[action]")]
|
||||
public async Task<bool> AddPatientPopulationCaseTypes(Guid configurationGuid, List<string> patientPopulations, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _variationCaseTypeService.AddPatientPopulationCaseTypes(configurationGuid, User?.Identity?.Name ?? "", patientPopulations, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class UVCaseTypeLoadOptions
|
||||
{
|
||||
public PagingOptions PagingOptions { get; set; }
|
||||
public UVCaseTypeFilters Filters { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ApiLib.Standard.Models;
|
||||
using Strata.ContinuousImprovement.Api.Generics;
|
||||
using Strata.ContinuousImprovement.Biz.Pagination;
|
||||
using Strata.ContinuousImprovement.Biz.UtilizationVariation.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.UtilizationVariation.Opportunities.Filters;
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class VariationOpportunitiesController : OpportunityController<UVOpportunity>
|
||||
{
|
||||
public VariationOpportunitiesController(IUVOpportunityService opportunityService)
|
||||
: base(opportunityService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
public async Task<PagedApiResponse<UVOpportunity>> GetOpportunities(UVOpportunityLoadOptions loadOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
return await OpportunityService.GetPagedAsync(loadOptions.PagingOptions, loadOptions.Filters, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
public async Task<IActionResult> Export(UVOpportunityExportOptions exportOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
var workbook = await ((IUVOpportunityService)OpportunityService).ExportAsync(exportOptions.SortOptions, exportOptions.Filters, cancellationToken);
|
||||
return new ExcelContentResult(workbook, "UV_Opportunities");
|
||||
|
||||
}
|
||||
|
||||
[HttpGet("{configurationGuid}/[action]")]
|
||||
public async Task<UVFilterOptions> GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
return await ((IUVOpportunityService)OpportunityService).GetFilterOptionsAsync(configurationGuid, cancellationToken);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class UVOpportunityLoadOptions
|
||||
{
|
||||
public PagingOptions PagingOptions { get; set; }
|
||||
public UVFilters Filters { get; set; }
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class UVOpportunityExportOptions
|
||||
{
|
||||
public SortOptions SortOptions { get; set; }
|
||||
public UVFilters Filters { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Extensions
|
||||
{
|
||||
public static class ControllerBaseExtensions
|
||||
{
|
||||
public static ObjectResult Problem(this ControllerBase controller, ProblemDetails problemDetails)
|
||||
=> controller.Problem(title: problemDetails.Title,
|
||||
detail: problemDetails.Detail,
|
||||
statusCode: problemDetails.Status,
|
||||
instance: problemDetails.Instance,
|
||||
type: problemDetails.Type);
|
||||
|
||||
public static ObjectResult NotFoundProblem(this ControllerBase controller, ProblemDetails problemDetails = default)
|
||||
=> controller.Problem(title: problemDetails.Title,
|
||||
detail: problemDetails.Detail,
|
||||
statusCode: problemDetails?.Status ?? StatusCodes.Status404NotFound,
|
||||
instance: problemDetails.Instance,
|
||||
type: problemDetails.Type);
|
||||
|
||||
public static ObjectResult NotFoundProblem(this ControllerBase controller, string title, string detail)
|
||||
{
|
||||
var problemDetails = new ProblemDetails
|
||||
{
|
||||
Title = title,
|
||||
Detail = detail,
|
||||
Status = StatusCodes.Status404NotFound
|
||||
};
|
||||
return controller.Problem(problemDetails);
|
||||
}
|
||||
|
||||
public static ObjectResult NoContentProblem(this ControllerBase controller, ProblemDetails problemDetails = default)
|
||||
=> controller.Problem(title: problemDetails?.Title,
|
||||
detail: problemDetails?.Detail,
|
||||
statusCode: problemDetails?.Status ?? StatusCodes.Status204NoContent,
|
||||
instance: problemDetails?.Instance,
|
||||
type: problemDetails?.Type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Extensions
|
||||
{
|
||||
internal static class MultipartFileControllerExtensions
|
||||
{
|
||||
public static async Task<FileMultipartSection> GetFileFromMultipartUpload(this HttpRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// validation of Content-Type
|
||||
// 1. first, it must be a form-data request
|
||||
// 2. a boundary should be found in the Content-Type
|
||||
if (!request.HasFormContentType ||
|
||||
!MediaTypeHeaderValue.TryParse(request.ContentType, out var mediaTypeHeader) ||
|
||||
string.IsNullOrEmpty(mediaTypeHeader.Boundary.Value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var boundary = HeaderUtilities.RemoveQuotes(mediaTypeHeader.Boundary.Value).Value ?? string.Empty;
|
||||
var reader = new MultipartReader(boundary, request.Body);
|
||||
var section = await reader.ReadNextSectionAsync(cancellationToken);
|
||||
var fileSection = section?.AsFileSection();
|
||||
return fileSection is null
|
||||
? null
|
||||
: section.AsFileSection();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.ContinuousImprovement.Biz.Generics;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Generics
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class OpportunityController<T> : ControllerBase where T : class, IOpportunity
|
||||
{
|
||||
protected readonly IOpportunityService<T> OpportunityService;
|
||||
|
||||
public OpportunityController(IOpportunityService<T> opportunityService)
|
||||
{
|
||||
OpportunityService = opportunityService;
|
||||
}
|
||||
|
||||
[HttpPut("{opportunityGuid}/[action]")]
|
||||
public async Task SetHiddenAsync(Guid opportunityGuid, [FromBody] bool isHidden, CancellationToken cancellationToken)
|
||||
{
|
||||
await OpportunityService.SetHiddenAsync(opportunityGuid, isHidden, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPut("{opportunityGuid}/[action]")]
|
||||
public virtual async Task SetNoteAsync(Guid opportunityGuid, [FromBody] string note, CancellationToken cancellationToken)
|
||||
{
|
||||
await OpportunityService.SetNoteAsync(opportunityGuid, note, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Strata.ContinuousImprovement.Api.Generics
|
||||
{
|
||||
public class Policies
|
||||
{
|
||||
public const string StrataOnly = "StrataOnly";
|
||||
public const string IsAdmin = "IsAdmin";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.Generics
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class ProcessingResult : StatusCodeResult
|
||||
{
|
||||
public ProcessingResult()
|
||||
: base(StatusCodes.Status102Processing)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.CommandLineUtils;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
using Strata.Configuration.Client.DependencyInjection;
|
||||
using Strata.ContinuousImprovement.Biz.DbContexts;
|
||||
using Strata.healthchecks.Biz;
|
||||
using Strata.Logging.DependencyInjection;
|
||||
using Strata.Snowflake.Migrations.Client;
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var commandLineApplication = new CommandLineApplication(false);
|
||||
var doMigrate = commandLineApplication.Option(
|
||||
"--ef-migrate",
|
||||
"Apply entity framework migrations and exit",
|
||||
CommandOptionType.NoValue);
|
||||
|
||||
commandLineApplication.HelpOption("-? | -h | --help");
|
||||
commandLineApplication.OnExecute(async () =>
|
||||
{
|
||||
await ExecuteApp(args, doMigrate);
|
||||
return 0;
|
||||
});
|
||||
commandLineApplication.Execute(args);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "There was an unhandled exception that caused the program to crash");
|
||||
|
||||
//This is for serilog to ensure that all of the cached logs are flushed when the app crashes
|
||||
//This is useful so we can capture logs when the application crashes during startup:
|
||||
Log.CloseAndFlush();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ExecuteApp(string[] args, CommandOption doMigrate)
|
||||
{
|
||||
var webHost = CreateWebHostBuilder(args).Build();
|
||||
|
||||
if (doMigrate.HasValue())
|
||||
{
|
||||
using var scope = webHost.Services.CreateScope();
|
||||
var logger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>();
|
||||
|
||||
try
|
||||
{
|
||||
// Snowflake Migrations
|
||||
logger.LogInformation("Uploading and applying Snowflake migrations");
|
||||
var snowflakeMigrationService = scope.ServiceProvider.GetRequiredService<ISnowflakeMigrationService>();
|
||||
var snowflakeMigrationDirectory = Path.Combine(Environment.CurrentDirectory, "SnowflakeMigrations");
|
||||
var success = await snowflakeMigrationService.PackageMigrations(snowflakeMigrationDirectory);
|
||||
if (!success)
|
||||
{
|
||||
logger.LogError("Failed to package migrations.");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
logger.LogInformation("Snowflake migrations successfully uploaded to S3. Begin apply migrations.");
|
||||
success = await snowflakeMigrationService.ApplyClientMigrations();
|
||||
if (!success)
|
||||
{
|
||||
logger.LogError("Failed to apply migrations.");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
// EF Migrations
|
||||
logger.LogInformation("Applying Entity Framework migrations");
|
||||
using var context = scope.ServiceProvider.GetService<CentralDbContext>();
|
||||
var hostEnv = scope.ServiceProvider.GetService<IHostEnvironment>();
|
||||
|
||||
context.Database.SetCommandTimeout(TimeSpan.FromMinutes(10));
|
||||
context.Database.Migrate();
|
||||
logger.LogInformation("All done, closing app");
|
||||
Environment.Exit(Environment.ExitCode);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "There was an unhandled exception that caused the program to crash");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// no flags provided, so just run the webhost
|
||||
webHost.Run();
|
||||
}
|
||||
|
||||
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
|
||||
WebHost.CreateDefaultBuilder(args)
|
||||
.UseStrataConfiguration()
|
||||
.UseStrataLogging()
|
||||
.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "Strata.healthchecks.Jazz")
|
||||
.UseStrataHealthChecks()
|
||||
.UseStartup<Startup>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"profiles": {
|
||||
"Strata.ContinuousImprovement.Api": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "",
|
||||
"applicationUrl": "https://localhost:8443",
|
||||
"environmentVariables": {
|
||||
"Hangfire__RunLocally": "true",
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"AWS_S3_US_EAST_1_REGIONAL_ENDPOINT": "regional",
|
||||
"AWS_Profile": "sdt-continuous-improvement-service-role"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.DecisionSupport.Biz.Parameters;
|
||||
using Strata.DecisionSupport.Biz.Services;
|
||||
using Strata.DecisionSupport.Biz.Utilities;
|
||||
using Strata.DecisionSupport.Models.Encounters;
|
||||
using Strata.DecisionSupport.Models.Jazz;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.SnowflakeControllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class EncountersController : Controller
|
||||
{
|
||||
private readonly IEncounterQueryService _encounterQueryService;
|
||||
|
||||
public EncountersController(IEncounterQueryService encounterQueryService)
|
||||
{
|
||||
_encounterQueryService = encounterQueryService;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
#pragma warning disable S4457
|
||||
public async Task<ActionResult<IEnumerable<IJazzEncounterInfo>>> GetAsync(
|
||||
[FromBody] EncounterQueryInfo queryInfo, CancellationToken cancellationToken)
|
||||
#pragma warning restore
|
||||
{
|
||||
if (queryInfo.EntityIDs.IsEmpty())
|
||||
return BadRequest(queryInfo);
|
||||
|
||||
return Ok(await _encounterQueryService.RunEncounterInfoQuery(queryInfo, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
#pragma warning disable S4457
|
||||
public async Task<ActionResult<IEnumerable<EncounterCostInfo>>> GetCostInfoAsync(
|
||||
[FromBody] EncounterCostQueryInfo queryInfo, CancellationToken cancellationToken)
|
||||
#pragma warning restore
|
||||
{
|
||||
if (queryInfo.EncounterIds.IsEmpty())
|
||||
return BadRequest(queryInfo);
|
||||
|
||||
return Ok(await _encounterQueryService.RunEncounterCostInfoQuery(queryInfo, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
#pragma warning disable S4457
|
||||
public async Task<ActionResult<IEnumerable<DepartmentEncounterUnitOfService>>> GetDepartmentUnitOfServiceInfoAsync(
|
||||
[FromBody] DepartmentEncounterUnitOfServiceQueryInfo queryInfo, CancellationToken cancellationToken)
|
||||
#pragma warning restore
|
||||
{
|
||||
if (queryInfo.EncounterIds.IsEmpty())
|
||||
return BadRequest(queryInfo);
|
||||
|
||||
return Ok(await _encounterQueryService.RunDepartmentEncounterUnitOfServiceQuery(queryInfo, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
#pragma warning disable S4457
|
||||
public async Task<ActionResult<IEnumerable<EncounterUnitOfService>>> GetUnitOfServiceInfoAsync(
|
||||
[FromBody] EncounterUnitOfServiceQueryInfo queryInfo, CancellationToken cancellationToken)
|
||||
#pragma warning restore
|
||||
{
|
||||
if (queryInfo.EncounterIds.IsEmpty())
|
||||
return BadRequest(queryInfo);
|
||||
|
||||
return Ok(await _encounterQueryService.RunEncounterUnitOfServiceQuery(queryInfo, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
#pragma warning disable S4457
|
||||
public async Task<ActionResult<IEnumerable<EncounterChargeInfo>>> GetEncounterChargeInfoAsync(
|
||||
[FromBody] EncounterChargeQueryInfo queryInfo, CancellationToken cancellationToken)
|
||||
#pragma warning restore
|
||||
{
|
||||
if (queryInfo.OpportunityGuid.Equals(Guid.Empty))
|
||||
return BadRequest(queryInfo);
|
||||
|
||||
return Ok(await _encounterQueryService.RunEncounterChargeQuery(queryInfo, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
#pragma warning disable S4457
|
||||
public async Task<ActionResult<IEnumerable<VariationEncounterChargeInfo>>> GetVariationEncounterChargeInfoAsync(
|
||||
[FromBody] VariationEncounterChargeQueryInfo queryInfo, CancellationToken cancellationToken)
|
||||
#pragma warning restore
|
||||
{
|
||||
if (queryInfo.OpportunityGuids.IsEmpty())
|
||||
return BadRequest(queryInfo);
|
||||
|
||||
return Ok(await _encounterQueryService.RunVariationEncounterChargeQuery(queryInfo, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("los-opportunity-info")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<IEnumerable<LOSOpportunityInfo>>> GetLOSOpportunityInfoAsync(
|
||||
[FromBody] LOSOpportunityQueryInfo queryInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
if (queryInfo.OpportunityGuid.Equals(Guid.Empty))
|
||||
return BadRequest(queryInfo);
|
||||
|
||||
return Ok(await _encounterQueryService.RunLOSOpportunityInfoQuery(queryInfo, cancellationToken));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Strata.DecisionSupport.Biz.Parameters;
|
||||
using Strata.DecisionSupport.Biz.Services;
|
||||
using Strata.DecisionSupport.Biz.Utilities;
|
||||
using Strata.DecisionSupport.Models.Jazz;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api.SnowflakeControllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class UtilizationVariationController : Controller
|
||||
{
|
||||
private readonly IUtilizationVariationService _utilizationVariationService;
|
||||
|
||||
public UtilizationVariationController(IUtilizationVariationService utilizationVariationService)
|
||||
{
|
||||
_utilizationVariationService = utilizationVariationService;
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<IEnumerable<AllChargeInfo>>> GetAllChargeInfoAsync(
|
||||
[FromBody] AllChargeQueryInfo queryInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
if (queryInfo.OpportunityGuid.Equals(Guid.Empty))
|
||||
return BadRequest(queryInfo);
|
||||
if (queryInfo.ChargeCodeIds.IsEmpty())
|
||||
return BadRequest(queryInfo);
|
||||
|
||||
return Ok(await _utilizationVariationService.RunAllChargeInfoQuery(queryInfo, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<IEnumerable<ChargeInfo>>> GetChargeInfoAsync(
|
||||
[FromBody] ChargeQueryInfo queryInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
if (queryInfo.ConfigurationGuid.Equals(Guid.Empty))
|
||||
return BadRequest(queryInfo);
|
||||
if (queryInfo.ChargeCodeIds.IsEmpty())
|
||||
return BadRequest(queryInfo);
|
||||
|
||||
return Ok(await _utilizationVariationService.RunChargeInfoQuery(queryInfo, cancellationToken));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
--Author: sdt-cloveland
|
||||
--Forward
|
||||
CREATE SCHEMA IF NOT EXISTS cci;
|
||||
|
||||
--Rollback
|
||||
--ILB
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
--Author: tdebolt
|
||||
--Forward
|
||||
CREATE OR REPLACE DYNAMIC TABLE cci.encounterchargecodecosts
|
||||
TARGET_LAG = DOWNSTREAM --important to set to downstream for creation, manually change to schedule as we enable for clients
|
||||
WAREHOUSE = 'APP_CI_DYNAMICTABLE_WH'
|
||||
COMMENT = 'Populate Charge Data by Encounter and Charge Code for use in CI'
|
||||
REFRESH_MODE = AUTO
|
||||
INITIALIZE = ON_SCHEDULE
|
||||
AS
|
||||
WITH pblidGrouping (pblidrowid, unitsofservice, fixeddirectcost, fixedindirectcost, variabledirectcost, variableindirectcost, totalcost) AS (
|
||||
SELECT
|
||||
cd.pblidrowid,
|
||||
min(unitsofservice) AS unitsofservice,
|
||||
sum(fixeddirectcost) as fixeddirectcost,
|
||||
sum(fixedindirectcost) as fixedindirectcost,
|
||||
sum(variabledirectcost) as variabledirectcost,
|
||||
sum(variableindirectcost) as variableindirectcost,
|
||||
sum(fixeddirectcost) + sum(fixedindirectcost) + sum(variabledirectcost) + sum(variableindirectcost) as totalcost
|
||||
FROM dss.factpatientcostdetail cd
|
||||
GROUP BY cd.pblidrowid
|
||||
ORDER BY cd.pblidrowid
|
||||
)
|
||||
|
||||
SELECT
|
||||
d.encounterid,
|
||||
d.chargecodeid,
|
||||
sum(d.charge) as charge,
|
||||
sum(g.unitsofservice) as unitsofservice,
|
||||
sum(g.fixeddirectcost) as fixeddirectcost,
|
||||
sum(g.fixedindirectcost) as fixedindirectcost,
|
||||
sum(g.variabledirectcost) as variabledirectcost,
|
||||
sum(g.variableindirectcost) as variableindirectcost,
|
||||
sum(d.fixeddirectunitcost) as fixeddirectunitcost,
|
||||
sum(d.fixedindirectunitcost) as fixedindirectunitcost,
|
||||
sum(d.variabledirectunitcost) as variabledirectunitcost,
|
||||
sum(d.variableindirectunitcost) as variableindirectunitcost,
|
||||
sum(g.totalcost) as totalcost,
|
||||
FROM dss.factpatientbillinglineitemdetail d
|
||||
LEFT JOIN pblidGrouping g on g.pblidrowid = d.rowid
|
||||
GROUP BY d.encounterid, d.chargecodeid
|
||||
;
|
||||
--Rollback
|
||||
DROP DYNAMIC TABLE IF EXISTS cci.encounterchargecodecosts;
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
--Author: sdt-cloveland
|
||||
--Forward
|
||||
CREATE SCHEMA IF NOT EXISTS cci;
|
||||
|
||||
CREATE OR REPLACE TABLE cci.StrategicOpportunityDetail (
|
||||
OpportunityId BIGINT,
|
||||
ChargeDepartmentID INT NOT NULL,
|
||||
EncounterID INT NOT NULL,
|
||||
ChargeCodeID INT NOT NULL,
|
||||
EntityID INT NOT NULL,
|
||||
PhysicianID INT NOT NULL,
|
||||
PrimaryServiceLineID INT NOT NULL,
|
||||
PhysicianSpecialtyID INT NOT NULL,
|
||||
CostDriver STRING NOT NULL,
|
||||
UnitsOfService FLOAT NOT NULL
|
||||
);
|
||||
|
||||
--Rollback
|
||||
DROP TABLE cci.StrategicOpportunityDetail;
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
--Author: sdt-dmoroziuk
|
||||
--Forward
|
||||
CREATE OR REPLACE TABLE cci.DimExplorationPopulation (
|
||||
ExplorationPopulationId INT NOT NULL,
|
||||
Name STRING NOT NULL,
|
||||
CaseTypeFamilyId INT NOT NULL,
|
||||
CostDriver STRING NOT NULL,
|
||||
TotalEncounters INT NOT NULL,
|
||||
TotalVariation FLOAT NOT NULL
|
||||
);
|
||||
|
||||
--Rollback
|
||||
DROP TABLE cci.DimExplorationPopulation;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
--Author: sdt-dmoroziuk
|
||||
--Forward
|
||||
CREATE OR REPLACE TABLE cci.FactExplorationPopulationEncounter (
|
||||
ExplorationPopulationId INT NOT NULL,
|
||||
EncounterId INT NOT NULL
|
||||
);
|
||||
|
||||
--Rollback
|
||||
DROP TABLE cci.FactExplorationPopulationEncounter;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
--Author: sdt-dmoroziuk
|
||||
--Forward
|
||||
CREATE OR REPLACE STAGE cci.POSTGRES_REPLICATION_DATA;
|
||||
|
||||
--Rollback
|
||||
DROP STAGE IF EXISTS cci.POSTGRES_REPLICATION_DATA;
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
--Author: sdt-cloveland
|
||||
--Forward
|
||||
ALTER TABLE cci.StrategicOpportunityDetail
|
||||
ADD COLUMN IF NOT EXISTS BaselineCost FLOAT NOT NULL;
|
||||
|
||||
ALTER TABLE cci.StrategicOpportunityDetail
|
||||
ADD COLUMN IF NOT EXISTS GoalUnits FLOAT NOT NULL;
|
||||
|
||||
ALTER TABLE cci.StrategicOpportunityDetail
|
||||
ADD COLUMN IF NOT EXISTS GoalCost FLOAT NOT NULL;
|
||||
|
||||
BEGIN
|
||||
IF (
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_CATALOG = CURRENT_DATABASE()
|
||||
AND TABLE_SCHEMA = 'CCI'
|
||||
AND TABLE_NAME = 'STRATEGICOPPORTUNITYDETAIL'
|
||||
AND COLUMN_NAME = 'UNITSOFSERVICE'
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_CATALOG = CURRENT_DATABASE()
|
||||
AND TABLE_SCHEMA = 'CCI'
|
||||
AND TABLE_NAME = 'STRATEGICOPPORTUNITYDETAIL'
|
||||
AND COLUMN_NAME = 'BASELINEUNITS'
|
||||
)
|
||||
)
|
||||
THEN
|
||||
ALTER TABLE cci.StrategicOpportunityDetail
|
||||
RENAME COLUMN UnitsOfService TO BaselineUnits;
|
||||
END IF;
|
||||
END;
|
||||
|
||||
--Rollback
|
||||
ALTER TABLE cci.StrategicOpportunityDetail
|
||||
DROP COLUMN BaselineCost;
|
||||
|
||||
ALTER TABLE cci.StrategicOpportunityDetail
|
||||
DROP COLUMN GoalCost;
|
||||
|
||||
ALTER TABLE cci.StrategicOpportunityDetail
|
||||
DROP COLUMN GoalUnits;
|
||||
|
||||
BEGIN
|
||||
IF (
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_CATALOG = CURRENT_DATABASE()
|
||||
AND TABLE_SCHEMA = 'CCI'
|
||||
AND TABLE_NAME = 'STRATEGICOPPORTUNITYDETAIL'
|
||||
AND COLUMN_NAME = 'BASELINEUNITS'
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_CATALOG = CURRENT_DATABASE()
|
||||
AND TABLE_SCHEMA = 'CCI'
|
||||
AND TABLE_NAME = 'STRATEGICOPPORTUNITYDETAIL'
|
||||
AND COLUMN_NAME = 'UNITSOFSERVICE'
|
||||
)
|
||||
)
|
||||
THEN
|
||||
ALTER TABLE cci.StrategicOpportunityDetail
|
||||
RENAME COLUMN BaselineUnits TO UnitsOfService;
|
||||
END IF;
|
||||
END;
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
--Author: tdebolt
|
||||
--Forward
|
||||
CREATE OR REPLACE DYNAMIC TABLE cci.encounterchargecodecosts
|
||||
TARGET_LAG = DOWNSTREAM --important to set to downstream for creation, manually change to schedule as we enable for clients
|
||||
WAREHOUSE = 'APP_CI_DYNAMICTABLE_WH'
|
||||
COMMENT = 'Populate Charge Data by Encounter and Charge Code for use in CI'
|
||||
REFRESH_MODE = AUTO
|
||||
INITIALIZE = ON_SCHEDULE
|
||||
AS
|
||||
WITH pblidGrouping (pblidrowid, unitsofservice, fixeddirectcost, fixedindirectcost, variabledirectcost, variableindirectcost, totalcost) AS (
|
||||
SELECT
|
||||
cd.pblidrowid,
|
||||
min(unitsofservice) AS unitsofservice,
|
||||
sum(fixeddirectcost) as fixeddirectcost,
|
||||
sum(fixedindirectcost) as fixedindirectcost,
|
||||
sum(variabledirectcost) as variabledirectcost,
|
||||
sum(variableindirectcost) as variableindirectcost,
|
||||
sum(fixeddirectcost) + sum(fixedindirectcost) + sum(variabledirectcost) + sum(variableindirectcost) as totalcost
|
||||
FROM dss.factpatientcostdetail cd
|
||||
GROUP BY cd.pblidrowid
|
||||
ORDER BY cd.pblidrowid
|
||||
)
|
||||
|
||||
SELECT
|
||||
d.encounterid,
|
||||
d.chargecodeid,
|
||||
d.rowid,
|
||||
sum(d.charge) as charge,
|
||||
sum(g.unitsofservice) as unitsofservice,
|
||||
sum(g.fixeddirectcost) as fixeddirectcost,
|
||||
sum(g.fixedindirectcost) as fixedindirectcost,
|
||||
sum(g.variabledirectcost) as variabledirectcost,
|
||||
sum(g.variableindirectcost) as variableindirectcost,
|
||||
sum(d.fixeddirectunitcost) as fixeddirectunitcost,
|
||||
sum(d.fixedindirectunitcost) as fixedindirectunitcost,
|
||||
sum(d.variabledirectunitcost) as variabledirectunitcost,
|
||||
sum(d.variableindirectunitcost) as variableindirectunitcost,
|
||||
sum(g.totalcost) as totalcost
|
||||
FROM dss.factpatientbillinglineitemdetail d
|
||||
LEFT JOIN pblidGrouping g on g.pblidrowid = d.rowid
|
||||
GROUP BY d.encounterid, d.chargecodeid, d.rowid
|
||||
;
|
||||
--Rollback
|
||||
CREATE OR REPLACE DYNAMIC TABLE cci.encounterchargecodecosts
|
||||
TARGET_LAG = DOWNSTREAM --important to set to downstream for creation, manually change to schedule as we enable for clients
|
||||
WAREHOUSE = 'APP_CI_DYNAMICTABLE_WH'
|
||||
COMMENT = 'Populate Charge Data by Encounter and Charge Code for use in CI'
|
||||
REFRESH_MODE = AUTO
|
||||
INITIALIZE = ON_SCHEDULE
|
||||
AS
|
||||
WITH pblidGrouping (pblidrowid, unitsofservice, fixeddirectcost, fixedindirectcost, variabledirectcost, variableindirectcost, totalcost) AS (
|
||||
SELECT
|
||||
cd.pblidrowid,
|
||||
min(unitsofservice) AS unitsofservice,
|
||||
sum(fixeddirectcost) as fixeddirectcost,
|
||||
sum(fixedindirectcost) as fixedindirectcost,
|
||||
sum(variabledirectcost) as variabledirectcost,
|
||||
sum(variableindirectcost) as variableindirectcost,
|
||||
sum(fixeddirectcost) + sum(fixedindirectcost) + sum(variabledirectcost) + sum(variableindirectcost) as totalcost
|
||||
FROM dss.factpatientcostdetail cd
|
||||
GROUP BY cd.pblidrowid
|
||||
ORDER BY cd.pblidrowid
|
||||
)
|
||||
|
||||
SELECT
|
||||
d.encounterid,
|
||||
d.chargecodeid,
|
||||
sum(d.charge) as charge,
|
||||
sum(g.unitsofservice) as unitsofservice,
|
||||
sum(g.fixeddirectcost) as fixeddirectcost,
|
||||
sum(g.fixedindirectcost) as fixedindirectcost,
|
||||
sum(g.variabledirectcost) as variabledirectcost,
|
||||
sum(g.variableindirectcost) as variableindirectcost,
|
||||
sum(d.fixeddirectunitcost) as fixeddirectunitcost,
|
||||
sum(d.fixedindirectunitcost) as fixedindirectunitcost,
|
||||
sum(d.variabledirectunitcost) as variabledirectunitcost,
|
||||
sum(d.variableindirectunitcost) as variableindirectunitcost,
|
||||
sum(g.totalcost) as totalcost
|
||||
FROM dss.factpatientbillinglineitemdetail d
|
||||
LEFT JOIN pblidGrouping g on g.pblidrowid = d.rowid
|
||||
GROUP BY d.encounterid, d.chargecodeid
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
--Author: sdt-dmoroziuk
|
||||
--Forward
|
||||
BEGIN
|
||||
IF (
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_CATALOG = CURRENT_DATABASE()
|
||||
AND TABLE_SCHEMA = 'CCI'
|
||||
AND TABLE_NAME = 'STRATEGICOPPORTUNITYDETAIL'
|
||||
AND COLUMN_NAME = 'ChargeDepartmentID'
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_CATALOG = CURRENT_DATABASE()
|
||||
AND TABLE_SCHEMA = 'CCI'
|
||||
AND TABLE_NAME = 'STRATEGICOPPORTUNITYDETAIL'
|
||||
AND COLUMN_NAME = 'DepartmentId'
|
||||
)
|
||||
)
|
||||
THEN
|
||||
ALTER TABLE cci.StrategicOpportunityDetail
|
||||
RENAME COLUMN ChargeDepartmentID TO DepartmentId;
|
||||
END IF;
|
||||
END;
|
||||
--Rollback
|
||||
BEGIN
|
||||
IF (
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_CATALOG = CURRENT_DATABASE()
|
||||
AND TABLE_SCHEMA = 'CCI'
|
||||
AND TABLE_NAME = 'STRATEGICOPPORTUNITYDETAIL'
|
||||
AND COLUMN_NAME = 'DepartmentId'
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_CATALOG = CURRENT_DATABASE()
|
||||
AND TABLE_SCHEMA = 'CCI'
|
||||
AND TABLE_NAME = 'STRATEGICOPPORTUNITYDETAIL'
|
||||
AND COLUMN_NAME = 'ChargeDepartmentID'
|
||||
)
|
||||
)
|
||||
THEN
|
||||
ALTER TABLE cci.StrategicOpportunityDetail
|
||||
RENAME COLUMN DepartmentId TO ChargeDepartmentID;
|
||||
END IF;
|
||||
END;
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
--Author: yrenz
|
||||
--Forward
|
||||
ALTER TABLE cci.StrategicOpportunityDetail
|
||||
ADD COLUMN IF NOT EXISTS CaseTypeFamilyID INT NOT NULL DEFAULT(0);
|
||||
|
||||
--Rollback
|
||||
ALTER TABLE cci.StrategicOpportunityDetail
|
||||
DROP COLUMN CaseTypeFamilyID;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
--Author: yrenz
|
||||
--Forward
|
||||
CREATE OR ALTER VIEW cci.viewEncounterPatientTypeRollup AS
|
||||
SELECT fpes.encounterid, ptr.Type as PatientTypeRollup
|
||||
FROM clientdss.FactPatientEncounterSummary fpes
|
||||
INNER JOIN fw.DimPatientType pt ON fpes.PatientTypeID = pt.PatientTypeID
|
||||
INNER JOIN fw.DimPatientTypeRollup ptr ON pt.PatientTypeRollupID = ptr.PatientTypeRollupID;
|
||||
--Rollback
|
||||
DROP VIEW IF EXISTS cci.viewEncounterPatientTypeRollup;
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
--Author: sdt-mvasylovskyi
|
||||
|
||||
--Forward
|
||||
|
||||
CREATE OR REPLACE TABLE cci.FactStrategicChargeCodeDetail (
|
||||
RowId BIGINT AUTOINCREMENT START 1 INCREMENT 1 PRIMARY KEY,
|
||||
IsBaseline BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
DischargeFiscalYearId SMALLINT NOT NULL DEFAULT 0,
|
||||
DischargeFiscalMonthId TINYINT NOT NULL DEFAULT 0,
|
||||
OpportunityId BIGINT,
|
||||
EntityId INT NOT NULL,
|
||||
PrimaryPhysicianId INT NOT NULL,
|
||||
PrimaryServiceLineId INT NOT NULL,
|
||||
DepartmentId INT NOT NULL,
|
||||
PhysicianSpecialtyId INT NOT NULL,
|
||||
ChargeCodeId INT NOT NULL,
|
||||
CaseTypeFamilyId INT NOT NULL,
|
||||
CostDriver VARCHAR(1024) NOT NULL COLLATE 'en-ci',
|
||||
Units FLOAT NOT NULL,
|
||||
Cost FLOAT NOT NULL,
|
||||
GoalUnits FLOAT NOT NULL,
|
||||
GoalCost FLOAT NOT NULL
|
||||
);
|
||||
DROP TABLE IF EXISTS cci.StrategicOpportunityDetail;
|
||||
|
||||
--Rollback
|
||||
|
||||
CREATE OR REPLACE TABLE cci.StrategicOpportunityDetail (
|
||||
OpportunityId BIGINT,
|
||||
EncounterId INT NOT NULL,
|
||||
EntityId INT NOT NULL,
|
||||
PhysicianId INT NOT NULL,
|
||||
PrimaryServiceLineId INT NOT NULL,
|
||||
DepartmentId INT NOT NULL,
|
||||
PhysicianSpecialtyId INT NOT NULL,
|
||||
ChargeCodeId INT NOT NULL,
|
||||
CaseTypeFamilyId INT NOT NULL,
|
||||
CostDriver VARCHAR(1024) NOT NULL COLLATE 'en-ci',
|
||||
BaselineUnits FLOAT NOT NULL,
|
||||
BaselineCost FLOAT NOT NULL,
|
||||
GoalUnits FLOAT NOT NULL,
|
||||
GoalCost FLOAT NOT NULL
|
||||
);
|
||||
DROP TABLE IF EXISTS cci.FactStrategicChargeCodeDetail;
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
--Author: sdt-mvasylovskyi
|
||||
|
||||
--Forward
|
||||
|
||||
BEGIN
|
||||
IF (
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM INFORMATION_SCHEMA.TABLES
|
||||
WHERE TABLE_SCHEMA ILIKE 'cci'
|
||||
AND TABLE_NAME ILIKE 'FactStrategicChargeCodeDetail'
|
||||
)
|
||||
)
|
||||
THEN
|
||||
IF (
|
||||
NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA ILIKE 'cci'
|
||||
AND TABLE_NAME ILIKE 'FactStrategicChargeCodeDetail'
|
||||
AND COLUMN_NAME ILIKE 'DistributedUnits'
|
||||
)
|
||||
)
|
||||
THEN
|
||||
ALTER TABLE cci.FactStrategicChargeCodeDetail
|
||||
ADD COLUMN DistributedUnits FLOAT NOT NULL DEFAULT(0);
|
||||
END IF;
|
||||
|
||||
IF (
|
||||
NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA ILIKE 'cci'
|
||||
AND TABLE_NAME ILIKE 'FactStrategicChargeCodeDetail'
|
||||
AND COLUMN_NAME ILIKE 'DistributedCost'
|
||||
)
|
||||
)
|
||||
THEN
|
||||
ALTER TABLE cci.FactStrategicChargeCodeDetail
|
||||
ADD COLUMN DistributedCost FLOAT NOT NULL DEFAULT(0);
|
||||
END IF;
|
||||
END IF;
|
||||
END;
|
||||
|
||||
--Rollback
|
||||
|
||||
BEGIN
|
||||
IF (
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM INFORMATION_SCHEMA.TABLES
|
||||
WHERE TABLE_SCHEMA ILIKE 'cci'
|
||||
AND TABLE_NAME ILIKE 'FactStrategicChargeCodeDetail'
|
||||
)
|
||||
)
|
||||
THEN
|
||||
ALTER TABLE cci.FactStrategicChargeCodeDetail
|
||||
DROP COLUMN IF EXISTS DistributedUnits;
|
||||
ALTER TABLE cci.FactStrategicChargeCodeDetail
|
||||
DROP COLUMN IF EXISTS DistributedCost;
|
||||
END IF;
|
||||
END;
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
--Author: sdt-ydzoba
|
||||
--Forward
|
||||
CREATE OR REPLACE TABLE cci.StrategicOpportunityMeasureDetail (
|
||||
MeasureId BIGINT NOT NULL,
|
||||
YearId SMALLINT NOT NULL,
|
||||
MonthId TINYINT NOT NULL,
|
||||
Value FLOAT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (MeasureId, YearId, MonthId)
|
||||
);
|
||||
|
||||
--Rollback
|
||||
DROP TABLE IF EXISTS cci.StrategicOpportunityMeasureDetail;
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
--Author: sdt-tdebolt
|
||||
|
||||
--Forward
|
||||
CREATE TABLE IF NOT EXISTS cci.dimstrategicitem (
|
||||
strategic_item_id NUMBER(38,0) NOT NULL,
|
||||
name STRING,
|
||||
rollup_one_id NUMBER(38,0),
|
||||
rollup_two_id NUMBER(38,0),
|
||||
rollup_three_id NUMBER(38,0),
|
||||
baseline_type STRING,
|
||||
strategic_item_type STRING,
|
||||
opportunity_type STRING,
|
||||
status STRING,
|
||||
baseline_distribution STRING,
|
||||
baseline_start_date DATE,
|
||||
baseline_end_date DATE,
|
||||
estimated_tracking_duration NUMBER(38,0),
|
||||
tracking_start_date DATE,
|
||||
tracking_end_date DATE,
|
||||
estimated_goal NUMBER(19,4),
|
||||
total_improvement NUMBER(19,4),
|
||||
cost_leader STRING,
|
||||
executive_sponsor STRING,
|
||||
owner STRING,
|
||||
stakeholders STRING,
|
||||
team_members STRING,
|
||||
last_run_tracking_date DATE,
|
||||
last_run_tracking_status STRING,
|
||||
|
||||
CONSTRAINT pk_dimstrategicitem
|
||||
PRIMARY KEY (strategic_item_id)
|
||||
);
|
||||
|
||||
--Rollback
|
||||
|
||||
DROP TABLE IF EXISTS cci.dimstrategicitem;
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
--Author: sdt-mleitch
|
||||
|
||||
--Forward
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cci.FactStrategicGLDetail (
|
||||
RowId BIGINT AUTOINCREMENT START 1 INCREMENT 1 PRIMARY KEY,
|
||||
IsBaseline BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
FiscalYearId SMALLINT NOT NULL DEFAULT 0,
|
||||
FiscalMonthId TINYINT NOT NULL DEFAULT 0,
|
||||
OpportunityId BIGINT NOT NULL,
|
||||
EntityId INT NOT NULL,
|
||||
DepartmentId INT NOT NULL,
|
||||
AccountId INT NOT NULL,
|
||||
AccountType TINYINT,
|
||||
Amount FLOAT NOT NULL DEFAULT 0,
|
||||
GoalAmount FLOAT NOT NULL DEFAULT 0,
|
||||
DistributedAmount FLOAT NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
--Rollback
|
||||
|
||||
DROP TABLE IF EXISTS cci.FactStrategicGLDetail;
|
||||
@@ -0,0 +1,122 @@
|
||||
using Amazon.SQS;
|
||||
using AutoMapper;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Prometheus;
|
||||
using Strata.ApiLib.Core.Cors.Extensions;
|
||||
using Strata.ApiLib.Core.ExceptionHandling.DependencyInjection.Bootstrappers;
|
||||
using Strata.ApiLib.Core.Logging.Bootstrappers;
|
||||
using Strata.ApiLib.Core.StrataAuthentication.Bootstrappers;
|
||||
using Strata.ApiLib.Core.Transformers;
|
||||
using Strata.ContinuousImprovement.Api.Generics;
|
||||
using Strata.ContinuousImprovement.Biz.BackgroundServices;
|
||||
using Strata.ContinuousImprovement.Biz.Configurations;
|
||||
using Strata.ContinuousImprovement.Biz.Configurations.Hangfire;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using Strata.DecisionSupport.Biz.Configurations;
|
||||
using Strata.healthchecks.Biz;
|
||||
using Strata.SignalR.Configuration;
|
||||
using Strata.SwaggerExtensions;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Net.Mime;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Api
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class Startup
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddGlobalExceptionMiddleware(_configuration);
|
||||
services.AddControllers(options =>
|
||||
{
|
||||
//Prevent browsers from caching API responses (this addresses an IE11 issue):
|
||||
options.Filters.Add(
|
||||
new ResponseCacheAttribute()
|
||||
{
|
||||
Location = ResponseCacheLocation.None,
|
||||
NoStore = false,
|
||||
Duration = -1
|
||||
});
|
||||
options.ReturnHttpNotAcceptable = true;
|
||||
options.Filters.Add(new ProducesAttribute(MediaTypeNames.Application.Json));
|
||||
options.Filters.Add(new ConsumesAttribute(MediaTypeNames.Application.Json));
|
||||
options.Conventions.Add(new RouteTokenTransformerConvention(new SlugifyParameterTransformer()));
|
||||
|
||||
})
|
||||
.AddJsonOptions(options =>
|
||||
{
|
||||
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
|
||||
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
|
||||
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
|
||||
})
|
||||
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
|
||||
|
||||
services.AddAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy(Policies.IsAdmin,
|
||||
policy => policy.RequireAssertion(context => context.User.IsInRole("CI - System Administrator") || context.User.IsSdtEmployee()));
|
||||
options.AddPolicy(Policies.StrataOnly,
|
||||
policy => policy.RequireAssertion(context => context.User.IsSdtEmployee()));
|
||||
});
|
||||
|
||||
services.AddStrataAuthentication(_configuration);
|
||||
services.AddStrataHangfire();
|
||||
services.AddContinuousImprovementServices(_configuration);
|
||||
services.AddDecisionSupportServices(_configuration);
|
||||
services.AddRouting();
|
||||
services.AddSwagger(_configuration);
|
||||
services.AddStrataSignalR();
|
||||
services.AddStrataHealthChecks(_configuration);
|
||||
services.AddSnowflakeMigrationServiceClient();
|
||||
services.AddStrataCors(_configuration);
|
||||
services.AddHealthChecks();
|
||||
|
||||
services.AddTransient<IAmazonSQS, AmazonSQSClient>();
|
||||
services.AddHostedService<ReplicationBackgroundService>();
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider provider)
|
||||
{
|
||||
app.UseGlobalExceptionMiddleware();
|
||||
app.UseRouting();
|
||||
app.UseStrataCorsPolicy(env);
|
||||
|
||||
if (!env.IsDevelopment())
|
||||
{
|
||||
app.UseHsts();
|
||||
app.UseHttpsRedirection();
|
||||
}
|
||||
|
||||
app.UseMetricServer();
|
||||
app.UseHttpMetrics();
|
||||
app.UseAccessTokenQueryStringMiddleware();
|
||||
app.UseStrataAuthentication();
|
||||
app.UseClaimsLogging();
|
||||
app.ConfigureSwagger(env, provider);
|
||||
app.UseEndpoints(endpoints => endpoints.MapControllers().RequireAuthorization());
|
||||
app.UseEndpoints(endpoints => endpoints.MapHangfireHub());
|
||||
app.UseEndpoints(endpoints => endpoints.MapNotificationHub());
|
||||
app.UseStrataHealthChecks(env);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user