diff --git a/LCU.API.Operations.IncentiveAPI/.dockerignore b/LCU.API.Operations.IncentiveAPI/.dockerignore new file mode 100644 index 0000000..186c6b6 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/.dockerignore @@ -0,0 +1,17 @@ +**/*.bat +**/*.log +**/*.md +**/*.yml +**/.dockerignore +**/.editorconfig +**/.git +**/.gitattributes +**/.gitignore +**/.vs +**/.vscode +**/bin +**/dist +**/dockerfile +**/node_modules +**/obj +**/package-lock.json diff --git a/LCU.API.Operations.IncentiveAPI/.editorconfig b/LCU.API.Operations.IncentiveAPI/.editorconfig new file mode 100644 index 0000000..db4457c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +charset = utf-8 +insert_final_newline = true +indent_size = 4 +indent_style = space +trim_trailing_whitespace = true diff --git a/LCU.API.Operations.IncentiveAPI/.gitignore b/LCU.API.Operations.IncentiveAPI/.gitignore new file mode 100644 index 0000000..f431ddc --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/.gitignore @@ -0,0 +1,329 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ diff --git a/LCU.API.Operations.IncentiveAPI/Dockerfile b/LCU.API.Operations.IncentiveAPI/Dockerfile new file mode 100644 index 0000000..3c9cc74 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/Dockerfile @@ -0,0 +1,44 @@ +# .NET Core SDK +FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine3.20 AS aspnetcore-build + +# Copy Projects +COPY LCU.API.Internal.Operations.IncentiveApi/LCU.API.Internal.Operations.IncentiveApi.csproj ./source/LCU.API.Internal.Operations.IncentiveApi/ +COPY LCU.API.Internal.Operations.IncentiveApiLib/LCU.API.Internal.Operations.IncentiveApiLib.csproj ./source/LCU.API.Internal.Operations.IncentiveApiLib/ +COPY LCU.Library.Internal.IncentiveApiModels/LCU.Library.Internal.IncentiveApiModels.csproj ./source/LCU.Library.Internal.IncentiveApiModels/ + +# Install Credential Provider and set env variables to enable Nuget restore with auth +ARG PAT=bh2lq3jibktw67b2wdbqyfln7cd3je6lk52kijntpj43v6kimpgq +RUN curl -L https://raw.githubusercontent.com/Microsoft/artifacts-credprovider/master/helpers/installcredprovider.sh | sh + +ENV NUGET_CREDENTIALPROVIDER_SESSIONTOKENCACHE_ENABLED true +ENV VSS_NUGET_EXTERNAL_FEED_ENDPOINTS "{\"endpointCredentials\": [{\"endpoint\":\"https://landmarkcu.pkgs.visualstudio.com/_packaging/LCU-Internal/nuget/v3/index.json\", \"password\":\"${PAT}\"}]}" + +# .NET Core Restore, use auth'd feed +RUN dotnet restore -s "https://landmarkcu.pkgs.visualstudio.com/_packaging/LCU-Internal/nuget/v3/index.json" -s "https://api.nuget.org/v3/index.json" ./source/LCU.API.Internal.Operations.IncentiveApi/LCU.API.Internal.Operations.IncentiveApi.csproj + +# Copy All Files +COPY . ./source/ + +FROM aspnetcore-build as test +LABEL test=true +RUN apk add --no-cache icu-libs && apk add --no-cache tzdata; +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false +ENV ASPNETCORE_ENVIRONMENT=Development +COPY LCU.API.Internal.Operations.IncentiveApi.UnitTests/LCU.API.Internal.Operations.IncentiveApi.UnitTests.csproj ./source/LCU.API.Internal.Operations.IncentiveApi.UnitTests/ +RUN dotnet restore ./source/LCU.API.Internal.Operations.IncentiveApi.UnitTests/LCU.API.Internal.Operations.IncentiveApi.UnitTests.csproj +RUN dotnet test -c Release --results-directory /testresults --logger "trx;LogFileName=test_results.trx" ./source/LCU.API.Internal.Operations.IncentiveApi.UnitTests/LCU.API.Internal.Operations.IncentiveApi.UnitTests.csproj + +# .NET Core Publish +FROM aspnetcore-build as publish +RUN dotnet publish ./source/LCU.API.Internal.Operations.IncentiveApi/LCU.API.Internal.Operations.IncentiveApi.csproj -c Release -o /dist + +# ASP.NET Core +FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine3.20 +RUN apk add --no-cache icu-libs && apk add --no-cache tzdata; +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false +ENV ASPNETCORE_ENVIRONMENT=Development + +WORKDIR /app +COPY --from=publish /dist . +ENTRYPOINT ["dotnet", "LCU.API.Internal.Operations.IncentiveApi.dll"] + diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveAPI.sln b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveAPI.sln new file mode 100644 index 0000000..8483a16 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveAPI.sln @@ -0,0 +1,99 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34322.80 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LCU.API.Internal.Operations.IncentiveApi", "LCU.API.Internal.Operations.IncentiveApi\LCU.API.Internal.Operations.IncentiveApi.csproj", "{29A04EEB-8970-41C5-BA8F-8B6B6C4CECE9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LCU.API.Internal.Operations.IncentiveApiLib", "LCU.API.Internal.Operations.IncentiveApiLib\LCU.API.Internal.Operations.IncentiveApiLib.csproj", "{B5CB3C54-9DC4-4D0A-80BB-0DA67BB716BF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LCU.API.Internal.Operations.IncentiveApi.UnitTests", "LCU.API.Internal.Operations.IncentiveApi.UnitTests\LCU.API.Internal.Operations.IncentiveApi.UnitTests.csproj", "{E23E5E87-B4C2-4EA0-AD9E-C08E8A47D3C5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LCU.Library.Internal.IncentiveApiModels", "LCU.Library.Internal.IncentiveApiModels\LCU.Library.Internal.IncentiveApiModels.csproj", "{49B35886-2376-4140-B144-A025B33B569F}" +EndProject +Project("{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}") = "LCU.API.Internal.Operations.IncentiveApiSql", "LCU.API.Internal.Operations.IncentiveApiSql\LCU.API.Internal.Operations.IncentiveApiSql.sqlproj", "{5D6DC990-F038-45D7-BBFA-06CAB0682B4F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {29A04EEB-8970-41C5-BA8F-8B6B6C4CECE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29A04EEB-8970-41C5-BA8F-8B6B6C4CECE9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29A04EEB-8970-41C5-BA8F-8B6B6C4CECE9}.Debug|x64.ActiveCfg = Debug|Any CPU + {29A04EEB-8970-41C5-BA8F-8B6B6C4CECE9}.Debug|x64.Build.0 = Debug|Any CPU + {29A04EEB-8970-41C5-BA8F-8B6B6C4CECE9}.Debug|x86.ActiveCfg = Debug|Any CPU + {29A04EEB-8970-41C5-BA8F-8B6B6C4CECE9}.Debug|x86.Build.0 = Debug|Any CPU + {29A04EEB-8970-41C5-BA8F-8B6B6C4CECE9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29A04EEB-8970-41C5-BA8F-8B6B6C4CECE9}.Release|Any CPU.Build.0 = Release|Any CPU + {29A04EEB-8970-41C5-BA8F-8B6B6C4CECE9}.Release|x64.ActiveCfg = Release|Any CPU + {29A04EEB-8970-41C5-BA8F-8B6B6C4CECE9}.Release|x64.Build.0 = Release|Any CPU + {29A04EEB-8970-41C5-BA8F-8B6B6C4CECE9}.Release|x86.ActiveCfg = Release|Any CPU + {29A04EEB-8970-41C5-BA8F-8B6B6C4CECE9}.Release|x86.Build.0 = Release|Any CPU + {B5CB3C54-9DC4-4D0A-80BB-0DA67BB716BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B5CB3C54-9DC4-4D0A-80BB-0DA67BB716BF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5CB3C54-9DC4-4D0A-80BB-0DA67BB716BF}.Debug|x64.ActiveCfg = Debug|Any CPU + {B5CB3C54-9DC4-4D0A-80BB-0DA67BB716BF}.Debug|x64.Build.0 = Debug|Any CPU + {B5CB3C54-9DC4-4D0A-80BB-0DA67BB716BF}.Debug|x86.ActiveCfg = Debug|Any CPU + {B5CB3C54-9DC4-4D0A-80BB-0DA67BB716BF}.Debug|x86.Build.0 = Debug|Any CPU + {B5CB3C54-9DC4-4D0A-80BB-0DA67BB716BF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B5CB3C54-9DC4-4D0A-80BB-0DA67BB716BF}.Release|Any CPU.Build.0 = Release|Any CPU + {B5CB3C54-9DC4-4D0A-80BB-0DA67BB716BF}.Release|x64.ActiveCfg = Release|Any CPU + {B5CB3C54-9DC4-4D0A-80BB-0DA67BB716BF}.Release|x64.Build.0 = Release|Any CPU + {B5CB3C54-9DC4-4D0A-80BB-0DA67BB716BF}.Release|x86.ActiveCfg = Release|Any CPU + {B5CB3C54-9DC4-4D0A-80BB-0DA67BB716BF}.Release|x86.Build.0 = Release|Any CPU + {E23E5E87-B4C2-4EA0-AD9E-C08E8A47D3C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E23E5E87-B4C2-4EA0-AD9E-C08E8A47D3C5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E23E5E87-B4C2-4EA0-AD9E-C08E8A47D3C5}.Debug|x64.ActiveCfg = Debug|Any CPU + {E23E5E87-B4C2-4EA0-AD9E-C08E8A47D3C5}.Debug|x64.Build.0 = Debug|Any CPU + {E23E5E87-B4C2-4EA0-AD9E-C08E8A47D3C5}.Debug|x86.ActiveCfg = Debug|Any CPU + {E23E5E87-B4C2-4EA0-AD9E-C08E8A47D3C5}.Debug|x86.Build.0 = Debug|Any CPU + {E23E5E87-B4C2-4EA0-AD9E-C08E8A47D3C5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E23E5E87-B4C2-4EA0-AD9E-C08E8A47D3C5}.Release|Any CPU.Build.0 = Release|Any CPU + {E23E5E87-B4C2-4EA0-AD9E-C08E8A47D3C5}.Release|x64.ActiveCfg = Release|Any CPU + {E23E5E87-B4C2-4EA0-AD9E-C08E8A47D3C5}.Release|x64.Build.0 = Release|Any CPU + {E23E5E87-B4C2-4EA0-AD9E-C08E8A47D3C5}.Release|x86.ActiveCfg = Release|Any CPU + {E23E5E87-B4C2-4EA0-AD9E-C08E8A47D3C5}.Release|x86.Build.0 = Release|Any CPU + {49B35886-2376-4140-B144-A025B33B569F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {49B35886-2376-4140-B144-A025B33B569F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {49B35886-2376-4140-B144-A025B33B569F}.Debug|x64.ActiveCfg = Debug|Any CPU + {49B35886-2376-4140-B144-A025B33B569F}.Debug|x64.Build.0 = Debug|Any CPU + {49B35886-2376-4140-B144-A025B33B569F}.Debug|x86.ActiveCfg = Debug|Any CPU + {49B35886-2376-4140-B144-A025B33B569F}.Debug|x86.Build.0 = Debug|Any CPU + {49B35886-2376-4140-B144-A025B33B569F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {49B35886-2376-4140-B144-A025B33B569F}.Release|Any CPU.Build.0 = Release|Any CPU + {49B35886-2376-4140-B144-A025B33B569F}.Release|x64.ActiveCfg = Release|Any CPU + {49B35886-2376-4140-B144-A025B33B569F}.Release|x64.Build.0 = Release|Any CPU + {49B35886-2376-4140-B144-A025B33B569F}.Release|x86.ActiveCfg = Release|Any CPU + {49B35886-2376-4140-B144-A025B33B569F}.Release|x86.Build.0 = Release|Any CPU + {5D6DC990-F038-45D7-BBFA-06CAB0682B4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D6DC990-F038-45D7-BBFA-06CAB0682B4F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D6DC990-F038-45D7-BBFA-06CAB0682B4F}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {5D6DC990-F038-45D7-BBFA-06CAB0682B4F}.Debug|x64.ActiveCfg = Debug|Any CPU + {5D6DC990-F038-45D7-BBFA-06CAB0682B4F}.Debug|x64.Build.0 = Debug|Any CPU + {5D6DC990-F038-45D7-BBFA-06CAB0682B4F}.Debug|x64.Deploy.0 = Debug|Any CPU + {5D6DC990-F038-45D7-BBFA-06CAB0682B4F}.Debug|x86.ActiveCfg = Debug|Any CPU + {5D6DC990-F038-45D7-BBFA-06CAB0682B4F}.Debug|x86.Build.0 = Debug|Any CPU + {5D6DC990-F038-45D7-BBFA-06CAB0682B4F}.Debug|x86.Deploy.0 = Debug|Any CPU + {5D6DC990-F038-45D7-BBFA-06CAB0682B4F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D6DC990-F038-45D7-BBFA-06CAB0682B4F}.Release|Any CPU.Build.0 = Release|Any CPU + {5D6DC990-F038-45D7-BBFA-06CAB0682B4F}.Release|Any CPU.Deploy.0 = Release|Any CPU + {5D6DC990-F038-45D7-BBFA-06CAB0682B4F}.Release|x64.ActiveCfg = Release|Any CPU + {5D6DC990-F038-45D7-BBFA-06CAB0682B4F}.Release|x64.Build.0 = Release|Any CPU + {5D6DC990-F038-45D7-BBFA-06CAB0682B4F}.Release|x64.Deploy.0 = Release|Any CPU + {5D6DC990-F038-45D7-BBFA-06CAB0682B4F}.Release|x86.ActiveCfg = Release|Any CPU + {5D6DC990-F038-45D7-BBFA-06CAB0682B4F}.Release|x86.Build.0 = Release|Any CPU + {5D6DC990-F038-45D7-BBFA-06CAB0682B4F}.Release|x86.Deploy.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {91D18C3F-574F-4696-90D6-F7FF07BF0354} + EndGlobalSection +EndGlobal diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Authorization/PermissionAuthorizationHandlerTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Authorization/PermissionAuthorizationHandlerTest.cs new file mode 100644 index 0000000..37cb637 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Authorization/PermissionAuthorizationHandlerTest.cs @@ -0,0 +1,174 @@ +using LCU.API.Internal.Operations.IncentiveApi.Authorization; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using Microsoft.AspNetCore.Authorization; +using Moq; +using NUnit.Framework; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Application.UnitTests.Authorization +{ + public class WrappedPermissionAuthorizationHandler : PermissionAuthorizationHandler + { + public WrappedPermissionAuthorizationHandler(ICurrentUserService currentUserService, IUserService userService) + : base(currentUserService, userService) + { + } + + public async Task WrappedHandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) + { + await HandleRequirementAsync(context, requirement); + } + } + + public class PermissionAuthorizationHandlerTest + { + private WrappedPermissionAuthorizationHandler _handler; + private Mock _mockCurrentUserService; + private Mock _mockUserService; + + [SetUp] + public void Setup() + { + _mockUserService = new Mock(); + _mockCurrentUserService = new Mock(); + _handler = new WrappedPermissionAuthorizationHandler(_mockCurrentUserService.Object, + _mockUserService.Object); + } + + [Test] + public async Task HandleAsync_MatchingPermission_ContextHasSucceeded() + { + var appUser = new ApplicationUser { Email = "User@landmarkcu.com" }; + var user = new AppUser { + Role = Role.OpsHelpDesk, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 55, + Role = Role.OpsHelpDesk + } + } + }; + _mockCurrentUserService.Setup(service => service.GetCurrentUser()) + .Returns(appUser); + + _mockUserService.Setup(service => service.UserHasAnyRequiredPermission(appUser.Email, It.IsAny>())) + .ReturnsAsync(true); + + var requirement = new PermissionRequirement("VIEW_DASHBOARD"); + var requirements = new List { requirement }; + + var _fakeContext = new AuthorizationHandlerContext(requirements, null, null); + + await _handler.WrappedHandleRequirementAsync(_fakeContext, requirement); + + Assert.True(_fakeContext.HasSucceeded); + } + + [Test] + public async Task HandleAsync_MatchingAdditionalPermission_ContextHasSucceeded() + { + var appUser = new ApplicationUser { Email = "User@landmarkcu.com" }; + var user = new AppUser { + Role = Role.OpsHelpDesk, + AdditionalPermissions = new List() + { + new UserPermission() + { + Permission = Permission.ViewDashboard + } + } + }; + _mockCurrentUserService.Setup(service => service.GetCurrentUser()) + .Returns(appUser); + + _mockUserService.Setup(service => service.UserHasAnyRequiredPermission(appUser.Email, It.IsAny>())) + .ReturnsAsync(true); + + var requirement = new PermissionRequirement("VIEW_DASHBOARD"); + var requirements = new List { requirement }; + + var _fakeContext = new AuthorizationHandlerContext(requirements, null, null); + + await _handler.WrappedHandleRequirementAsync(_fakeContext, requirement); + + Assert.True(_fakeContext.HasSucceeded); + } + + [Test] + public async Task HandleAsync_NoMatchingPermission_ContextHasFailed() + { + var appUser = new ApplicationUser { Email = "User@landmarkcu.com" }; + var user = new AppUser + { + Role = Role.OpsHelpDesk + }; + _mockCurrentUserService.Setup(service => service.GetCurrentUser()) + .Returns(appUser); + + _mockUserService.Setup(service => service.GetUserByAdEmailAsync(appUser.Email)) + .ReturnsAsync(user); + + var requirement = new PermissionRequirement("NO_MATCH"); + var requirements = new List { requirement }; + var _fakeContext = new AuthorizationHandlerContext(requirements, null, null); + + await _handler.WrappedHandleRequirementAsync(_fakeContext, requirement); + + Assert.True(_fakeContext.HasFailed); + } + + [Test] + public async Task HandleAsync_NoApplicationUserEmail_ContextHasFailed() + { + var appUser = new ApplicationUser(); + _mockCurrentUserService.Setup(service => service.GetCurrentUser()) + .Returns(appUser); + var requirement = new PermissionRequirement(""); + var requirements = new List { requirement }; + var _fakeContext = new AuthorizationHandlerContext(requirements, null, null); + + await _handler.WrappedHandleRequirementAsync(_fakeContext, requirement); + + Assert.True(_fakeContext.HasFailed); + } + + [Test] + public async Task HandleAsync_NoUserFound_ContextHasFailed() + { + var appUser = new ApplicationUser { Email = "User@landmarkcu.com" }; + _mockCurrentUserService.Setup(service => service.GetCurrentUser()) + .Returns(appUser); + _mockUserService.Setup(service => service.GetUserByAdEmailAsync(appUser.Email)) + .ReturnsAsync((AppUser)null); + var requirement = new PermissionRequirement(""); + var requirements = new List { requirement }; + var _fakeContext = new AuthorizationHandlerContext(requirements, null, null); + + await _handler.WrappedHandleRequirementAsync(_fakeContext, requirement); + + Assert.True(_fakeContext.HasFailed); + } + + [Test] + public async Task HandleAsync_UserServiceException_ContextHasFailed() + { + var appUser = new ApplicationUser { Email = "User@landmarkcu.com" }; + _mockCurrentUserService.Setup(service => service.GetCurrentUser()) + .Returns(appUser); + _mockUserService.Setup(service => service.GetUserByAdEmailAsync(appUser.Email)) + .ThrowsAsync(new System.Exception("err")); + var requirement = new PermissionRequirement(""); + var requirements = new List { requirement }; + var _fakeContext = new AuthorizationHandlerContext(requirements, null, null); + + await _handler.WrappedHandleRequirementAsync(_fakeContext, requirement); + + Assert.True(_fakeContext.HasFailed); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Authorization/PermissionPolicyProviderTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Authorization/PermissionPolicyProviderTest.cs new file mode 100644 index 0000000..69c9355 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Authorization/PermissionPolicyProviderTest.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Options; +using NUnit.Framework; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApi.Authorization; + +namespace Application.UnitTests.Authorization +{ + public class PermissionPolicyProviderTest + { + private PermissionPolicyProvider _provider; + + [SetUp] + public void Setup() + { + var options = Options.Create(new AuthorizationOptions()); + _provider = new PermissionPolicyProvider(options); + } + + [Test] + public async Task GetPolicyAsync_GivenPolicyName_ReturnsPermissionsPolicy() + { + var result = await _provider.GetPolicyAsync("Permission"); + + Assert.IsInstanceOf(result.Requirements.First()); + } + + [Test] + public async Task GetPolicyAsync_GivenParameters_PolicyWithPermissions() + { + var result = await _provider.GetPolicyAsync("Permissionp1,p2"); + + var requirement = result.Requirements.First() as PermissionRequirement; + Assert.AreEqual("p1,p2", requirement.Permissions); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Configuration/FileOperationsApiConfigurationTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Configuration/FileOperationsApiConfigurationTest.cs new file mode 100644 index 0000000..31f8db5 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Configuration/FileOperationsApiConfigurationTest.cs @@ -0,0 +1,25 @@ +using System; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using NUnit.Framework; + +namespace Application.UnitTests.Configuration +{ + public class FileOperationsApiConfigurationTest + { + [Test] + public void GetFileContentEndpoint_ValidUri() + { + var fileOperationsApiConfiguration = new FileOperationsApiConfiguration() + { + Url = "https://test/", + GetFileContentEndpoint = "test" + }; + + var result = fileOperationsApiConfiguration.GetFileContentUri; + + Assert.NotNull(result); + var testUri = new Uri("https://test/test"); + Assert.AreEqual(testUri, result); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/ActivityControllerTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/ActivityControllerTest.cs new file mode 100644 index 0000000..4b60871 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/ActivityControllerTest.cs @@ -0,0 +1,786 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using Microsoft.AspNetCore.Mvc; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApi.Controllers; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; + +namespace Application.UnitTests.Controllers +{ + public class ActivityControllerTest + { + private ActivityController _controller; + private Mock _mockActivityService; + private Mock _mockActivityDetailService; + private Mock _mockUserService; + private Mock _mockCurrentUserService; + private Mock _mockDashboardService; + private Mock _mockActivityDecisionService; + private Mock _mockEStatementService; + + [SetUp] + public void Setup() + { + _mockActivityService = new Mock(); + _mockActivityDetailService = new Mock(); + _mockUserService = new Mock(); + _mockCurrentUserService = new Mock(); + _mockDashboardService = new Mock(); + _mockActivityDecisionService = new Mock(); + _mockEStatementService = new Mock(); + _controller = new ActivityController(_mockActivityService.Object, _mockActivityDetailService.Object, _mockUserService.Object, + _mockCurrentUserService.Object, _mockDashboardService.Object, _mockActivityDecisionService.Object, _mockEStatementService.Object); + } + + [Test] + public async Task Get_OK() + { + var filter = new ActivityFilter() + { + Limit = 999, + Page = 1 + }; + var results = new PagedResult(); + results.Items = new List() + { + new Activity() + { + Id = 1 + } + }; + results.Total = 1; + + _mockActivityService + .Setup(service => service.ListByFilterAsync(filter)).ReturnsAsync(results); + var result = await _controller.Get(filter); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task POST_OK() + { + var activity = new Activity() + { + Product = Product.CheckCard, + State = ActivityState.Created, + EmployeeId = "55", + DepartmentId = 55, + SourceId = "444", + SourceSystem = SystemType.Dna + }; + _mockActivityService + .Setup(service => service.SaveActivityAsync(activity)).ReturnsAsync(1); + + var result = await _controller.CreateActivity(activity); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task Get_CurrentUserActivity_OK() + { + var user1 = new ApplicationUser() + { + Email = "tester@test.com", + FirstName = "Test", + LastName = "McTester" + }; + var user = new AppUser() + { + Role = Role.Admin, + AdEmail = "tester@test.com", + BaseRole = "Tester", + EmployeeId = "11771", + FullName = "Test McTester", + HomeDepartment = "HOME", + PersonId = 565 + }; + var range = new MonthRange(2021,1,2021,2); + var goals = new EmployeeProgress(user, range, new List()); + + _mockCurrentUserService.Setup(service => service.GetCurrentUser()).Returns(user1); + _mockUserService.Setup(service => service.GetUserByAdEmailAsync(user1.Email)).ReturnsAsync(user); + _mockDashboardService + .Setup(service => service.GetEmployeeProgressForMonthRangeAsync(goals.Employee, range)) + .ReturnsAsync(goals); + + var result = await _controller.GetCurrentUserProgress(range.FromMonth, range.FromYear, range.ToMonth, range.ToYear); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task Get_CurrentUserDepartmentProgress_OK() + { + var user1 = new ApplicationUser() + { + Email = "tester@test.com", + FirstName = "Test", + LastName = "McTester" + }; + var user = new AppUser() + { + Role = Role.Admin, + AdEmail = "tester@test.com", + BaseRole = "Tester", + EmployeeId = "11771", + FullName = "Test McTester", + HomeDepartment = "HOME", + PersonId = 565, + Departments = new List { + new UserDepartment { + DepartmentId = 1, + StartDate = new DateTime(2021, 7, 1), + EndDate = new DateTime(2021, 7, 31) + } + } + }; + var range = new MonthRange(2021,1,2021,2); + var goals = new List { new DepartmentProgress(1, range) }; + + _mockCurrentUserService.Setup(service => service.GetCurrentUser()).Returns(user1); + _mockUserService.Setup(service => service.GetUserByAdEmailAsync(user1.Email)).ReturnsAsync(user); + _mockActivityService + .Setup(service => service.GetDepartmentActivityForMonthRangeAsync(user.Departments, range)) + .ReturnsAsync(goals); + + var result = await _controller.GetCurrentUserProgress(range.FromMonth, range.FromYear, range.ToMonth, range.ToYear); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task Get_CurrentUserRegionProgress_OK() + { + var user1 = new ApplicationUser() + { + Email = "tester@test.com", + FirstName = "Test", + LastName = "McTester" + }; + var user = new AppUser() + { + Role = Role.RegionalManager, + AdEmail = "tester@test.com", + BaseRole = "Tester", + EmployeeId = "11771", + FullName = "Test McTester", + HomeDepartment = "HOME", + PersonId = 565, + Departments = new List { + new UserDepartment { + DepartmentId = 1, + StartDate = new DateTime(2021, 7, 1), + EndDate = new DateTime(2021, 7, 31), + Role = Role.RegionalManager, + Department = new Department() + { + Id = 5, + State = DepartmentState.Active, + DepartmentName = "Dept 1", + RegionCode = Region.North.Code + } + } + } + }; + var range = new MonthRange(2021,1,2021,2); + var progress = new RegionProgress(Region.North, range); + + _mockCurrentUserService.Setup(service => service.GetCurrentUser()).Returns(user1); + _mockUserService.Setup(service => service.GetUserByAdEmailAsync(user1.Email)).ReturnsAsync(user); + _mockDashboardService + .Setup(service => service.GetRegionProgressForMonthRangeAsync(Region.North, range)) + .ReturnsAsync(progress); + + var result = await _controller.GetRegionProgress(range.FromMonth, range.FromYear, range.ToMonth, range.ToYear, Region.East.Code); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task GetActivityStateChanges_OK() + { + _mockActivityService + .Setup(service => service.GetActivityStateChanges(It.IsAny())).ReturnsAsync( + new List() + { + new ActivityStateChange() + { + Id = 1, + ActivityId = 5 + }, + new ActivityStateChange() + { + Id = 2, + ActivityId = 5 + }, + new ActivityStateChange() + { + Id = 3, + ActivityId = 5 + } + }); + + var result = await _controller.GetActivityStateChanges(5); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + + + var value = okResult.Value as IReadOnlyCollection; + Assert.NotNull(value); + Assert.AreEqual(3, value.Count); + } + + [Test] + public async Task GetActivityStateChanges_Exception() + { + _mockActivityService.Setup(x => x.GetActivityStateChanges(It.IsAny())) + .ThrowsAsync(new Exception("oops")); + + var result = await _controller.GetActivityStateChanges(5); + Assert.NotNull(result); + + var badResult = result as StatusCodeResult; + Assert.NotNull(badResult); + Assert.AreEqual(500, badResult.StatusCode); + } + + [Test] + public async Task Get_Single_Ok() + { + + _mockActivityService.Setup(x => x.GetActivity(5)) + .ReturnsAsync(new Activity() + { + Id = 5, + }); + + var result = await _controller.Get(5); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + + + var value = okResult.Value as Activity; + Assert.NotNull(value); + Assert.AreEqual(5, value.Id); + } + + [Test] + public async Task Get_Single_NotFound() + { + _mockActivityService.Setup(x => x.GetActivity(1)) + .ThrowsAsync(new NotFoundException("Not Found")); + + var result = await _controller.Get(1); + Assert.NotNull(result); + + var badRequestResult = result as BadRequestObjectResult; + Assert.NotNull(badRequestResult); + Assert.AreEqual(400, badRequestResult.StatusCode); + Assert.AreEqual("Activity Not Found.", badRequestResult.Value); + } + + [Test] + public async Task Get_Single_Exception() + { + _mockActivityService.Setup(x => x.GetActivity(1)) + .ThrowsAsync(new Exception("Internal Server Error")); + + var result = await _controller.Get(1); + Assert.NotNull(result); + + var badResult = result as StatusCodeResult; + Assert.NotNull(badResult); + Assert.AreEqual(500, badResult.StatusCode); + } + + [Test] + public async Task ManuallyUpdateActivityState_Ok() + { + var stateChange = new ActivityStateChange + { + ActivityId = 11, + Note = "Change", + State = ActivityState.Accepted + }; + var activityResult = new Activity + { + Id = 11, + State = ActivityState.Accepted + }; + _mockCurrentUserService.Setup(mock => mock.GetCurrentUser()) + .Returns(new ApplicationUser { Email = "user@landmarkcu.com" }); + _mockUserService.Setup(mock => mock.GetUserByAdEmailAsync(It.Is(s => s == "user@landmarkcu.com"))) + .ReturnsAsync(new AppUser()); + _mockActivityService.Setup(mock => mock.ValidateCanApplyActivityStateChange(It.IsAny(), stateChange)); + _mockActivityService.Setup(mock => mock.ApplyStateChange(stateChange,true)) + .ReturnsAsync(activityResult); + + var result = await _controller.ManuallyUpdateActivityState(stateChange); + + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task ManuallyUpdateActivityState_AppUserNotFound() + { + var stateChange = new ActivityStateChange + { + ActivityId = 11, + Note = "Change", + State = ActivityState.Accepted + }; + _mockCurrentUserService.Setup(mock => mock.GetCurrentUser()) + .Returns((ApplicationUser)null); + + var result = await _controller.ManuallyUpdateActivityState(stateChange); + Assert.NotNull(result); + + var unauthorizedResult = result as UnauthorizedObjectResult; + Assert.NotNull(unauthorizedResult); + Assert.AreEqual(401, unauthorizedResult.StatusCode); + Assert.AreEqual("No AD User Found.", unauthorizedResult.Value); + } + + [Test] + public async Task ManuallyUpdateActivityState_UserNotFound() + { + var stateChange = new ActivityStateChange + { + ActivityId = 11, + Note = "Change", + State = ActivityState.Accepted + }; + _mockCurrentUserService.Setup(mock => mock.GetCurrentUser()) + .Returns(new ApplicationUser { Email = "user@landmarkcu.com" }); + _mockUserService.Setup(mock => mock.GetUserByAdEmailAsync(It.Is(s => s == "user@landmarkcu.com"))) + .ReturnsAsync((AppUser)null); + + var result = await _controller.ManuallyUpdateActivityState(stateChange); + Assert.NotNull(result); + + var unauthorizedResult = result as UnauthorizedObjectResult; + Assert.NotNull(unauthorizedResult); + Assert.AreEqual(401, unauthorizedResult.StatusCode); + Assert.AreEqual("No AD User Found.", unauthorizedResult.Value); + } + + [Test] + public async Task ManuallyUpdateActivityState_BadActivityStateChangeException() + { + var stateChange = new ActivityStateChange + { + ActivityId = 11, + Note = "Change", + State = ActivityState.Accepted + }; + var activityResult = new Activity + { + Id = 11, + State = ActivityState.Accepted + }; + _mockCurrentUserService.Setup(mock => mock.GetCurrentUser()) + .Returns(new ApplicationUser { Email = "user@landmarkcu.com" }); + _mockUserService.Setup(mock => mock.GetUserByAdEmailAsync(It.Is(s => s == "user@landmarkcu.com"))) + .ReturnsAsync(new AppUser()); + _mockActivityService.Setup(mock => mock.ValidateCanApplyActivityStateChange(It.IsAny(), stateChange)) + .ThrowsAsync(new BadActivityStateChangeException("Bad activity state change.")); + + var result = await _controller.ManuallyUpdateActivityState(stateChange); + + Assert.NotNull(result); + + var badRequestResult = result as BadRequestObjectResult; + Assert.NotNull(badRequestResult); + Assert.AreEqual(400, badRequestResult.StatusCode); + Assert.AreEqual("Bad activity state change.", badRequestResult.Value); + } + + [Test] + public async Task ManuallyUpdateActivityState_NotAuthorizedException() + { + var stateChange = new ActivityStateChange + { + ActivityId = 11, + Note = "Change", + State = ActivityState.Accepted + }; + var activityResult = new Activity + { + Id = 11, + State = ActivityState.Accepted + }; + _mockCurrentUserService.Setup(mock => mock.GetCurrentUser()) + .Returns(new ApplicationUser { Email = "user@landmarkcu.com" }); + _mockUserService.Setup(mock => mock.GetUserByAdEmailAsync(It.Is(s => s == "user@landmarkcu.com"))) + .ReturnsAsync(new AppUser()); + _mockActivityService.Setup(mock => mock.ValidateCanApplyActivityStateChange(It.IsAny(), stateChange)) + .ThrowsAsync(new NotAuthorizedException("Not Authorized.")); + + var result = await _controller.ManuallyUpdateActivityState(stateChange); + + Assert.NotNull(result); + + var objectResult = result as ObjectResult; + Assert.NotNull(objectResult); + Assert.AreEqual(403, objectResult.StatusCode); + Assert.AreEqual("Not Authorized.", objectResult.Value); + } + + [Test] + public async Task ManuallyUpdateActivityState_NotFoundException() + { + var stateChange = new ActivityStateChange + { + ActivityId = 11, + Note = "Change", + State = ActivityState.Accepted + }; + var activityResult = new Activity + { + Id = 11, + State = ActivityState.Accepted + }; + _mockCurrentUserService.Setup(mock => mock.GetCurrentUser()) + .Returns(new ApplicationUser { Email = "user@landmarkcu.com" }); + _mockUserService.Setup(mock => mock.GetUserByAdEmailAsync(It.Is(s => s == "user@landmarkcu.com"))) + .ReturnsAsync(new AppUser()); + _mockActivityService.Setup(mock => mock.ValidateCanApplyActivityStateChange(It.IsAny(), stateChange)) + .ThrowsAsync(new NotFoundException("Activity Not Found.")); + + var result = await _controller.ManuallyUpdateActivityState(stateChange); + + Assert.NotNull(result); + + var badRequestResult = result as BadRequestObjectResult; + Assert.NotNull(badRequestResult); + Assert.AreEqual(400, badRequestResult.StatusCode); + Assert.AreEqual("Activity Not Found.", badRequestResult.Value); + } + + [Test] + public async Task ManuallyUpdateActivityState_Exception() + { + var stateChange = new ActivityStateChange + { + ActivityId = 11, + Note = "Change", + State = ActivityState.Accepted + }; + var activityResult = new Activity + { + Id = 11, + State = ActivityState.Accepted + }; + _mockCurrentUserService.Setup(mock => mock.GetCurrentUser()) + .Returns(new ApplicationUser { Email = "user@landmarkcu.com" }); + _mockUserService.Setup(mock => mock.GetUserByAdEmailAsync(It.Is(s => s == "user@landmarkcu.com"))) + .ReturnsAsync(new AppUser()); + _mockActivityService.Setup(mock => mock.ValidateCanApplyActivityStateChange(It.IsAny(), stateChange)) + .ThrowsAsync(new Exception("Internal Server Error.")); + + var result = await _controller.ManuallyUpdateActivityState(stateChange); + + Assert.NotNull(result); + + var badResult = result as StatusCodeResult; + Assert.NotNull(badResult); + Assert.AreEqual(500, badResult.StatusCode); + } + + [Test] + public async Task RequestTransfer_Ok() + { + var request = new ActivityTransferRequest + { + ActivityStateBeforeTransfer = ActivityState.Accepted, + RequestedActivityId = 1, + NewDepartmentId = 2, + NewEmployeeId = "C0000" + }; + + var response = new ActivityTransferAction + { + ActivityStateBeforeTransfer = ActivityState.Accepted, + RequestedActivityId = 1, + NewActivity = new Activity + { + Product = Product.CreditCard, + Type = ActivityType.Referral, + Detail = new ActivityDetail + { + Activity = new Activity + { + Product = Product.CreditCard, + Type = ActivityType.Referral, + }, + AccountNumber = "1234567890" + } + } + }; + + _mockActivityService.Setup(mock => mock.RequestTransfer(request)).ReturnsAsync(response).Verifiable(); + + var result = await _controller.RequestTransfer(request); + + _mockActivityService.Verify(); + Assert.NotNull(result); + var okResult = result as OkObjectResult; + + var activityTransfer = okResult.Value as ActivityTransferAction; + + Assert.NotNull(okResult); + Assert.AreEqual("XXXXXX7890", activityTransfer.NewActivity.Detail.AccountNumber); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task RequestTransfer_NotFoundException() + { + var request = new ActivityTransferRequest + { + ActivityStateBeforeTransfer = ActivityState.Accepted, + RequestedActivityId = 1, + NewDepartmentId = 2, + NewEmployeeId = "C0000" + }; + _mockActivityService.Setup(mock => mock.RequestTransfer(It.IsAny())) + .ThrowsAsync(new NotFoundException("")); + + var result = await _controller.RequestTransfer(request); + + Assert.NotNull(result); + var badRequestResult = result as BadRequestObjectResult; + Assert.NotNull(badRequestResult); + Assert.AreEqual(400, badRequestResult.StatusCode); + Assert.AreEqual("Activity Not Found.", badRequestResult.Value); + } + + [Test] + public async Task RequestTransfer_BadActivityTransferRequestException() + { + var request = new ActivityTransferRequest + { + ActivityStateBeforeTransfer = ActivityState.Accepted, + RequestedActivityId = 1, + NewDepartmentId = 2, + NewEmployeeId = "C0000" + }; + _mockActivityService.Setup(mock => mock.RequestTransfer(It.IsAny())) + .ThrowsAsync(new BadActivityTransferActionException("Cannot complete request.")); + + var result = await _controller.RequestTransfer(request); + + Assert.NotNull(result); + var badRequestResult = result as BadRequestObjectResult; + Assert.NotNull(badRequestResult); + Assert.AreEqual(400, badRequestResult.StatusCode); + Assert.AreEqual("Cannot complete request.", badRequestResult.Value); + } + + [Test] + public async Task RequestTransfer_Exception() + { + var request = new ActivityTransferRequest + { + ActivityStateBeforeTransfer = ActivityState.Accepted, + RequestedActivityId = 1, + NewDepartmentId = 2, + NewEmployeeId = "C0000" + }; + _mockActivityService.Setup(mock => mock.RequestTransfer(It.IsAny())) + .ThrowsAsync(new Exception("Internal Server Error.")); + + var result = await _controller.RequestTransfer(request); + + Assert.NotNull(result); + var badResult = result as StatusCodeResult; + Assert.NotNull(badResult); + Assert.AreEqual(500, badResult.StatusCode); + } + + [Test] + public async Task ApproveTransfer_Ok() + { + var approval = new ActivityTransferApprovalRequest + { + Id = 1, + ApprovalState = ActivityTransferState.Accepted + }; + _mockCurrentUserService.Setup(mock => mock.GetCurrentUser()) + .Returns(new ApplicationUser { Email = "user@landmarkcu.com" }); + _mockUserService.Setup(mock => mock.GetUserByAdEmailAsync(It.Is(s => s == "user@landmarkcu.com"))) + .ReturnsAsync(new AppUser()); + _mockActivityService.Setup(mock => mock.ApproveTransfer(It.IsAny(), approval)) + .ReturnsAsync(new ActivityTransferAction { Id = 1 } ); + + var result = await _controller.ApproveTransfer(approval); + + Assert.NotNull(result); + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task ApproveTransfer_AppUserNotFound() + { + _mockCurrentUserService.Setup(mock => mock.GetCurrentUser()) + .Returns((ApplicationUser)null); + + var result = await _controller.ApproveTransfer(new ActivityTransferApprovalRequest()); + + Assert.NotNull(result); + var unauthorizedResult = result as UnauthorizedObjectResult; + Assert.NotNull(unauthorizedResult); + Assert.AreEqual(401, unauthorizedResult.StatusCode); + Assert.AreEqual("No AD User Found.", unauthorizedResult.Value); + } + + [Test] + public async Task ApproveTransfer_UserNotFound() + { + _mockCurrentUserService.Setup(mock => mock.GetCurrentUser()) + .Returns(new ApplicationUser { Email = "user@landmarkcu.com" }); + _mockUserService.Setup(mock => mock.GetUserByAdEmailAsync(It.Is(s => s == "user@landmarkcu.com"))) + .ReturnsAsync((AppUser)null); + + var result = await _controller.ApproveTransfer(new ActivityTransferApprovalRequest()); + + Assert.NotNull(result); + var unauthorizedResult = result as UnauthorizedObjectResult; + Assert.NotNull(unauthorizedResult); + Assert.AreEqual(401, unauthorizedResult.StatusCode); + Assert.AreEqual("No AD User Found.", unauthorizedResult.Value); + } + + [Test] + public async Task ApproveTransfer_NotFoundException() + { + _mockCurrentUserService.Setup(mock => mock.GetCurrentUser()) + .Returns(new ApplicationUser { Email = "user@landmarkcu.com" }); + _mockUserService.Setup(mock => mock.GetUserByAdEmailAsync(It.Is(s => s == "user@landmarkcu.com"))) + .ReturnsAsync(new AppUser()); + _mockActivityService + .Setup(mock => mock.ApproveTransfer(It.IsAny(), It.IsAny())) + .ThrowsAsync(new NotFoundException("")); + + var result = await _controller.ApproveTransfer(new ActivityTransferApprovalRequest()); + + Assert.NotNull(result); + var badRequestResult = result as BadRequestObjectResult; + Assert.NotNull(badRequestResult); + Assert.AreEqual(400, badRequestResult.StatusCode); + Assert.AreEqual("Activity Transfer Not Found.", badRequestResult.Value); + } + + [Test] + public async Task ApproveTransfer_BadActivityTransferActionException() + { + _mockCurrentUserService.Setup(mock => mock.GetCurrentUser()) + .Returns(new ApplicationUser { Email = "user@landmarkcu.com" }); + _mockUserService.Setup(mock => mock.GetUserByAdEmailAsync(It.Is(s => s == "user@landmarkcu.com"))) + .ReturnsAsync(new AppUser()); + _mockActivityService + .Setup(mock => mock.ApproveTransfer(It.IsAny(), It.IsAny())) + .ThrowsAsync(new BadActivityTransferActionException("Cannot complete request.")); + + var result = await _controller.ApproveTransfer(new ActivityTransferApprovalRequest()); + + Assert.NotNull(result); + var badRequestResult = result as BadRequestObjectResult; + Assert.NotNull(badRequestResult); + Assert.AreEqual(400, badRequestResult.StatusCode); + Assert.AreEqual("Cannot complete request.", badRequestResult.Value); + } + + [Test] + public async Task ApproveTransfer_Exception() + { + _mockCurrentUserService.Setup(mock => mock.GetCurrentUser()) + .Returns(new ApplicationUser { Email = "user@landmarkcu.com" }); + _mockUserService.Setup(mock => mock.GetUserByAdEmailAsync(It.Is(s => s == "user@landmarkcu.com"))) + .ReturnsAsync(new AppUser()); + _mockActivityService + .Setup(mock => mock.ApproveTransfer(It.IsAny(), It.IsAny())) + .ThrowsAsync(new Exception("Internal Server Error.")); + + var result = await _controller.ApproveTransfer(new ActivityTransferApprovalRequest()); + + Assert.NotNull(result); + var badResult = result as StatusCodeResult; + Assert.NotNull(badResult); + Assert.AreEqual(500, badResult.StatusCode); + } + + [Test] + public async Task GetTransferAction_Ok() + { + var actionFromService = new ActivityTransferAction + { + Id = 1, + State = ActivityTransferState.Requested + }; + _mockActivityService.Setup(mock => mock.GetRequestedTransfer(1)) + .ReturnsAsync(actionFromService); + + var result = await _controller.GetTransferAction(1); + + Assert.NotNull(result); + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task GetTransferAction_Exception() + { + _mockActivityService.Setup(mock => mock.GetRequestedTransfer(1)) + .ThrowsAsync(new Exception("Internal Server Error.")); + + var result = await _controller.GetTransferAction(1); + + Assert.NotNull(result); + var badResult = result as StatusCodeResult; + Assert.NotNull(badResult); + Assert.AreEqual(500, badResult.StatusCode); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/ActivityImportControllerTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/ActivityImportControllerTest.cs new file mode 100644 index 0000000..fd3ddbc --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/ActivityImportControllerTest.cs @@ -0,0 +1,113 @@ +using System; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApi.Services.QueueServices; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using Microsoft.AspNetCore.Mvc; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApi.Controllers; +using LCU.API.Internal.Operations.IncentiveApi.Services; + +namespace Application.UnitTests.Controllers +{ + public class ActivityImportControllerTest + { + private ActivityImportController _controller; + private Mock _mockQueueCreditCardImportService; + private Mock _mockQueueVehicleImportService; + private Mock _mockQueueAlliedGapMbpService; + private Mock _mockQueueActionCreditCardService; + private Mock _mockQueueActionVehicleService; + private Mock _mockEStatementService; + + [SetUp] + public void Setup() + { + _mockQueueCreditCardImportService = new Mock(); + _mockQueueVehicleImportService = new Mock(); + _mockQueueActionVehicleService = new Mock(); + _mockQueueActionCreditCardService = new Mock(); + _mockQueueAlliedGapMbpService = new Mock(); + _controller = new ActivityImportController(_mockQueueCreditCardImportService.Object, _mockQueueVehicleImportService.Object, + _mockQueueActionCreditCardService.Object, + _mockQueueActionVehicleService.Object, _mockQueueAlliedGapMbpService.Object); + } + + [Test] + public async Task QueueCreditCardImportRequest_Accepted() + { + _mockQueueCreditCardImportService.Setup(x => x.QueueCreditCardSalesImport(It.IsAny())); + + var result = await _controller.QueueCreditCardImportRequest(It.IsAny()); + + var acceptedResult = result as AcceptedResult; + Assert.NotNull(acceptedResult); + Assert.AreEqual(202, acceptedResult.StatusCode); + } + + [Test] + public async Task QueueCreditCardImportRequest_ServiceFails_ServiceException() + { + _mockQueueCreditCardImportService.Setup(x => x.QueueCreditCardSalesImport(It.IsAny())) + .ThrowsAsync(new ServiceException("Failure")); + + var result = await _controller.QueueCreditCardImportRequest(It.IsAny()); + + var objectResult = result as ObjectResult; + Assert.NotNull(objectResult); + Assert.AreEqual(500, objectResult.StatusCode); + } + + [Test] + public async Task QueueCreditCardImportRequest_ServiceFails_Exception() + { + _mockQueueCreditCardImportService.Setup(x => x.QueueCreditCardSalesImport(It.IsAny())) + .ThrowsAsync(new Exception("Failure")); + + var result = await _controller.QueueCreditCardImportRequest(It.IsAny()); + + var objectResult = result as StatusCodeResult; + Assert.NotNull(objectResult); + Assert.AreEqual(500, objectResult.StatusCode); + } + + [Test] + public async Task QueueIndirectVehicleImportRequest_Accepted() + { + _mockQueueVehicleImportService.Setup(x => x.QueueIndirectVehicleSalesImport(It.IsAny())); + + var result = await _controller.QueueIndirectVehicleImportRequest(It.IsAny()); + + var acceptedResult = result as AcceptedResult; + Assert.NotNull(acceptedResult); + Assert.AreEqual(202, acceptedResult.StatusCode); + } + + [Test] + public async Task QueueIndirectVehicleImportRequest_ServiceFails_ServiceException() + { + _mockQueueVehicleImportService.Setup(x => x.QueueIndirectVehicleSalesImport(It.IsAny())) + .ThrowsAsync(new ServiceException("Failure")); + + var result = await _controller.QueueIndirectVehicleImportRequest(It.IsAny()); + + var objectResult = result as ObjectResult; + Assert.NotNull(objectResult); + Assert.AreEqual(500, objectResult.StatusCode); + } + + [Test] + public async Task QueueeIndirectVehicleImportRequest_ServiceFails_Exception() + { + _mockQueueVehicleImportService.Setup(x => x.QueueIndirectVehicleSalesImport(It.IsAny())) + .ThrowsAsync(new Exception("Failure")); + + var result = await _controller.QueueIndirectVehicleImportRequest(It.IsAny()); + + var objectResult = result as StatusCodeResult; + Assert.NotNull(objectResult); + Assert.AreEqual(500, objectResult.StatusCode); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/CommissionControllerTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/CommissionControllerTest.cs new file mode 100644 index 0000000..a1e4772 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/CommissionControllerTest.cs @@ -0,0 +1,103 @@ +using System.Collections.Generic; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using Microsoft.AspNetCore.Mvc; +using Moq; +using NUnit.Framework; +using System; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApi.Controllers; +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; + +namespace Application.UnitTests.Controllers +{ + public class CommissionControllerTest + { + private CommissionController _controller; + private Mock _mockCommissionService; + + + [SetUp] + public void Setup() + { + _mockCommissionService = new Mock(); + _controller = new CommissionController(_mockCommissionService.Object); + } + + [Test] + public async Task Get_OK() + { + _mockCommissionService.Setup(mock => mock.GetCommissionsAsync()) + .ReturnsAsync(new List + { + new Commission() + }); + + var result = await _controller.Get(); + + Assert.NotNull(result); + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task Get_ServiceException_Returns500() + { + _mockCommissionService.Setup(mock => mock.GetCommissionsAsync()) + .ThrowsAsync(new Exception("error thrown")); + + var result = await _controller.Get(); + + Assert.NotNull(result); + var errorResult = result as StatusCodeResult; + Assert.NotNull(errorResult); + Assert.AreEqual(500, errorResult.StatusCode); + } + + [Test] + public async Task SetCommissions_Created() + { + var commissions = new List(); + _mockCommissionService.Setup(mock => mock.SaveCommissionsAsync(commissions)); + + var result = await _controller.SetCommissions(commissions); + + Assert.NotNull(result); + var noContentResult = result as NoContentResult; + Assert.NotNull(noContentResult); + Assert.AreEqual(204, noContentResult.StatusCode); + } + + [Test] + public async Task SetCommissions_ServiceException_Returns500() + { + var commissions = new List(); + _mockCommissionService.Setup(mock => mock.SaveCommissionsAsync(commissions)) + .ThrowsAsync(new Exception("error thrown")); + + var result = await _controller.SetCommissions(commissions); + + Assert.NotNull(result); + var errorResult = result as StatusCodeResult; + Assert.NotNull(errorResult); + Assert.AreEqual(500, errorResult.StatusCode); + } + + [Test] + public async Task SetCommissions_CommissionExistsException_Returns409() + { + var commissions = new List(); + _mockCommissionService.Setup(mock => mock.SaveCommissionsAsync(commissions)) + .ThrowsAsync(new CommissionExistsException("commission exists error thrown")); + + var result = await _controller.SetCommissions(commissions); + + Assert.NotNull(result); + var conflictResult = result as ConflictObjectResult; + Assert.NotNull(conflictResult); + Assert.AreEqual(409, conflictResult.StatusCode); + Assert.AreEqual("commission exists error thrown", conflictResult.Value); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/DeadActivityControllerTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/DeadActivityControllerTest.cs new file mode 100644 index 0000000..c11fa58 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/DeadActivityControllerTest.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Microsoft.AspNetCore.Mvc; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApi.Controllers; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; + +namespace Application.UnitTests.Controllers +{ + public class DeadActivityControllerTest + { + private DeadActivityController _controller; + private Mock _mockDeadActivityService; + + [SetUp] + public void Setup() + { + _mockDeadActivityService = new Mock(); + _controller = new DeadActivityController(_mockDeadActivityService.Object); + } + + [Test] + public async Task Get_OK() + { + var filter = new DeadActivityFilter() + { + Limit = 999, + Page = 1 + }; + var results = new PagedResult(); + results.Items = new List() + { + new DeadActivity("1234", SystemType.Action, DeadActivityReason.Other, "test") + }; + results.Total = 1; + + _mockDeadActivityService + .Setup(service => service.ListByFilterAsync(filter)).ReturnsAsync(results); + var result = await _controller.Get(filter); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task Get_Single_Ok() + { + _mockDeadActivityService.Setup(x => x.GetDeadActivity(5)) + .ReturnsAsync(new DeadActivity("1234", SystemType.Action, DeadActivityReason.Other, "test")); + + var result = await _controller.Get(5); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + + var value = okResult.Value as DeadActivity; + Assert.NotNull(value); + Assert.AreEqual("1234", value.SourceId); + } + + [Test] + public async Task Get_Single_NotFound() + { + _mockDeadActivityService.Setup(x => x.GetDeadActivity(1)) + .ThrowsAsync(new NotFoundException("Not Found")); + + var result = await _controller.Get(1); + Assert.NotNull(result); + + var badRequestResult = result as BadRequestObjectResult; + Assert.NotNull(badRequestResult); + Assert.AreEqual(400, badRequestResult.StatusCode); + Assert.AreEqual("DeadActivity Not Found.", badRequestResult.Value); + } + + [Test] + public async Task Get_Single_Exception() + { + _mockDeadActivityService.Setup(x => x.GetDeadActivity(1)) + .ThrowsAsync(new Exception("Internal Server Error")); + + var result = await _controller.Get(1); + Assert.NotNull(result); + + var badResult = result as StatusCodeResult; + Assert.NotNull(badResult); + Assert.AreEqual(500, badResult.StatusCode); + } + + [Test] + public async Task ProcessDeadActivityState_Ok() + { + var deadActivity = new DeadActivity("1234", SystemType.Action, DeadActivityReason.Other, "test"); + _mockDeadActivityService.Setup(mock => mock.ProcessDeadActivity(1,2)) + .ReturnsAsync(deadActivity); + + var result = await _controller.Delete(1,2); + + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task ProcessDeadActivityState_Ok_NoLink() + { + var deadActivity = new DeadActivity("1234", SystemType.Action, DeadActivityReason.Other, "test"); + _mockDeadActivityService.Setup(mock => mock.ProcessDeadActivity(1,-1)) + .ReturnsAsync(deadActivity); + + var result = await _controller.Delete(1); + + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/DeadActiviyControlerTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/DeadActiviyControlerTest.cs new file mode 100644 index 0000000..658c65e --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/DeadActiviyControlerTest.cs @@ -0,0 +1,64 @@ +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Microsoft.AspNetCore.Mvc; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApi.Controllers; +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; + +namespace Application.UnitTests.Controllers +{ + public class DeadActiviyControlerTest + { + private DeadActivityController _controller; + private Mock _mockDeadActivityService; + + [SetUp] + public void SetUp() + { + _mockDeadActivityService = new Mock(); + _controller = new DeadActivityController(_mockDeadActivityService.Object); + } + + [Test] + public async Task Get_Single_Ok() + { + + _mockDeadActivityService.Setup(x => x.GetDeadActivity(5)) + .ReturnsAsync(new DeadActivity(new Activity() + { + Id = 5, + Detail = new ActivityDetail() + }, DeadActivityReason.Other, "Test")); + + var result = await _controller.Get(5); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + + + var value = okResult.Value as DeadActivity; + Assert.NotNull(value); + Assert.AreEqual("Test", value.ReasonNote); + } + + [Test] + public async Task Get_Single_NotFound() + { + _mockDeadActivityService.Setup(x => x.GetDeadActivity(It.IsAny())) + .ThrowsAsync(new NotFoundException("Not Found")); + + var result = await _controller.Get(1); + Assert.NotNull(result); + + var badRequestResult = result as BadRequestObjectResult; + Assert.NotNull(badRequestResult); + Assert.AreEqual(400, badRequestResult.StatusCode); + Assert.AreEqual("DeadActivity Not Found.", badRequestResult.Value); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/DepartmentControllerTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/DepartmentControllerTest.cs new file mode 100644 index 0000000..0610bff --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/DepartmentControllerTest.cs @@ -0,0 +1,254 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using Microsoft.AspNetCore.Mvc; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApi.Controllers; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.API.Internal.Operations.IncentiveApi.Models; + +namespace Application.UnitTests.Controllers +{ + public class DepartmentControllerTest + { + private DepartmentController _controller; + private IDepartmentService _mockDepartmentService; + private IUserService _userService; + private ICurrentUserService _currentUser; + + [SetUp] + public void Setup() + { + _mockDepartmentService = Mock.Of(); + _userService = Mock.Of(); + _currentUser = Mock.Of(); + _controller = new DepartmentController(_mockDepartmentService, _userService, _currentUser); + } + + [Test] + public async Task GetAll_OK() + { + Mock.Get(_mockDepartmentService).Setup(service => service.GetAllDepartmentsAsync()).ReturnsAsync( + new List() + { + new Department() + { + Id = 0 + } + }); + + var result = await _controller.GetDepartments(null,null); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task GetAll_Exception() + { + Mock.Get(_mockDepartmentService).Setup(service => service.GetAllDepartmentsAsync()) + .ThrowsAsync(new Exception("Oops")); + + var result = await _controller.GetDepartments(null, null); + Assert.NotNull(result); + + var statusRes = result as StatusCodeResult; + Assert.NotNull(statusRes); + Assert.AreEqual(500, statusRes.StatusCode); + } + + [Test] + public async Task GetById_OK() + { + Mock.Get(_mockDepartmentService).Setup(service => service.GetDepartmentByIdAsync(It.IsAny())) + .ReturnsAsync(new Department() { Id = 5 }); + + var result = await _controller.GetDepartmentById(0); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task GetById_NotFound() + { + Mock.Get(_mockDepartmentService).Setup(service => service.GetDepartmentByIdAsync(It.IsAny())) + .ThrowsAsync(new NotFoundException("Oops")); + + var result = await _controller.GetDepartmentById(5); + ; + Assert.NotNull(result); + + var statusRes = result as NotFoundObjectResult; + Assert.NotNull(statusRes); + Assert.AreEqual(404, statusRes.StatusCode); + } + + [Test] + public async Task GetById_Exception() + { + Mock.Get(_mockDepartmentService).Setup(service => service.GetDepartmentByIdAsync(It.IsAny())) + .ThrowsAsync(new Exception("Oops")); + + var result = await _controller.GetDepartmentById(5); + Assert.NotNull(result); + + var statusRes = result as StatusCodeResult; + Assert.NotNull(statusRes); + Assert.AreEqual(500, statusRes.StatusCode); + } + + [Test] + public async Task CreateDepartment_OK() + { + Mock.Get(_mockDepartmentService).Setup(service => service.SaveDepartmentAsync(It.IsAny())) + .ReturnsAsync(new Department() {Id = 5}); + + var result = await _controller.CreateDepartment(new Department() {DepartmentName = "Test"}); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task CreateDepartment_Exception() + { + Mock.Get(_mockDepartmentService).Setup(service => service.SaveDepartmentAsync(It.IsAny())) + .ThrowsAsync(new Exception("Oops")); + + var result = await _controller.CreateDepartment(new Department() {DepartmentName = "Test"}); + Assert.NotNull(result); + + var statusRes = result as StatusCodeResult; + Assert.NotNull(statusRes); + Assert.AreEqual(500, statusRes.StatusCode); + } + + [Test] + public async Task CreateDepartment_DuplicateName() + { + Mock.Get(_mockDepartmentService).Setup(service => service.SaveDepartmentAsync(It.IsAny())) + .ThrowsAsync(new DuplicateDataException("Oops")); + + var result = await _controller.CreateDepartment(new Department() {DepartmentName = "Test"}); + Assert.NotNull(result); + + var statusRes = result as BadRequestObjectResult; + Assert.NotNull(statusRes); + Assert.AreEqual(400, statusRes.StatusCode); + } + + [Test] + public async Task UpdateDepartment_OK() + { + Mock.Get(_mockDepartmentService).Setup(service => service.UpdateDepartmentAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new Department() {Id = 5}); + + var result = await _controller.UpdateDepartment(new Department() {DepartmentName = "Test"}, 3); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task UpdateDepartment_Exception() + { + Mock.Get(_mockDepartmentService).Setup(service => service.UpdateDepartmentAsync(It.IsAny(), It.IsAny())) + .ThrowsAsync(new Exception("Oops")); + + var result = await _controller.UpdateDepartment(new Department() {DepartmentName = "Test"}, 3); + Assert.NotNull(result); + + var statusRes = result as StatusCodeResult; + Assert.NotNull(statusRes); + Assert.AreEqual(500, statusRes.StatusCode); + } + + [Test] + public async Task UpdateDepartment_NotFound() + { + Mock.Get(_mockDepartmentService).Setup(service => service.UpdateDepartmentAsync(It.IsAny(), It.IsAny())) + .ThrowsAsync(new NotFoundException("Oops")); + + var result = await _controller.UpdateDepartment(new Department() {DepartmentName = "Test"}, 3); + Assert.NotNull(result); + + var statusRes = result as NotFoundObjectResult; + Assert.NotNull(statusRes); + Assert.AreEqual(404, statusRes.StatusCode); + } + + [Test] + public async Task UpdateDepartment_DuplicateName() + { + Mock.Get(_mockDepartmentService).Setup(service => service.UpdateDepartmentAsync(It.IsAny(), It.IsAny())) + .ThrowsAsync(new DuplicateDataException("Oops")); + + var result = await _controller.UpdateDepartment(new Department() {DepartmentName = "Test"}, 3); + Assert.NotNull(result); + + var statusRes = result as BadRequestObjectResult; + Assert.NotNull(statusRes); + Assert.AreEqual(400, statusRes.StatusCode); + } + + [Test] + public async Task GetDepartmentDetails_OK() + { + Mock.Get(_mockDepartmentService).Setup(service => service.GetDepartmentDetailsAsync(It.IsAny())) + .ReturnsAsync(new DepartmentDetails { + Department = new Department { Id = 3 }, + Assignments = new List { new UserDepartment(), new UserDepartment() }, + RegionalManager = new AppUser { EmployeeId = "C0000" } + }); + + var result = await _controller.GetDepartmentDetails(3); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task GetDepartmentDetails_Exception() + { + Mock.Get(_mockDepartmentService).Setup(service => service.GetDepartmentDetailsAsync( It.IsAny())) + .ThrowsAsync(new Exception("Oops")); + + var result = await _controller.GetDepartmentDetails(3); + Assert.NotNull(result); + + var statusRes = result as StatusCodeResult; + Assert.NotNull(statusRes); + Assert.AreEqual(500, statusRes.StatusCode); + } + + [Test] + public async Task GetDepartmentDetails_NotFound() + { + Mock.Get(_mockDepartmentService).Setup(service => service.GetDepartmentDetailsAsync(It.IsAny())) + .ThrowsAsync(new NotFoundException("Oops")); + + var result = await _controller.GetDepartmentDetails(3); + Assert.NotNull(result); + + var statusRes = result as NotFoundObjectResult; + Assert.NotNull(statusRes); + Assert.AreEqual(404, statusRes.StatusCode); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/EmployeeControllerTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/EmployeeControllerTest.cs new file mode 100644 index 0000000..4536dac --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/EmployeeControllerTest.cs @@ -0,0 +1,703 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Microsoft.AspNetCore.Mvc; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApi.Controllers; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApi.Services.Dayforce; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace Application.UnitTests.Controllers +{ + public class EmployeeControllerTest + { + private EmployeeController _controller; + private Mock _mockUserService; + private Mock _mockDayForceService; + private Mock _mockCurrentUserService; + + [SetUp] + public void Setup() + { + _mockCurrentUserService = new Mock(); + _mockDayForceService = new Mock(); + _mockUserService = new Mock(); + _controller = new EmployeeController( + _mockCurrentUserService.Object, + _mockDayForceService.Object, + _mockUserService.Object + ); + } + + [Test] + public async Task Get_OK() + { + _mockUserService + .Setup(service => service.GetAllUsersAsync(null)) + .ReturnsAsync( + new List() + { + new AppUser() + { + AdEmail = "me2@test.com", + PersonId = 123, + EmployeeId = "ABC", + BaseRole = "Developer", + Role = Role.UNKNOWN_ROLE, + HomeDepartment = "Branch-44" + }, + new AppUser() + { + AdEmail = "me3@test.com", + PersonId = 456, + EmployeeId = "DEF", + BaseRole = "Developer", + Role = Role.Admin, + HomeDepartment = "Branch-44" + }, + new AppUser() + { + AdEmail = "me4@test.com", + PersonId = 789, + EmployeeId = "CONTRACTOR1", + BaseRole = "QA", + Role = Role.Teller, + HomeDepartment = "Branch-44" + }, + new AppUser() + { + AdEmail = "me5@test.com", + PersonId = 900, + EmployeeId = "CONTRACTOR2", + BaseRole = "QA", + Role = Role.BM, + HomeDepartment = "Branch-0" + } + }); + + var result = await _controller.GetAllUsers(null, null); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task Get_Resolve_ok() + { + _mockUserService + .Setup(service => service.FindUserAsync("123", null)) + .ReturnsAsync( + new AppUser() + { + AdEmail = "me2@test.com", + PersonId = 123, + EmployeeId = "ABC", + BaseRole = "Developer", + Role = Role.UNKNOWN_ROLE, + HomeDepartment = "Branch-44" + } + ); + + var result = await _controller.ResolveUser("123"); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task Get_Resolve_BadRequest() + { + _mockUserService + .Setup(service => service.FindUserAsync("11771", null)) + .ReturnsAsync( + new AppUser() + { + AdEmail = "me2@test.com", + PersonId = 123, + EmployeeId = "ABC", + BaseRole = "Developer", + Role = Role.UNKNOWN_ROLE, + HomeDepartment = "Branch-44" + } + ); + + var result = await _controller.ResolveUser("11771", "tester@test.com"); + Assert.NotNull(result); + + var statusRes = result as BadRequestObjectResult; + Assert.NotNull(statusRes); + Assert.AreEqual(400, statusRes.StatusCode); + } + + [Test] + public async Task Get_Resolve_Throws() + { + _mockUserService + .Setup(service => service.FindUserAsync("123", null)) + .Throws(); + + var result = await _controller.ResolveUser("123"); + Assert.NotNull(result); + + var statusRes = result as StatusCodeResult; + Assert.NotNull(statusRes); + Assert.AreEqual(500, statusRes.StatusCode); + } + + [Test] + public async Task Get_Self_ok() + { + _mockCurrentUserService.Setup(service => service.GetCurrentUser()) + .Returns( + new ApplicationUser() + { + FirstName = "Test", + LastName = "McGee", + Roles = new[] { "QA" }, + Email = "test@test.com", + }); + _mockUserService + .Setup(service => service.GetUserByAdEmailAsync("test@test.com")) + .ReturnsAsync( + new AppUser() + { + AdEmail = "test@test.com", + PersonId = 123, + EmployeeId = "ABC", + BaseRole = "Developer", + Role = Role.UNKNOWN_ROLE, + HomeDepartment = "Branch-44" + } + ); + + var result = await _controller.GetCurrentEmployee(); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task Get_Self_Unknown() + { + _mockCurrentUserService.Setup(service => service.GetCurrentUser()) + .Returns( + new ApplicationUser() + { + FirstName = "Test", + LastName = "McGee", + Roles = new[] { "QA" }, + Email = "test@test.com", + }); + _mockUserService + .Setup(service => service.GetUserByAdEmailAsync("test@test.com")) + .ReturnsAsync( + (AppUser)null + ); + + var result = await _controller.GetCurrentEmployee(); + Assert.NotNull(result); + + var okResult = result as NoContentResult; + Assert.NotNull(okResult); + Assert.AreEqual(204, okResult.StatusCode); + } + + [Test] + public async Task Post_DepartmentAssignment_Ok() + { + var employee = new AppUser() + { + EmployeeId = "11771", + PersonId = 0, + AdEmail = "nobody@nowhere.com" + }; + + var department = new Department() + { + Id = 555, + State = DepartmentState.Active, + DepartmentName = "Test Dept", + BusinessLineCode = BusinessLine.Operations.Code + }; + + var assignment = new DepartmentAssignmentRequest() + { + Note = "Test Assignment", + DepartmentId = new int [555], + StartDate = DateTime.Today + }; + + var userDepartments = new List + { + new UserDepartment() + { + Employee = employee, + Note = "Test Assignment", + Department = department, + StartDate = DateTime.Today, + IsHome = false + } + }; + + _mockCurrentUserService.Setup(service => service.GetCurrentUser()) + .Returns( + new ApplicationUser() + { + FirstName = "Test", + LastName = "McGee", + Roles = new[] { "QA" }, + Email = "test@test.com", + }); + _mockUserService + .Setup(service => service.GetUserByAdEmailAsync("test@test.com")) + .ReturnsAsync( + new AppUser() + { + EmployeeId = "1", + PersonId = 0, + AdEmail = "one@nowhere.com" + } + ); + _mockUserService + .Setup(service => service.AssignDepartments(employee.EmployeeId, It.IsAny())) + .ReturnsAsync( + userDepartments + ); + + var result = await _controller.AssignDepartment(employee.EmployeeId, assignment); + Assert.NotNull(result); + + var okResult = result as OkResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task Post_DepartmentAssignment_BadRequest() + { + var department = new Department() + { + Id = 555, + State = DepartmentState.Active, + DepartmentName = "Test Dept", + BusinessLineCode = BusinessLine.Operations.Code + }; + + var assignment = new DepartmentAssignmentRequest() + { + Note = "Test Assignment", + DepartmentId = new int [555], + StartDate = DateTime.Today + }; + + var employee = new AppUser() + { + EmployeeId = "11771", + PersonId = 0, + AdEmail = "nobody@nowhere.com" + }; + + _mockCurrentUserService.Setup(service => service.GetCurrentUser()) + .Returns( + new ApplicationUser() + { + FirstName = "Test", + LastName = "McGee", + Roles = new[] { "QA" }, + Email = "test@test.com", + }); + _mockUserService + .Setup(service => service.GetUserByAdEmailAsync("test@test.com")) + .ReturnsAsync( + new AppUser() + { + EmployeeId = "1", + PersonId = 0, + AdEmail = "one@nowhere.com" + } + ); + _mockUserService + .Setup(service => service.AssignDepartments(employee.EmployeeId, It.IsAny())) + .ThrowsAsync(new BadDepartmentAssignmentException("test")); + + var result = await _controller.AssignDepartment(employee.EmployeeId, assignment); + Assert.NotNull(result); + + var okResult = result as BadRequestObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(400, okResult.StatusCode); + } + + [Test] + public async Task Post_DepartmentAssignment_ADUserNotFound() + { + var assignment = new DepartmentAssignmentRequest() + { + Note = "Test Assignment", + DepartmentId = new int [555], + StartDate = DateTime.Today + }; + _mockCurrentUserService + .Setup(service => service.GetCurrentUser()) + .Returns(null); + + var result = await _controller.AssignDepartment("C1", assignment); + + Assert.NotNull(result); + + var unauthorizedResult = result as UnauthorizedObjectResult; + Assert.NotNull(unauthorizedResult); + Assert.AreEqual(401, unauthorizedResult.StatusCode); + } + + [Test] + public async Task Post_DepartmentAssignment_ForbiddenSelfAssign() + { + var assignment = new DepartmentAssignmentRequest() + { + Note = "Test Assignment", + DepartmentId = new int [555], + StartDate = DateTime.Today + }; + _mockCurrentUserService.Setup(service => service.GetCurrentUser()) + .Returns( + new ApplicationUser() + { + FirstName = "Test", + LastName = "McGee", + Roles = new[] { "QA" }, + Email = "test@test.com", + }); + + _mockUserService + .Setup(service => service.GetUserByAdEmailAsync("test@test.com")) + .ReturnsAsync( + new AppUser() + { + EmployeeId = "999", + PersonId = 0, + AdEmail = "test@test.com" + } + ); + + var result = await _controller.AssignDepartment("999", assignment); + + Assert.NotNull(result); + + var forbiddennResult = result as ObjectResult; + Assert.NotNull(forbiddennResult); + Assert.AreEqual(403, forbiddennResult.StatusCode); + Assert.AreEqual("Not allowed to assign yourself to a department.", forbiddennResult.Value); + } + + [Test] + public async Task Post_DepartmentAssignmentUpdate_Ok() + { + var employee = new AppUser() + { + EmployeeId = "11771", + PersonId = 0, + AdEmail = "nobody@nowhere.com" + }; + var assignment = new UserDepartment() + { + Note = "Test Assignment", + DepartmentId = 555, + Employee = employee, + IsHome = false, + StartDate = DateTime.Today + }; + var request = new DepartmentUpdateRequest() + { + EmployeeId = "11771", + AssignmentId = 5, + SlidingPayout = 100, + Role = "Teller" + }; + + _mockCurrentUserService.Setup(service => service.GetCurrentUser()) + .Returns( + new ApplicationUser() + { + FirstName = "Test", + LastName = "McGee", + Roles = new[] { "QA" }, + Email = "test@test.com", + }); + _mockUserService + .Setup(service => service.GetUserByAdEmailAsync("test@test.com")) + .ReturnsAsync( + new AppUser() + { + EmployeeId = "1", + PersonId = 0, + AdEmail = "one@nowhere.com" + } + ); + _mockUserService + .Setup(service => + service.UpdateDepartmentAssignment(request)) + .ReturnsAsync( + assignment + ); + + var result = + await _controller.UpdateDepartmentAssignment(assignment.Employee.EmployeeId, assignment.DepartmentId, request); + Assert.NotNull(result); + + var okResult = result as OkResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task Post_DepartmentAssignmentTerm_BadRequest() + { + var employee = new AppUser() + { + EmployeeId = "11771", + PersonId = 0, + AdEmail = "nobody@nowhere.com" + }; + var assignment = new UserDepartment() + { + Note = "Test Assignment", + DepartmentId = 555, + Employee = employee, + IsHome = false, + StartDate = DateTime.Today + }; + var request = new DepartmentUpdateRequest() + { + EmployeeId = "11771", + AssignmentId = 5, + SlidingPayout = 999, + Role = "Teller" + }; + + _mockCurrentUserService.Setup(service => service.GetCurrentUser()) + .Returns( + new ApplicationUser() + { + FirstName = "Test", + LastName = "McGee", + Roles = new[] { "QA" }, + Email = "test@test.com", + }); + _mockUserService + .Setup(service => service.GetUserByAdEmailAsync("test@test.com")) + .ReturnsAsync( + new AppUser() + { + EmployeeId = "1", + PersonId = 0, + AdEmail = "one@nowhere.com" + } + ); + _mockUserService + .Setup(service => + service.UpdateDepartmentAssignment(request)) + .ThrowsAsync(new BadDepartmentAssignmentException("test")); + + var result = + await _controller.UpdateDepartmentAssignment(assignment.Employee.EmployeeId, assignment.DepartmentId, request); + Assert.NotNull(result); + + var okResult = result as BadRequestObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(400, okResult.StatusCode); + } + + [Test] + public async Task Post_DepartmentAssignmentTerm_ADUserNotFound() + { + var assignment = new DepartmentAssignmentRequest() + { + Note = "Test Assignment", + DepartmentId = new int [555], + StartDate = DateTime.Today + }; + _mockCurrentUserService + .Setup(service => service.GetCurrentUser()) + .Returns(null); + + var result = await _controller.UpdateDepartmentAssignment("C1", 1, null); + + Assert.NotNull(result); + + var unauthorizedResult = result as UnauthorizedObjectResult; + Assert.NotNull(unauthorizedResult); + Assert.AreEqual(401, unauthorizedResult.StatusCode); + } + + [Test] + public async Task Post_DepartmentAssignmentTerm_ForbiddenSelfTerm() + { + var assignment = new DepartmentAssignmentRequest() + { + Note = "Test Assignment", + DepartmentId = new int [555], + StartDate = DateTime.Today + }; + var request = new DepartmentUpdateRequest() + { + EmployeeId = "11771", + AssignmentId = 5, + SlidingPayout = 999, + Role = "Teller" + }; + _mockCurrentUserService.Setup(service => service.GetCurrentUser()) + .Returns( + new ApplicationUser() + { + FirstName = "Test", + LastName = "McGee", + Roles = new[] { "QA" }, + Email = "test@test.com", + }); + + _mockUserService + .Setup(service => service.GetUserByAdEmailAsync("test@test.com")) + .ReturnsAsync( + new AppUser() + { + EmployeeId = "999", + PersonId = 0, + AdEmail = "test@test.com" + } + ); + + var result = await _controller.UpdateDepartmentAssignment("999", 1, request); + + Assert.NotNull(result); + + var forbiddennResult = result as ObjectResult; + Assert.NotNull(forbiddennResult); + Assert.AreEqual(403, forbiddennResult.StatusCode); + Assert.AreEqual("Not allowed to terminate yourself from a department.", forbiddennResult.Value); + } + + [Test] + public async Task Get_Employee_By_Id_ok() + { + _mockCurrentUserService.Setup(service => service.GetCurrentUser()) + .Returns( + new ApplicationUser() + { + FirstName = "Test", + LastName = "McGee", + Roles = new[] { "QA" }, + Email = "test@test.com", + }); + _mockUserService + .Setup(service => service.GetUserByEmployeeIdAsync("ABC")) + .ReturnsAsync( + new AppUser() + { + AdEmail = "test@test.com", + PersonId = 123, + EmployeeId = "ABC", + BaseRole = "Developer", + Role = Role.UNKNOWN_ROLE, + HomeDepartment = "Branch-44" + } + ); + + var result = await _controller.GetEmployeeById("ABC"); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task Get_Employee_By_Id_Unknown() + { + _mockCurrentUserService.Setup(service => service.GetCurrentUser()) + .Returns( + new ApplicationUser() + { + FirstName = "Test", + LastName = "McGee", + Roles = new[] { "QA" }, + Email = "test@test.com", + }); + _mockUserService + .Setup(service => service.GetUserByEmployeeIdAsync("DEF")) + .ReturnsAsync( + (AppUser)null + ); + _mockUserService + .Setup(service => service.GetUserByEmployeeIdAsync("ABC")) + .ReturnsAsync( + new AppUser() + { + AdEmail = "test@test.com", + PersonId = 123, + EmployeeId = "ABC", + BaseRole = "Developer", + Role = Role.UNKNOWN_ROLE, + HomeDepartment = "Branch-44" + } + ); + + var result = await _controller.GetEmployeeById("DEF"); + Assert.NotNull(result); + + var okResult = result as NoContentResult; + Assert.NotNull(okResult); + Assert.AreEqual(204, okResult.StatusCode); + } + + [Test] + public async Task Get_Employee_By_Id_Throw() + { + _mockCurrentUserService.Setup(service => service.GetCurrentUser()) + .Returns( + new ApplicationUser() + { + FirstName = "Test", + LastName = "McGee", + Roles = new[] { "QA" }, + Email = "test@test.com" + }); + _mockUserService + .Setup(service => service.GetUserByEmployeeIdAsync("DEF")) + .Throws(); + + var result = await _controller.GetEmployeeById("DEF"); + Assert.NotNull(result); + + var badResult = result as StatusCodeResult; + Assert.NotNull(badResult); + Assert.AreEqual(500, badResult.StatusCode); + } + + [Test] + public async Task SyncDayForce_ForceRefresh() + { + _mockDayForceService.Setup(service => service.SyncDayForceUsers(new DayforceProcessRequest())); + + var result = await _controller.SyncDayForce(); + Assert.NotNull(result); + + var successResult = result as StatusCodeResult; + Assert.NotNull(successResult); + Assert.AreEqual(200, successResult.StatusCode); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/GoalControllerTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/GoalControllerTest.cs new file mode 100644 index 0000000..031cf26 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/GoalControllerTest.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using Microsoft.AspNetCore.Mvc; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApi.Controllers; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace Application.UnitTests.Controllers +{ + public class GoalControllerTest + { + private GoalController _controller; + private Mock _mockGoalService; + private Mock _mockUserService; + private Mock _mockCurrentUserService; + private Mock _goalDispersalService; + + [SetUp] + public void Setup() + { + _mockGoalService = new Mock(); + _mockUserService = new Mock(); + _goalDispersalService = new Mock(); + _mockCurrentUserService = new Mock(); + _controller = new GoalController(_mockGoalService.Object, _goalDispersalService.Object ,_mockCurrentUserService.Object, + _mockUserService.Object); + } + + [Test] + public async Task Get_OK() + { + _mockGoalService + .Setup(service => service.GetGoalsAsync(It.IsAny())).ReturnsAsync( + new List() + { + new Goal() + { + Id = 1 + } + }); + + var result = await _controller.GetGoals(12,2021,12,2021, 1); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task Get_DepartmentGoals_OK() + { + var goals = new DepartmentGoals(55, DateTime.Now.Year); + _mockGoalService + .Setup(service => service.GetAnnualDepartmentGoalsAsync(goals.DepartmentId, goals.Year)).ReturnsAsync( + goals); + + var result = await _controller.GetDepartmentGoals(55, DateTime.Now.Year); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task Get_PersonnelGoals_OK() + { + var users = new List() + { + new AppUser() + { + Role = Role.Admin, + AdEmail = "tester@test.com", + BaseRole = "Tester", + EmployeeId = "11771", + FullName = "Test McTester", + HomeDepartment = "HOME", + PersonId = 565 + } + }; + + var goals = new PersonnelGoals(55, DateTime.Now.Month, DateTime.Now.Year, users); + var searchRequest = new DepartmentPersonnelSearchRequest + { + DepartmentId = goals.DepartmentId, + Year = goals.Month, + Month = goals.Year + }; + _mockGoalService + .Setup(service => service.GetMonthlyPersonnelGoalsAsync(searchRequest)) + .ReturnsAsync( + goals); + + var result = await _controller.GetPersonnelGoals(55, DateTime.Now.Month, DateTime.Now.Year); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task Get_CurrentUserGoals_OK() + { + var user1 = new ApplicationUser() + { + Email = "tester@test.com", + FirstName = "Test", + LastName = "McTester" + }; + var user = new AppUser() + { + Role = Role.Admin, + AdEmail = "tester@test.com", + BaseRole = "Tester", + EmployeeId = "11771", + FullName = "Test McTester", + HomeDepartment = "HOME", + PersonId = 565 + }; + var range = new MonthRange(2021,1,2021,2); + var goals = new EmployeeGoals(user, range); + + _mockCurrentUserService.Setup(service => service.GetCurrentUser()).Returns(user1); + _mockUserService.Setup(service => service.GetUserByAdEmailAsync(user1.Email)).ReturnsAsync(user); + _mockGoalService + .Setup(service => service.GetEmployeeGoalsForMonthRangeAsync(goals.Employee, range)) + .ReturnsAsync(goals); + + var result = await _controller.GetCurrentUserGoals(range.FromMonth, range.FromYear, range.ToMonth, range.ToYear); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task Get_CurrentUserDepartmentsGoals_OK() + { + var user1 = new ApplicationUser() + { + Email = "tester@test.com", + FirstName = "Test", + LastName = "McTester" + }; + var user = new AppUser() + { + Role = Role.Admin, + AdEmail = "tester@test.com", + BaseRole = "Tester", + EmployeeId = "11771", + FullName = "Test McTester", + HomeDepartment = "HOME", + PersonId = 565 + }; + var range = new MonthRange(2021,1,2021,2); + var goals = new List { new DepartmentGoalsForRange(1, range) }; + + _mockCurrentUserService.Setup(service => service.GetCurrentUser()).Returns(user1); + _mockUserService.Setup(service => service.GetUserByAdEmailAsync(user1.Email)).ReturnsAsync(user); + _mockGoalService + .Setup(service => service.GetEmployeeDepartmentsGoalsForMonthRangeAsync(user, range)) + .ReturnsAsync(goals); + + var result = await _controller.GetCurrentUserDepartmentsGoals(range.FromMonth, range.FromYear, range.ToMonth, range.ToYear); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/PayoutControllerTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/PayoutControllerTest.cs new file mode 100644 index 0000000..8d1462d --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/PayoutControllerTest.cs @@ -0,0 +1,106 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.API.Internal.Operations.IncentiveApi.Services.Payout; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using Microsoft.AspNetCore.Mvc; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApi.Controllers; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApi.Models.Payout; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace Application.UnitTests.Controllers +{ + public class PayoutControllerTest + { + private PayoutController _controller; + private Mock _mockPayoutService; + private Mock _mockUserService; + private Mock _mockActivityService; + private Mock _mockCurrentUserService; + + [SetUp] + public void Setup() + { + _mockPayoutService = new Mock(); + _mockUserService = new Mock(); + _mockCurrentUserService = new Mock(); + _mockActivityService = new Mock(); + _controller = new PayoutController(_mockPayoutService.Object, _mockUserService.Object, + _mockCurrentUserService.Object, _mockActivityService.Object); + } + + [Test] + public async Task Get_Employee_OK() + { + var user1 = new ApplicationUser() + { + Email = "tester@test.com", + FirstName = "Test", + LastName = "McTester" + }; + var user = new AppUser() + { + Role = Role.Admin, + AdEmail = "tester@test.com", + BaseRole = "Tester", + EmployeeId = "5", + FullName = "Test McTester", + HomeDepartment = "HOME", + PersonId = 565 + }; + + _mockCurrentUserService.Setup(service => service.GetCurrentUser()).Returns(user1); + _mockUserService.Setup(service => service.GetUserByEmployeeIdAsync(user.EmployeeId)).ReturnsAsync(user); + _mockPayoutService + .Setup(service => service.BuildUserDepartmentsPayoutView(It.IsAny(), It.IsAny())) + .ReturnsAsync( + new List()); + + var result = await _controller.GetEmployeePayout("5", 2021, 12); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task Get_Me_OK() + { + var user1 = new ApplicationUser() + { + Email = "tester@test.com", + FirstName = "Test", + LastName = "McTester" + }; + var user = new AppUser() + { + Role = Role.Admin, + AdEmail = "tester@test.com", + BaseRole = "Tester", + EmployeeId = "5", + FullName = "Test McTester", + HomeDepartment = "HOME", + PersonId = 565 + }; + + _mockCurrentUserService.Setup(service => service.GetCurrentUser()).Returns(user1); + _mockUserService.Setup(service => service.GetUserByEmployeeIdAsync(user.EmployeeId)).ReturnsAsync(user); + _mockPayoutService + .Setup(service => service.BuildUserDepartmentsPayoutView(It.IsAny(), It.IsAny())) + .ReturnsAsync( + new List()); + + var result = await _controller.GetEmployeePayout(user.EmployeeId, 2021, 12); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/SettingsControllerTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/SettingsControllerTest.cs new file mode 100644 index 0000000..1ad4117 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/SettingsControllerTest.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using Microsoft.AspNetCore.Mvc; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApi.Controllers; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; + +namespace Application.UnitTests.Controllers +{ + public class SettingsControllerTest + { + private SettingsController _controller; + private Mock _mockCurrentUserService; + private Mock _mockDepartmentService; + private Mock _mockSettingsService; + private Mock _mockUserService; + + [SetUp] + public void Setup() + { + _mockCurrentUserService = new Mock(); + _mockDepartmentService = new Mock(); + _mockSettingsService = new Mock(); + _mockUserService = new Mock(); + _controller = new SettingsController( + _mockCurrentUserService.Object, + new DateTimeService(), + _mockDepartmentService.Object, + _mockSettingsService.Object, + _mockUserService.Object + ); + } + + [Test] + public async Task Get_OK() + { + _mockDepartmentService.Setup(service => service.GetAllDepartmentsAsync()).ReturnsAsync( + new List() + { + new Department() + { + Id = 0 + } + }); + + _mockUserService.Setup(service => service.GetAllUserSummariesAsync()).ReturnsAsync( + new List() + { + new UserSummary() + { + } + }); + + var result = await _controller.Get(); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/UserAssignmentControllerTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/UserAssignmentControllerTest.cs new file mode 100644 index 0000000..bfdcc5c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Controllers/UserAssignmentControllerTest.cs @@ -0,0 +1,111 @@ +using System.Collections.Generic; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Microsoft.AspNetCore.Mvc; +using Moq; +using NUnit.Framework; +using System; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApi.Controllers; +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; + +namespace Application.UnitTests.Controllers +{ + public class UserAssignmentControllerTest + { + private UserAssignmentController _controller; + private Mock _mockUserAssignmentService; + + [SetUp] + public void Setup() + { + _mockUserAssignmentService = new Mock(); + _controller = new UserAssignmentController(_mockUserAssignmentService.Object); + } + + [Test] + public async Task GetAllUserAssignments_OK() + { + _mockUserAssignmentService.Setup(service => service.GetAllUserAssignmentsAsync()) + .ReturnsAsync(new List()); + + var result = await _controller.GetAllUserAssignments(); + Assert.NotNull(result); + + var okResult = result as OkObjectResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task GetAllUserAssignments_Exception() + { + { + _mockUserAssignmentService.Setup(service => service.GetAllUserAssignmentsAsync()) + .ThrowsAsync(new Exception("Exception Message")); + + var result = await _controller.GetAllUserAssignments(); + Assert.NotNull(result); + + var statusRes = result as StatusCodeResult; + Assert.NotNull(statusRes); + Assert.AreEqual(500, statusRes.StatusCode); + } + } + + [Test] + public async Task SetRegionalManager_OK() + { + + var assignment = new UserAssignmentRequest + { + EmployeeId = "123", + Key = "NORTH", + Type = AssignmentType.RegionalManager + }; + + _mockUserAssignmentService.Setup(service => service.SetRegionalManager(It.IsAny())) + .ReturnsAsync(new UserAssignment()); + + var result = await _controller.SetRegionalManager(assignment); + Assert.NotNull(result); + + var okResult = result as OkResult; + Assert.NotNull(okResult); + Assert.AreEqual(200, okResult.StatusCode); + } + + [Test] + public async Task SetRegionalManager_BadUserAssignmentException() + { + var assignment = new UserAssignmentRequest(); + _mockUserAssignmentService.Setup(service => service.SetRegionalManager(assignment)) + .ThrowsAsync(new BadUserAssignmentException("Exception Message")); + + var result = await _controller.SetRegionalManager(assignment); + Assert.NotNull(result); + + var badRequestRes = result as BadRequestObjectResult; + Assert.NotNull(badRequestRes); + Assert.AreEqual(400, badRequestRes.StatusCode); + Assert.AreEqual("Exception Message", badRequestRes.Value); + } + + [Test] + public async Task SetRegionalManager_Exception() + { + var assignment = new UserAssignmentRequest(); + _mockUserAssignmentService.Setup(service => service.SetRegionalManager(assignment)) + .ThrowsAsync(new Exception("Exception Message")); + + var result = await _controller.SetRegionalManager(assignment); + Assert.NotNull(result); + + var statusRes = result as StatusCodeResult; + Assert.NotNull(statusRes); + Assert.AreEqual(500, statusRes.StatusCode); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/ActivityTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/ActivityTest.cs new file mode 100644 index 0000000..8fb3cd5 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/ActivityTest.cs @@ -0,0 +1,97 @@ +using LCU.API.Internal.Operations.IncentiveApiLib; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using NUnit.Framework; +using System; + +namespace Application.UnitTests.Entities +{ + public class ActivityTest + { + [Test] + public void CopyActivityForTransfer_Basic() + { + var activityToCopy = new Activity + { + Accepted = new DateTime(2021, 9, 22), + DepartmentId = 2, + Detail = new ActivityDetail + { + AccountBalance = 100.00, + AccountNewBalance = 99.00, + AccountNumber = "888888", + AccountStatus = "Open", + AgreementNumber = "999", + BranchOrgName = "Org Name", + BranchOrgNbr = "Org Number", + DnaPersonNbr = "DNA number", + IsDebtProtectionEligible = true, + MemberName = "Member Name", + StationName = "Station Name", + SubjectOrgNbr = "Subject Org Number", + SubjectPersonNbr = "Subject Person Number", + IsGapInsuranceEligible = true, + }, + EmployeeEmail = "employee@landmarkcu.com", + EmployeeId = "E0000", + MatchingActivityId = 0, + Product = Product.CheckCard, + SourceCreated = new DateTime(2021, 9, 22), + SourceId = "Source ID", + SourceSystem = SystemType.Dna, + State = ActivityState.PendingTransfer, + Type = ActivityType.Sale + }; + var activityTransferAction = new ActivityTransferAction + { + NewActivityDepartmentId = 88, + NewActivityEmployeeId = "C0001", + ActivityStateBeforeTransfer = ActivityState.Accepted + }; + var employeeEmailAddress = "newEmployee@landmarkcu.com"; + + var result = Activity.CopyActivityForTransfer(activityToCopy, activityTransferAction, employeeEmailAddress); + + Assert.AreEqual(true, result.Detail.IsDebtProtectionEligible); + Assert.AreEqual(0, result.Id); + Assert.AreEqual("888888", result.Detail.AccountNumber); + Assert.AreEqual(new DateTime(2021, 9, 22), result.Accepted); + Assert.AreEqual(new DateTime(2021, 9, 22), result.SourceCreated); + Assert.AreEqual(ActivityType.Sale, result.Type); + Assert.AreEqual(activityTransferAction.NewActivityDepartmentId, result.DepartmentId); + Assert.AreEqual(activityTransferAction.NewActivityEmployeeId, result.EmployeeId); + Assert.AreEqual(activityTransferAction.ActivityStateBeforeTransfer, result.State); + Assert.AreEqual(employeeEmailAddress, result.EmployeeEmail); + Assert.AreEqual(new DateTime(), result.LastUpdated); + Assert.AreEqual(null, result.LastUpdatedBy); + Assert.AreEqual(UserType.Associate, result.LastUpdatedByType); + Assert.AreEqual(new DateTime(), result.Created); + Assert.AreEqual(null, result.CreatedBy); + Assert.AreEqual(UserType.Associate, result.CreatedByType); + Assert.AreEqual(true, result.Detail.IsGapInsuranceEligible); + } + + [Test] + public void ActivityTransformForView() + { + var activity = new Activity + { + Product = Product.CreditCard, + Type = ActivityType.Referral, + Detail = new ActivityDetail + { + Activity = new Activity + { + Product = Product.CreditCard, + Type = ActivityType.Referral, + }, + AccountNumber = "1234567890" + } + }; + + var result = activity.TransformForView(); + + Assert.AreEqual("XXXXXX7890", result.Detail.AccountNumber); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/ActivityTransferActionTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/ActivityTransferActionTest.cs new file mode 100644 index 0000000..62ceb42 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/ActivityTransferActionTest.cs @@ -0,0 +1,21 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using NUnit.Framework; + +namespace Application.UnitTests.Entities +{ + public class ActivityTransferActionTest + { + [Test] + public void IsEligibleForTransfer() + { + Assert.True(ActivityTransferAction.IsEligibleForTransfer(ActivityState.Accepted)); + Assert.True(ActivityTransferAction.IsEligibleForTransfer(ActivityState.PendingReferral)); + Assert.True(ActivityTransferAction.IsEligibleForTransfer(ActivityState.ManualReview)); + Assert.False(ActivityTransferAction.IsEligibleForTransfer(ActivityState.Created)); + Assert.False(ActivityTransferAction.IsEligibleForTransfer(ActivityState.PendingTransfer)); + Assert.False(ActivityTransferAction.IsEligibleForTransfer(ActivityState.Rejected)); + Assert.False(ActivityTransferAction.IsEligibleForTransfer(ActivityState.Transferred)); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/BusinessLineTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/BusinessLineTest.cs new file mode 100644 index 0000000..4681422 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/BusinessLineTest.cs @@ -0,0 +1,18 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using NUnit.Framework; + +namespace Application.UnitTests.Entities +{ + public class BusinessLineTest + { + [Test] + public void DetermineBusinessLine() + { + Assert.AreEqual(BusinessLine.TryLookup("HEADQUARTERS - SECURITY & FRAUD"),BusinessLine.MemberServices); + Assert.AreEqual(BusinessLine.TryLookup("GERMANTOWN - BRANCH"),BusinessLine.BranchOperations); + Assert.AreEqual(BusinessLine.TryLookup("HEADQUARTERS - OPERATIONS"),BusinessLine.Operations); + Assert.AreEqual(BusinessLine.TryLookup(null),BusinessLine.MemberServices); + Assert.AreEqual(BusinessLine.TryLookup(""),BusinessLine.MemberServices); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/CommissionTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/CommissionTest.cs new file mode 100644 index 0000000..9f0630b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/CommissionTest.cs @@ -0,0 +1,41 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using NUnit.Framework; + +namespace Application.UnitTests.Entities +{ + public class CommissionTest + { + [Test] + public void HasSameRoleAndProduct_FullMatch_True() + { + var commission = new Commission { Role = Role.ABM, Product = Product.ATMCard }; + var fullMatch = new Commission { Role = Role.ABM, Product = Product.ATMCard }; + Assert.True(commission.HasSameRoleAndProduct(fullMatch)); + } + + [Test] + public void HasSameRoleAndProduct_NoMatch_False() + { + var commission = new Commission { Role = Role.ABM, Product = Product.ATMCard }; + var noMatch = new Commission { Role = Role.Admin, Product = Product.NewChecking }; + Assert.False(commission.HasSameRoleAndProduct(noMatch)); + } + + [Test] + public void HasSameRoleAndProduct_OnlyRoleMatch_False() + { + var commission = new Commission { Role = Role.ABM, Product = Product.ATMCard }; + var roleMatch = new Commission { Role = Role.ABM, Product = Product.NewChecking }; + Assert.False(commission.HasSameRoleAndProduct(roleMatch)); + } + + [Test] + public void HasSameRoleAndProduct_OnlyProductMatch_False() + { + var commission = new Commission { Role = Role.ABM, Product = Product.ATMCard }; + var productMatch = new Commission { Role = Role.Admin, Product = Product.ATMCard }; + Assert.False(commission.HasSameRoleAndProduct(productMatch)); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/DepartmentLineItemTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/DepartmentLineItemTest.cs new file mode 100644 index 0000000..1f80bc3 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/DepartmentLineItemTest.cs @@ -0,0 +1,38 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using NUnit.Framework; + +namespace Application.UnitTests.Entities +{ + public class DepartmentLineItemTest + { + [Test] + public void Validate_CalculatePercentage_TotalNonZero() + { + var departmentLineItem = new DepartmentLineItem + { + DepartmentName = "Test1", + PayoutTotal = 12 + }; + + departmentLineItem.CalculatePercentage(12); + + Assert.NotNull(departmentLineItem.PayoutPercentage); + Assert.AreEqual((decimal) 100, departmentLineItem.PayoutPercentage); + } + + [Test] + public void Validate_CalculatePercentage_TotalZero() + { + var departmentLineItem = new DepartmentLineItem + { + DepartmentName = "Test1", + PayoutTotal = 12 + }; + + departmentLineItem.CalculatePercentage(0); + + Assert.NotNull(departmentLineItem.PayoutPercentage); + Assert.AreEqual((decimal) 0, departmentLineItem.PayoutPercentage); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/DepartmentTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/DepartmentTest.cs new file mode 100644 index 0000000..dd65a86 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/DepartmentTest.cs @@ -0,0 +1,114 @@ +using System.ComponentModel.DataAnnotations; +using System.Linq; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using NUnit.Framework; + +namespace Application.UnitTests.Entities +{ + public class DepartmentTest + { + [Test] + public void Validate_OK() + { + var dept = new Department() + { + DepartmentName = "Test1", + BusinessLineCode = "MEMSRV", + State = DepartmentState.Active + }; + + var results = dept.Validate(new ValidationContext(dept)); + Assert.IsEmpty(results); + } + + [TestCase("BCHOPS", false)] + [TestCase("MEMSRV", false)] + [TestCase("OPS", false)] + [TestCase("TOAST", true)] + public void Validate_BusinessLineCode_OK(string businessLine, bool hasError) + { + var dept = new Department() + { + DepartmentName = "Test1", + BusinessLineCode = businessLine, + State = DepartmentState.Active + }; + + var results = dept.Validate(new ValidationContext(dept)); + Assert.AreEqual(hasError,results.Any()); + } + + [Test] + public void Validate_Bad_BusinessLine() + { + var dept = new Department() + { + DepartmentName = "Test1", + BusinessLineCode = "THISISNOTAREALBUSINESSLINECODE", + State = DepartmentState.Active + }; + + var results = dept.Validate(new ValidationContext(dept)); + Assert.NotNull(results); + Assert.AreEqual(1, results.Count()); + Assert.AreEqual(results.First().ErrorMessage, "The supplied BusinessLineCode is not associated with any known BusinessLine"); + } + + [Test] + public void Validate_NullRegionCode_OK() + { + var department = new Department() + { + DepartmentName = "Test1", + BusinessLineCode = "MEMSRV", + RegionCode = null, + State = DepartmentState.Active + }; + + var results = department.Validate(new ValidationContext(department)); + + Assert.NotNull(results); + Assert.IsEmpty(results); + } + + [TestCase("INVALID_REGION")] + [TestCase("")] + [TestCase(" ")] + public void Validate_BadRegionCode_IncludesValidationResult(string regionCode) + { + var department = new Department() + { + DepartmentName = "Test1", + BusinessLineCode = "MEMSRV", + RegionCode = regionCode, + State = DepartmentState.Active + }; + + var results = department.Validate(new ValidationContext(department)); + + Assert.NotNull(results); + Assert.AreEqual(1, results.Count()); + Assert.AreEqual(results.First().ErrorMessage, "The supplied RegionCode is not associated with any known Region"); + } + + [TestCase("EAST")] + [TestCase("NORTH")] + [TestCase("SOUTH")] + [TestCase("WEST")] + public void Validate_RegionCode_OK(string regionCode) + { + var dept = new Department() + { + DepartmentName = "Test1", + BusinessLineCode = "MEMSRV", + RegionCode = regionCode, + State = DepartmentState.Active + }; + + var results = dept.Validate(new ValidationContext(dept)); + Assert.NotNull(results); + Assert.IsEmpty(results); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/RegionTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/RegionTest.cs new file mode 100644 index 0000000..1ff3774 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/RegionTest.cs @@ -0,0 +1,38 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using NUnit.Framework; + +namespace Application.UnitTests.Entities +{ + public class RegionTest + { + [Test] + public void Regions_ContainsRegions() + { + var regions = Region.Regions; + + Assert.AreEqual(4, regions.Count); + Assert.Contains(Region.North, regions); + Assert.Contains(Region.South, regions); + Assert.Contains(Region.East, regions); + Assert.Contains(Region.West, regions); + } + + [Test] + public void FindByCode_ValidCodes_ReturnsRegion() + { + Assert.AreSame(Region.North, Region.FindByCode("NORTH")); + Assert.AreSame(Region.South, Region.FindByCode("SOUTH")); + Assert.AreSame(Region.East, Region.FindByCode("EAST")); + Assert.AreSame(Region.West, Region.FindByCode("WEST")); + } + + [TestCase(null)] + [TestCase("")] + [TestCase(" ")] + [TestCase("INVALID")] + public void FindByCode_InvalidCodes_ReturnsNull(string regionCode) + { + Assert.Null(Region.FindByCode(regionCode)); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/RoleTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/RoleTest.cs new file mode 100644 index 0000000..c400fb4 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/RoleTest.cs @@ -0,0 +1,94 @@ +using System.Linq; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using NUnit.Framework; + +namespace Application.UnitTests.Entities +{ + public class RoleTest + { + [Test] + public void GetRolesList_OK() + { + var roles = Role.GetRolesList(); + Assert.Contains(Role.Admin, roles); + Assert.Contains(Role.RegionalManager, roles); + Assert.Contains(Role.BM, roles); + Assert.Contains(Role.ABM, roles); + Assert.Contains(Role.BOM, roles); + Assert.Contains(Role.PFO, roles); + Assert.Contains(Role.Teller, roles); + Assert.Contains(Role.MSCTeamLead, roles); + Assert.Contains(Role.MSLO, roles); + Assert.Contains(Role.MSR, roles); + Assert.Contains(Role.OpsHelpDesk, roles); + Assert.Contains(Role.UniversalSpecialistPFO, roles); + Assert.Contains(Role.UniversalSpecialistTeller, roles); + Assert.Contains(Role.InStoreBranchManager, roles); + Assert.Contains(Role.InStoreAssistantBranchManager, roles); + Assert.Contains(Role.ManagementTrainee, roles); + Assert.Contains(Role.BusinessLineManager, roles); + Assert.Contains(Role.MISSING_ROLE, roles); + Assert.Contains(Role.UNKNOWN_ROLE, roles); + Assert.Contains(Role.Developer, roles); + Assert.Contains(Role.Mortgage, roles); + Assert.Contains(Role.LIC, roles); + Assert.Contains(Role.Manager, roles); + } + + [Test] + public void DetermineRoleOrDefault_Ok() + { + var role = Role.DetermineRoleOrDefault("Admin"); + Assert.AreEqual(Role.Admin, role); + + var roleUnknownEmpty = Role.DetermineRoleOrDefault(""); + Assert.AreEqual(Role.UNKNOWN_ROLE, roleUnknownEmpty); + + var roleUnknown = Role.DetermineRoleOrDefault("NOT_REAL_HOPEFULLY"); + Assert.AreEqual(Role.UNKNOWN_ROLE, roleUnknown); + } + + [Test] + public void SearchRoles_OK() + { + var foundRoles = Role.SearchRoles("Admin"); + Assert.Contains(Role.Admin, foundRoles); + Assert.AreEqual(2, foundRoles.Count()); + + foundRoles = Role.SearchRoles("Branch Manager"); + Assert.Contains(Role.BM, foundRoles); + Assert.Contains(Role.ABM, foundRoles); + Assert.Contains(Role.InStoreBranchManager, foundRoles); + Assert.Contains(Role.InStoreAssistantBranchManager, foundRoles); + Assert.AreEqual(4, foundRoles.Count()); + + foundRoles = Role.SearchRoles("Developer"); + Assert.Contains(Role.Developer, foundRoles); + Assert.AreEqual(1, foundRoles.Count()); + + foundRoles = Role.SearchRoles("Develop"); + Assert.Contains(Role.Developer, foundRoles); + Assert.AreEqual(1, foundRoles.Count()); + + foundRoles = Role.SearchRoles("LIC"); + Assert.Contains(Role.LIC, foundRoles); + Assert.AreEqual(2, foundRoles.Count()); + + foundRoles = Role.SearchRoles("LIC "); + Assert.Contains(Role.LIC, foundRoles); + Assert.AreEqual(2, foundRoles.Count()); + + foundRoles = Role.SearchRoles(" LIC"); + Assert.Contains(Role.LIC, foundRoles); + Assert.AreEqual(2, foundRoles.Count()); + + foundRoles = Role.SearchRoles(" LIC "); + Assert.Contains(Role.LIC, foundRoles); + Assert.AreEqual(2, foundRoles.Count()); + + foundRoles = Role.SearchRoles(" lic "); + Assert.Contains(Role.LIC, foundRoles); + Assert.AreEqual(2, foundRoles.Count()); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/UserDepartmentTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/UserDepartmentTest.cs new file mode 100644 index 0000000..51238f8 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Entities/UserDepartmentTest.cs @@ -0,0 +1,315 @@ +using System; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using NUnit.Framework; + +namespace Application.UnitTests.Entities +{ + public class UserDepartmentTest + { + private readonly AppUser _employee = new AppUser() + { + EmployeeId = "5", + PersonId = 0, + AdEmail = "nobody@nowhere.com" + }; + + [Test] + public void NonConflictingAssignment_Diff_Dept() + { + var existingAssignment = new UserDepartment() + { + DepartmentId = 5, + Employee = _employee, + StartDate = DateTime.Today, + EndDate = null + }; + + var newAssignment = new UserDepartment() + { + // DepartmentID will be NULL. + Department = new Department() + { + Id = 6, + State = DepartmentState.Active, + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code + }, + Employee = _employee, + StartDate = DateTime.Today, + EndDate = null + }; + + Assert.False(existingAssignment.IsConflictingAssignment(newAssignment, out string message)); + Assert.AreEqual(string.Empty, message); + } + + [Test] + public void NonConflictingAssignment_Diff_User() + { + var employee2 = new AppUser() + { + EmployeeId = "6", + PersonId = 0, + AdEmail = "nobody2@nowhere.com" + }; + var existingAssignment = new UserDepartment() + { + DepartmentId = 5, + Employee = _employee, + StartDate = DateTime.Today, + EndDate = null + }; + + var newAssignment = new UserDepartment() + { + // DepartmentID will be NULL. + Department = new Department() + { + Id = 5, + State = DepartmentState.Active, + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code + }, + Employee = employee2, + StartDate = DateTime.Today, + EndDate = null + }; + + Assert.False(existingAssignment.IsConflictingAssignment(newAssignment, out string message)); + Assert.AreEqual(string.Empty, message); + } + + [Test] + public void ConflictingAssignment_Case_Null_Null() + { + var existingAssignment = new UserDepartment() + { + DepartmentId = 5, + Employee = _employee, + StartDate = DateTime.Today, + EndDate = null + }; + + var newAssignment = new UserDepartment() + { + // DepartmentID will be NULL. + Department = new Department() + { + Id = 5, + State = DepartmentState.Active, + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code + }, + Employee = _employee, + StartDate = DateTime.Today, + EndDate = null + }; + + Assert.True(existingAssignment.IsConflictingAssignment(newAssignment, out string message)); + } + + [Test] + public void ConflictingAssignment_Case_Null_Valid() + { + var existingAssignment = new UserDepartment() + { + DepartmentId = 5, + Employee = _employee, + StartDate = DateTime.Parse("2021-01-01T00:00:00.000Z").ToUniversalTime(), + EndDate = null + }; + + var newAssignment = new UserDepartment() + { + // DepartmentID will be NULL. + Department = new Department() + { + Id = 5, + State = DepartmentState.Active, + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code + }, + Employee = _employee, + StartDate = DateTime.Parse("2021-07-01T00:00:00.000Z").ToUniversalTime(), + EndDate = DateTime.Parse("2021-07-31T23:59:00.000Z").ToUniversalTime() + }; + + Assert.True(existingAssignment.IsConflictingAssignment(newAssignment, out string message)); + } + + [Test] + public void ConflictingAssignment_Case_Valid_Null_Before() + { + var existingAssignment = new UserDepartment() + { + DepartmentId = 5, + Employee = _employee, + StartDate = DateTime.Today, + EndDate = DateTime.Today.AddDays(1) + }; + + var newAssignment = new UserDepartment() + { + // DepartmentID will be NULL. + Department = new Department() + { + Id = 5, + State = DepartmentState.Active, + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code + }, + Employee = _employee, + StartDate = DateTime.Today.AddDays(1), + EndDate = null + }; + + Assert.True(existingAssignment.IsConflictingAssignment(newAssignment, out string message)); + } + + [Test] + public void NonConflictingAssignment_Case_Valid_Null_After() + { + var existingAssignment = new UserDepartment() + { + DepartmentId = 5, + Employee = _employee, + StartDate = DateTime.Today, + EndDate = DateTime.Today.AddDays(1) + }; + + var newAssignment = new UserDepartment() + { + // DepartmentID will be NULL. + Department = new Department() + { + Id = 5, + State = DepartmentState.Active, + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code + }, + Employee = _employee, + StartDate = DateTime.Today.AddDays(2), + EndDate = null + }; + + Assert.False(existingAssignment.IsConflictingAssignment(newAssignment, out string message)); + } + + [Test] + public void NonConflictingAssignment_Case_Valid_Valid_After() + { + var existingAssignment = new UserDepartment() + { + DepartmentId = 5, + Employee = _employee, + StartDate = DateTime.Parse("2021-07-01T00:00:00.000Z").ToUniversalTime(), + EndDate = DateTime.Parse("2021-07-31T23:59:00.000Z").ToUniversalTime() + }; + + var newAssignment = new UserDepartment() + { + // DepartmentID will be NULL. + Department = new Department() + { + Id = 5, + State = DepartmentState.Active, + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code + }, + Employee = _employee, + StartDate = DateTime.Parse("2021-08-01T00:00:00.000Z").ToUniversalTime(), + EndDate = DateTime.Parse("2021-08-31T23:59:00.000Z").ToUniversalTime() + }; + + Assert.False(existingAssignment.IsConflictingAssignment(newAssignment, out string message)); + } + + [Test] + public void NonConflictingAssignment_Case_Valid_Valid_Before() + { + var existingAssignment = new UserDepartment() + { + DepartmentId = 5, + Employee = _employee, + StartDate = DateTime.Parse("2021-07-01T00:00:00.000Z").ToUniversalTime(), + EndDate = DateTime.Parse("2021-07-31T23:59:00.000Z").ToUniversalTime() + }; + + var newAssignment = new UserDepartment() + { + // DepartmentID will be NULL. + Department = new Department() + { + Id = 5, + State = DepartmentState.Active, + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code + }, + Employee = _employee, + StartDate = DateTime.Parse("2021-06-01T00:00:00.000Z").ToUniversalTime(), + EndDate = DateTime.Parse("2021-06-30T23:59:00.000Z").ToUniversalTime() + }; + + Assert.False(existingAssignment.IsConflictingAssignment(newAssignment, out string message)); + } + + [Test] + public void ConflictingAssignment_Case_Valid_Valid_Overlap() + { + var existingAssignment = new UserDepartment() + { + DepartmentId = 5, + Employee = _employee, + StartDate = DateTime.Today, + EndDate = DateTime.Today.AddDays(5) + }; + + var newAssignment = new UserDepartment() + { + // DepartmentID will be NULL. + Department = new Department() + { + Id = 5, + State = DepartmentState.Active, + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code + }, + Employee = _employee, + StartDate = DateTime.Today.AddDays(2), + EndDate = DateTime.Today.AddDays(10) + }; + + Assert.True(existingAssignment.IsConflictingAssignment(newAssignment, out string message)); + } + + [Test] + public void ConflictingAssignment_Case_Overlap_IgnoreTimestamp() + { + var existingAssignment = new UserDepartment() + { + DepartmentId = 5, + Employee = _employee, + StartDate = DateTime.Parse("2021-07-01T00:00:00.000Z").ToUniversalTime(), + EndDate = DateTime.Parse("2021-07-05T12:00:00.000Z").ToUniversalTime() + }; + + var newAssignment = new UserDepartment() + { + // DepartmentID will be NULL. + Department = new Department() + { + Id = 5, + State = DepartmentState.Active, + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code + }, + Employee = _employee, + StartDate = DateTime.Parse("2021-07-05T15:00:00.000Z").ToUniversalTime(), + EndDate = DateTime.Parse("2021-07-08T00:00:00.000Z").ToUniversalTime() + }; + + Assert.True(existingAssignment.IsConflictingAssignment(newAssignment, out string message)); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Extensions/PayoutProfileCalculateInput.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Extensions/PayoutProfileCalculateInput.cs new file mode 100644 index 0000000..f777621 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Extensions/PayoutProfileCalculateInput.cs @@ -0,0 +1,30 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace Application.UnitTests.Extensions +{ + public class PayoutProfileCalculateTestInput + { + public PayoutProfileCalculateTestInput(Product product, Role role, PayoutType type, PayoutProfile expectedProfile, int goal, + int actual, int expectedPay, double expectedPercentage) + { + Product = product; + Role = role; + Type = type; + ExpectedProfile = expectedProfile; + Goal = goal; + Actual = actual; + ExpectedPay = expectedPay; + ExpectedPercentage = expectedPercentage; + } + + public Product Product { get; set; } + public Role Role { get; set; } + public PayoutType Type { get; set; } + public PayoutProfile ExpectedProfile { get; set; } + public int Goal { get; set; } + public int Actual { get; set; } + public int ExpectedPay { get; set; } + public double ExpectedPercentage { get; set; } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Extensions/PayoutProfileExtensionsTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Extensions/PayoutProfileExtensionsTest.cs new file mode 100644 index 0000000..3200139 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Extensions/PayoutProfileExtensionsTest.cs @@ -0,0 +1,111 @@ +using System; +using System.Globalization; +using System.Linq; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +using NUnit.Framework; + +namespace Application.UnitTests.Extensions +{ + public class PayoutProfileExtensionsTest + { + private readonly DateTime _nonOverriddenDateTime = DateTime.ParseExact("2021-12-01", "yyyy-MM-dd", CultureInfo.InvariantCulture); + + [Test] + public void Payout_Basic() + { + var product = Product.Vehicle; + var role = Role.BM; + var expectedProfile = PayoutProfile.BMVehicle; + var goal = 10; + var actual = 10; + var expectedPay = 125; + var expectedPercentage = 1.0; + + var profile = PayoutProfile.LookupProfile(product, role, PayoutType.Department); + Assert.NotNull(profile); + Assert.AreEqual(profile, expectedProfile); + + double percentage = actual / (double)goal; + Assert.AreEqual(expectedPercentage, percentage); + + var range = profile.FindPayoutRange(percentage, _nonOverriddenDateTime); + Assert.IsTrue(range.IsInRange(percentage)); + + var result = profile.CalculateLineItemPayout(goal, actual, _nonOverriddenDateTime); + Assert.AreEqual(expectedPay, result); + } + + [TestCase(0, 0)] + [TestCase(0.12, 0.1)] + [TestCase(41.62, 33.3)] + [TestCase(83.38, 66.7)] + [TestCase(41.25, 33)] + [TestCase(31.25, 25)] + [TestCase(123.75, 99)] + [TestCase(125.00, 100)] + public void Payout_Basic_Percentage(decimal expectedPayout, decimal fromPercentage) + { + var product = Product.Vehicle; + var role = Role.BM; + var expectedProfile = PayoutProfile.BMVehicle; + var goal = 10; + var actual = 10; + var expectedPay = expectedPayout; + var expectedPercentage = 1.0; + + var profile = PayoutProfile.LookupProfile(product, role, PayoutType.Department); + Assert.NotNull(profile); + Assert.AreEqual(profile, expectedProfile); + + double percentage = actual / (double)goal; + Assert.AreEqual(expectedPercentage, percentage); + + var range = profile.FindPayoutRange(percentage, _nonOverriddenDateTime); + Assert.IsTrue(range.IsInRange(percentage)); + + var result = profile.CalculateLineItemPayout(goal, actual, _nonOverriddenDateTime, fromPercentage); + Assert.AreEqual(expectedPay, result); + } + + [Test] + public void Payout_All_Test() + { + var payoutProfiles = PayoutProfile.PayoutProfiles; + var testInputs = new[] + { + new PayoutProfileCalculateTestInput(Product.Mortgage, Role.BM, PayoutType.Department, + payoutProfiles.Find(payout => payout.Id == 1), 10, 15, 325, 1.5), + new PayoutProfileCalculateTestInput(Product.Mortgage, Role.ABM, PayoutType.Department, + payoutProfiles.Find(payout => payout.Id == 2), 8, 10, 175, 1.25), + new PayoutProfileCalculateTestInput(Product.Mortgage, Role.MSCTeamLead, PayoutType.Department, + payoutProfiles.Find(payout => payout.Id == 5), 8, 10, 75, 1.25), + new PayoutProfileCalculateTestInput(Product.Mortgage, Role.MSCTeamLead, PayoutType.Personal, + payoutProfiles.Find(payout => payout.Id == 4), 8, 10, 150, 1.25), + new PayoutProfileCalculateTestInput(Product.Vehicle, Role.BM, PayoutType.Department, + payoutProfiles.Find(payout => payout.Id == 6), 10, 10, 125, 1.0), + new PayoutProfileCalculateTestInput(Product.Vehicle, Role.PFO, PayoutType.Personal, + payoutProfiles.Find(payout => payout.Id == 8), 4, 3, 25, 0.75), + // You can't test GAP here, it doesn't pay out like that. @see GapPayoutStrategy + }; + + foreach (var testInput in testInputs) + { + var profile = PayoutProfile.LookupProfile(testInput.Product, testInput.Role, testInput.Type); + Assert.NotNull(profile); + Assert.AreEqual(profile, testInput.ExpectedProfile); + Assert.AreEqual(profile.Product, testInput.Product); + Assert.IsTrue(profile.Roles.Contains(testInput.Role)); + + double percentage = testInput.Actual / (double)testInput.Goal; + Assert.AreEqual(testInput.ExpectedPercentage, percentage); + + var range = profile.FindPayoutRange(percentage, _nonOverriddenDateTime); + Assert.IsTrue(range.IsInRange(percentage)); + var result = profile.CalculateLineItemPayout(testInput.Goal, testInput.Actual, _nonOverriddenDateTime); + Assert.AreEqual(testInput.ExpectedPay, result); + } + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/LCU.API.Internal.Operations.IncentiveApi.UnitTests.csproj b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/LCU.API.Internal.Operations.IncentiveApi.UnitTests.csproj new file mode 100644 index 0000000..d555a8c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/LCU.API.Internal.Operations.IncentiveApi.UnitTests.csproj @@ -0,0 +1,54 @@ + + + + net8.0 + false + + + + + + + + + + + + + + + + + + + + + Always + + + + + + + PreserveNewest + + + + + PreserveNewest + + + + PreserveNewest + + + + PreserveNewest + + + + + + + + diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/DepartmentGoalsTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/DepartmentGoalsTest.cs new file mode 100644 index 0000000..cbdd1e8 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/DepartmentGoalsTest.cs @@ -0,0 +1,65 @@ +using System; +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using System.Collections.Generic; +using NUnit.Framework; + +namespace Application.UnitTests.Models +{ + public class DepartmentGoalsTest + { + [Test] + public void ApplyGoals_Basic() + { + var goals = new List() + { + new Goal() + { + Product = Product.Vehicle, + DepartmentId = 55, + Value = 1, + StartDate = DateTime.Today + }, + new Goal() + { + Product = Product.CheckCard, + DepartmentId = 55, + Value = 34, + StartDate = DateTime.Today + } + }; + var user = new AppUser() + { + AdEmail = "tester@test.com", + BaseRole = "Tester", + EmployeeId = "11771", + FullName = "Test McTester", + HomeDepartment = "HOME", + PersonId = 565, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 55, + Role = Role.Admin + } + } + }; + var users = new List() + { + user + }; + + var departmentGoals = new DepartmentGoals(55, DateTime.Now.Year); + Assert.NotNull(departmentGoals.MonthlyGoals); + Assert.AreEqual(12, departmentGoals.MonthlyGoals.Length); + + Assert.AreEqual(0,departmentGoals.MonthlyGoals[DateTime.Today.Month-1].ProductGoals[Product.Vehicle] ); + Assert.AreEqual(0,departmentGoals.MonthlyGoals[DateTime.Today.Month-1].ProductGoals[Product.CheckCard] ); + departmentGoals.ApplyAll(goals); + Assert.AreEqual(1,departmentGoals.MonthlyGoals[DateTime.Today.Month-1].ProductGoals[Product.Vehicle] ); + Assert.AreEqual(34,departmentGoals.MonthlyGoals[DateTime.Today.Month-1].ProductGoals[Product.CheckCard] ); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/EmployeeGoalTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/EmployeeGoalTest.cs new file mode 100644 index 0000000..4ab9185 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/EmployeeGoalTest.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +using NUnit.Framework; + +namespace Application.UnitTests.Models +{ + public class EmployeeGoalTest + { + [Test] + public void ApplyGoals_Basic() + { + var goals = new List() + { + new Goal() + { + Product = Product.Vehicle, + DepartmentId = 55, + EmployeeId = "11771", + Value = 1 + }, + new Goal() + { + Product = Product.CheckCard, + DepartmentId = 55, + EmployeeId = "11771", + Value = 34 + }, + // This user shouldn't have this goal, but they do... + new Goal() + { + Product = Product.CheckCard, + DepartmentId = 56, + EmployeeId = "11771", + Value = 44 + }, + }; + var user = new AppUser() + { + Role = Role.Admin, + AdEmail = "tester@test.com", + BaseRole = "Tester", + EmployeeId = "11771", + FullName = "Test McTester", + HomeDepartment = "HOME", + PersonId = 565, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 55 + } + } + }; + var range = new MonthRange(2021,1,2021,2); + var employeeGoals = new EmployeeGoals(user, range); + Assert.NotNull(employeeGoals.Departments); + Assert.AreEqual(1, employeeGoals.Departments.Count); + + Assert.AreEqual(0,employeeGoals.Goals[55].ProductGoals[Product.Vehicle]); + Assert.AreEqual(0,employeeGoals.Goals[55].ProductGoals[Product.CheckCard]); + employeeGoals.ApplyAll(goals); + Assert.AreEqual(1,employeeGoals.Goals[55].ProductGoals[Product.Vehicle]); + Assert.AreEqual(34,employeeGoals.Goals[55].ProductGoals[Product.CheckCard]); + } + + [Test] + public void EmployeeGoals_CreatesGoalsForDepartmentsAssignedInRange() + { + var deparmentBeforeRange = new UserDepartment() + { + DepartmentId = 1, + StartDate = new DateTime(2020, 12, 1), + EndDate = new DateTime(2020, 12, 31) + }; + + var departmentAfterRange = new UserDepartment() + { + DepartmentId = 2, + StartDate = new DateTime(2021, 3, 1), + EndDate = new DateTime(2021, 3, 31) + }; + + var departmentInRange = new UserDepartment() + { + DepartmentId = 3, + StartDate = new DateTime(2021, 2, 1), + EndDate = new DateTime(2021, 2, 28) + }; + + var departmentCurrentlyAssigned = new UserDepartment() + { + DepartmentId = 4, + StartDate = new DateTime(2020, 1, 1), + EndDate = null + }; + + var user = new AppUser() + { + Role = Role.Admin, + AdEmail = "tester@test.com", + BaseRole = "Tester", + EmployeeId = "11771", + FullName = "Test McTester", + HomeDepartment = "HOME", + PersonId = 565, + Departments = new List() + { + deparmentBeforeRange, + departmentAfterRange, + departmentCurrentlyAssigned, + departmentInRange + } + }; + var range = new MonthRange(2021,1,2021,2); + + var employeeGoals = new EmployeeGoals(user, range); + + Assert.AreEqual(2, employeeGoals.Departments.Count); + Assert.IsFalse(employeeGoals.Departments.Contains(deparmentBeforeRange)); + Assert.IsFalse(employeeGoals.Departments.Contains(departmentAfterRange)); + Assert.IsTrue(employeeGoals.Departments.Contains(departmentInRange)); + Assert.IsTrue(employeeGoals.Departments.Contains(departmentCurrentlyAssigned)); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/EmployeeProgressTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/EmployeeProgressTest.cs new file mode 100644 index 0000000..6cae041 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/EmployeeProgressTest.cs @@ -0,0 +1,313 @@ +using System; +using System.Collections.Generic; +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +using NUnit.Framework; + +namespace Application.UnitTests.Models +{ + public class EmployeeProgressTest + { + [Test] + public void ApplyProgress_Basic() + { + var activity = new List() + { + new Activity() + { + Id = 5, + Product = Product.Mortgage, + State = ActivityState.Accepted, + EmployeeId = "11771", + DepartmentId = 55, + Type = ActivityType.Sale, + Detail = new ActivityDetail() + { + AccountBalance = 5100.25 + } + }, + new Activity() + { + Id = 5, + Product = Product.Vehicle, + State = ActivityState.Accepted, + EmployeeId = "11771", + DepartmentId = 55, + Type = ActivityType.Sale, + Detail = new ActivityDetail() + { + IsDebtProtectionEligible = true, + DebtProtectionAddDate = DateTime.Today, + AccountBalance = 5100.25 + } + }, + new Activity() + { + Id = 6, + Product = Product.Mortgage, + State = ActivityState.Rejected, + EmployeeId = "11771", + DepartmentId = 55, + Type = ActivityType.Sale, + Detail = new ActivityDetail() + { + AccountBalance = 7100.75 + } + }, + new Activity() + { + Id = 55, + Product = Product.CheckCard, + State = ActivityState.Accepted, + EmployeeId = "11771", + DepartmentId = 55, + Type = ActivityType.Sale + }, + new Activity() + { + Id = 56, + Product = Product.CheckCard, + State = ActivityState.Transferred, + EmployeeId = "11771", + DepartmentId = 55, + Type = ActivityType.Sale + }, + new Activity() + { + Id = 57, + Product = Product.ATMCard, + State = ActivityState.Accepted, + EmployeeId = "11771", + DepartmentId = 55, + Type = ActivityType.Sale + } + }; + var user = new AppUser() + { + AdEmail = "tester@test.com", + BaseRole = "Tester", + EmployeeId = "11771", + FullName = "Test McTester", + HomeDepartment = "HOME", + PersonId = 565, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 55, + Role = Role.Admin + } + } + }; + var range = new MonthRange(2021, 1, 2021, 2); + + var employeeProgress = new EmployeeProgress(user, range, new List()); + Assert.NotNull(employeeProgress.Departments); + Assert.AreEqual(1, employeeProgress.Departments.Count); + + Assert.AreEqual(0, employeeProgress.Progress[55].ProductActivity[Product.Mortgage]); + Assert.AreEqual(0, employeeProgress.Progress[55].ProductActivity[Product.CheckCard]); + employeeProgress.ApplyAll(activity); + Assert.AreEqual(5100.25, employeeProgress.Progress[55].ProductActivity[Product.Mortgage]); + Assert.AreEqual(1, employeeProgress.Progress[55].ProductActivity[Product.CheckCard]); + } + + [Test] + public void ApplyProgress_GAP_DP() + { + var activity = new List() + { + new Activity() + { + Id = 2, + Product = Product.Mortgage, + State = ActivityState.Accepted, + EmployeeId = "11771", + DepartmentId = 55, + Type = ActivityType.Sale, + Detail = new ActivityDetail() + { + AccountBalance = 5100.25 + } + }, + new Activity() + { + Id = 2, + Product = Product.Vehicle, + State = ActivityState.Accepted, + EmployeeId = "11771", + DepartmentId = 55, + Type = ActivityType.Sale, + Detail = new ActivityDetail() + { + IsDebtProtectionEligible = true, + DebtProtectionAddDate = null, + AccountBalance = 5100.25 + } + }, + new Activity() + { + Id = 3, + Product = Product.DebtProtectionEligibleLoan, + State = ActivityState.Accepted, + EmployeeId = "11771", + DepartmentId = 55, + Type = ActivityType.Sale, + Detail = new ActivityDetail() + { + IsDebtProtectionEligible = true, + DebtProtectionAddDate = null, + AccountBalance = 5100.25 + } + }, + new Activity() + { + Id = 5, + Product = Product.Vehicle, + State = ActivityState.Accepted, + EmployeeId = "11771", + DepartmentId = 55, + Type = ActivityType.Sale, + Detail = new ActivityDetail() + { + IsDebtProtectionEligible = true, + DebtProtectionAddDate = DateTime.Today, + AccountBalance = 5100.25 + } + }, + new Activity() + { + Id = 6, + Product = Product.Mortgage, + State = ActivityState.Rejected, + EmployeeId = "11771", + DepartmentId = 55, + Type = ActivityType.Sale, + Detail = new ActivityDetail() + { + AccountBalance = 7100.75 + } + }, + new Activity() + { + Id = 55, + Product = Product.CheckCard, + State = ActivityState.Accepted, + EmployeeId = "11771", + DepartmentId = 55, + Type = ActivityType.Sale + }, + new Activity() + { + Id = 56, + Product = Product.CheckCard, + State = ActivityState.Transferred, + EmployeeId = "11771", + DepartmentId = 55, + Type = ActivityType.Sale + }, + new Activity() + { + Id = 57, + Product = Product.ATMCard, + State = ActivityState.Accepted, + EmployeeId = "11771", + DepartmentId = 55, + Type = ActivityType.Sale + } + }; + var user = new AppUser() + { + AdEmail = "tester@test.com", + BaseRole = "Tester", + EmployeeId = "11771", + FullName = "Test McTester", + HomeDepartment = "HOME", + PersonId = 565, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 55, + Role = Role.Admin + } + } + }; + var range = new MonthRange(2021, 1, 2021, 2); + + var employeeProgress = new EmployeeProgress(user, range, new List()); + Assert.NotNull(employeeProgress.Departments); + Assert.AreEqual(1, employeeProgress.Departments.Count); + + Assert.AreEqual(0, employeeProgress.Progress[55].ProductActivity[Product.DebtProtectionEligibleLoan]); + employeeProgress.ApplyAll(activity); + Assert.AreEqual(1, employeeProgress.Progress[55].ProductActivity[Product.DebtProtectionEligibleLoan]); + Assert.AreEqual(3, employeeProgress.Progress[55].ProductOther[Product.DebtProtectionEligibleLoan]); + } + + [Test] + public void EmployeeProgress_CreatesProgressForDepartmentsAssignedInRange() + { + var departmentBeforeRange = new UserDepartment() + { + DepartmentId = 1, + StartDate = new DateTime(2020, 12, 1), + EndDate = new DateTime(2020, 12, 31), + Role = Role.Admin + }; + + var departmentAfterRange = new UserDepartment() + { + DepartmentId = 2, + StartDate = new DateTime(2021, 3, 1), + EndDate = new DateTime(2021, 3, 31), + Role = Role.Admin + }; + + var departmentInRange = new UserDepartment() + { + DepartmentId = 3, + StartDate = new DateTime(2021, 2, 1), + EndDate = new DateTime(2021, 2, 28), + Role = Role.Admin + }; + + var departmentCurrentlyAssigned = new UserDepartment() + { + DepartmentId = 4, + StartDate = new DateTime(2020, 1, 1), + EndDate = null, + Role = Role.Admin + }; + + var user = new AppUser() + { + AdEmail = "tester@test.com", + BaseRole = "Tester", + EmployeeId = "11771", + FullName = "Test McTester", + HomeDepartment = "HOME", + PersonId = 565, + Departments = new List() + { + departmentBeforeRange, + departmentAfterRange, + departmentCurrentlyAssigned, + departmentInRange + } + }; + var range = new MonthRange(2021, 1, 2021, 2); + + var employeeProgress = new EmployeeProgress(user, range, new List()); + + Assert.AreEqual(2, employeeProgress.Departments.Count); + Assert.IsFalse(employeeProgress.Departments.Contains(departmentBeforeRange)); + Assert.IsFalse(employeeProgress.Departments.Contains(departmentAfterRange)); + Assert.IsTrue(employeeProgress.Departments.Contains(departmentInRange)); + Assert.IsTrue(employeeProgress.Departments.Contains(departmentCurrentlyAssigned)); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/ExternalActivity/ExternalActivityTests.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/ExternalActivity/ExternalActivityTests.cs new file mode 100644 index 0000000..495f32a --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/ExternalActivity/ExternalActivityTests.cs @@ -0,0 +1,151 @@ +using System; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using NUnit.Framework; + +namespace Application.UnitTests.Models.ExternalActivity +{ + public class ExternalActivityTests + { + private NewLoanSaleActivity externalActivity = new NewLoanSaleActivity(); + [SetUp] + public void SetUp() + { + externalActivity.ActivityNbr = 100; + externalActivity.ActivityDateTime = DateTime.Parse("2021-09-28T21:32:38"); + externalActivity.SubjectAccountNbr = 555; + externalActivity.OriginatingPersonNbr = 10; + externalActivity.ActivityTypeCode = "ADAC"; + externalActivity.MajorAccountType = "CNS"; + externalActivity.MinorAccountType = "DV01"; + externalActivity.AssociateFirst = "Bruce"; + externalActivity.AssociateMiddleInit = null; + externalActivity.AssociateLast = "Willis"; + externalActivity.TaxPersonNbr = "1258"; + externalActivity.TaxOrgNbr = null; + externalActivity.AccountContractDate = DateTime.Parse("2021-09-28T00:00:00"); + externalActivity.CurrentAccountStatus = "ACT"; + externalActivity.BranchOrgNbr = 1119; + externalActivity.BranchOrgName = "Oak Creek Branch"; + externalActivity.EmployeeEmail = "email@test.com"; + externalActivity.LoanSourceCode = "NEW"; + externalActivity.LoanAmount = 23000; + externalActivity.DebtProtectionEffectiveDate = DateTime.Parse("2021-09-29T00:00:00"); + externalActivity.YoungestBirthdateOnLoan = DateTime.Parse("1999-04-01T00:00:00"); + externalActivity.HasOrgRoles = 0; + } + + [TestCase("BE21", 0, null, false, false)] + [TestCase("DV01", 1, null, false, true)] + [TestCase("DV01", 0, "12345", false, true)] + [TestCase("DV02", 0, null, true, true)] + [TestCase("DV03", 0, null, true, true)] + [TestCase("DV04", 0, null, true, true)] + [TestCase("DV05", 0, null, true, true)] + [TestCase("DV06", 0, null, true, true)] + [TestCase("DV07", 0, null, true, true)] + [TestCase("DV08", 0, null, true, true)] + [TestCase("SC01", 0, null, true, false)] + [TestCase("SC02", 0, null, true, false)] + [TestCase("SG01", 0, null, true, false)] + [TestCase("NotReal", 0, null, false, true)] + [TestCase("FE01", 0, null, false, false)] + public void ExternalLoanSaleActivity_IsDebtProtectionEligible_OK(string minorAccountType, int hasOrgRoles, string taxOrgNbr, + bool result, bool isGap) + { + externalActivity.MinorAccountType = minorAccountType; + externalActivity.TaxOrgNbr = taxOrgNbr; + externalActivity.HasOrgRoles = hasOrgRoles; + + var mapped = externalActivity.MapToActivity(); + Assert.AreEqual(result, mapped.Detail.IsDebtProtectionEligible); + Assert.AreEqual(isGap, mapped.Detail.IsGapInsuranceEligible); + } + + + [TestCase("FE01", Product.Mortgage, false)] + [TestCase("FE02", Product.Mortgage, false)] + [TestCase("FLML", Product.Mortgage, false)] + [TestCase("MF01", Product.Mortgage, false)] + [TestCase("FNMA", Product.FirstMortgageReferral, false)] + [TestCase("fnma", Product.FirstMortgageReferral, false)] + [TestCase("FRML", Product.FirstMortgageReferral, false)] + [TestCase("frMl", Product.FirstMortgageReferral, false)] + [TestCase("mJ30", Product.FirstMortgageReferral, false)] + [TestCase("MJ30", Product.FirstMortgageReferral, false)] + [TestCase("DL01",Product.Vehicle, true)] + [TestCase("DV07",Product.Vehicle, true)] + [TestCase("DV08",Product.Vehicle, true)] + [TestCase("DL09",Product.Vehicle, true)] + [TestCase("DV02",Product.Vehicle, true)] + [TestCase("DV03",Product.Vehicle, true)] + [TestCase("DV04",Product.Vehicle, true)] + [TestCase("DV05",Product.Vehicle, true)] + [TestCase("DV06",Product.Vehicle, true)] + [TestCase("DL08",Product.Vehicle, true)] + [TestCase("DV01",Product.Vehicle, true)] + [TestCase("DL13",Product.Vehicle, true)] + [TestCase("DL14",Product.Vehicle, true)] + [TestCase("DL16",Product.Vehicle, true)] + [TestCase("DL10",Product.Vehicle, true)] + [TestCase("DL12",Product.Vehicle, true)] + [TestCase("DL11",Product.Vehicle, true)] + [TestCase("DL07",Product.Vehicle, true)] + [TestCase("DL06",Product.Vehicle, true)] + [TestCase("SC01",Product.DebtProtectionEligibleLoan, false)] + [TestCase("SC02",Product.DebtProtectionEligibleLoan, false)] + [TestCase("SG01",Product.DebtProtectionEligibleLoan, false)] + + public void NewLoanActivity_MapActivity_Product(string minorAccountType, Product expectedProduct, bool isGap) + { + externalActivity.MinorAccountType = minorAccountType; + var mapped = externalActivity.MapToActivity(); + Assert.AreEqual(expectedProduct, mapped.Product); + Assert.AreEqual(isGap, externalActivity.IsGapInsuranceEligible()); + } + + [TestCase("CC Balance Transfer", Product.CreditCardBalanceTransfer, 0)] + [TestCase("ATM Card", Product.ATMCard, 0)] + [TestCase("Check Card Referral", Product.CheckCard, 0)] + [TestCase("Checking Referral", Product.NewChecking, 0)] + [TestCase("Mortgage Referral", Product.Mortgage, 0)] + [TestCase("Direct Deposit", Product.DirectDeposit, 0)] + [TestCase("LIC Referral", Product.LIC, 1)] + [TestCase("First Mortgage Referral", Product.FirstMortgageReferral, 0)] + [TestCase("eStatement Referral", Product.eStatement, 0)] + public void NewActivityManagerReferral_MapActivity_Product(string campaign, Product expectedProduct, int isFinalStation) + { + var amr = new NewActivityManagerReferral(); + amr.Campaign = campaign; + amr.IsFinalStation = isFinalStation; + var mapped = amr.MapToActivity(); + Assert.AreEqual(expectedProduct, mapped.Product); + } + + [TestCase("ATM", Product.ATMCard)] + [TestCase("DB01", Product.CheckCard)] + public void NewCardSaleActivity_MapActivity_Product(string cardType, Product expectedProduct) + { + var amr = new NewAtmCheckCardActivity(); + amr.CardType = cardType; + var mapped = amr.MapToActivity(); + Assert.AreEqual(expectedProduct, mapped.Product); + } + + [Test] + public void ExternalLoanSaleActivity_IsDebtProtectionEligible_AgeCalc_OK() + { + externalActivity.YoungestBirthdateOnLoan = DateTime.Now.AddYears(-70).AddDays(-1); + var mapped = externalActivity.MapToActivity(); + Assert.AreEqual(false, mapped.Detail.IsDebtProtectionEligible); + + externalActivity.YoungestBirthdateOnLoan = DateTime.Now.AddYears(-68).AddDays(-1); + mapped = externalActivity.MapToActivity(); + Assert.AreEqual(true, mapped.Detail.IsDebtProtectionEligible); + + externalActivity.YoungestBirthdateOnLoan = DateTime.Now.AddYears(-22).AddDays(-1); + mapped = externalActivity.MapToActivity(); + Assert.AreEqual(true, mapped.Detail.IsDebtProtectionEligible); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/GoalDispersalReciept.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/GoalDispersalReciept.cs new file mode 100644 index 0000000..1b6d05c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/GoalDispersalReciept.cs @@ -0,0 +1,31 @@ +using System; +using System.Linq; +using LCU.API.Internal.Operations.IncentiveApi.Models.GoalDispersal; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using NUnit.Framework; + +namespace Application.UnitTests.Models +{ + public class GoalDispersalReceiptTest + { + [Test] + public void GoalDispersalReceiptItem_Does_Not_Require_Name() + { + var GDR = new GoalDispersalReceipt(10, GoalDispersalStrategy.ExtrapolateFromExistingGoals); + GDR.AddNewDispersalError(GoalDispersalError.ErrorsDuringDisperse, "1", "ERROR"); + GDR.AddNewDispersalError(GoalDispersalError.ErrorsDuringDisperse, "2", "ERROR", null); + var obj = new {name = "batman"}; + + var testFunc = new Func((string name) => + { + if (name == "batman") return null; + return "name"; + }); + + GDR.AddNewDispersalError(GoalDispersalError.ErrorsDuringDisperse, "3", "ERROR", obj.name); + GDR.AddNewDispersalError(GoalDispersalError.ErrorsDuringDisperse, "4", "ERROR", testFunc(obj.name)); + Assert.AreEqual(4,GDR.GoalDispersalErrors.Count); + Assert.AreEqual(3, GDR.GoalDispersalErrors.Count(x => x.EmployeeName == null)); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/PayDayTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/PayDayTest.cs new file mode 100644 index 0000000..2ec6617 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/PayDayTest.cs @@ -0,0 +1,22 @@ +using System; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using NUnit.Framework; + +namespace Application.UnitTests.Models +{ + public class PayDayTest + { + [Test] + public void PayDay_Predict_2() + { + var service = new DateTimeService(); + var date = service.getNextBonusPeriod(new DateTime(2021,10,3)); + Assert.AreEqual("10/15/2021", date.ToString("MM/dd/yyyy")); + date = service.getNextBonusPeriod(new DateTime(2021,10,22)); + Assert.AreEqual("11/26/2021", date.ToString("MM/dd/yyyy")); + date = service.getNextBonusPeriod(new DateTime(2022,4,17)); + Assert.AreEqual("05/27/2022", date.ToString("MM/dd/yyyy")); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/PersonnelGoalsTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/PersonnelGoalsTest.cs new file mode 100644 index 0000000..d2fb270 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/PersonnelGoalsTest.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +using NUnit.Framework; + +namespace Application.UnitTests.Models +{ + public class PersonnelGoalsTest + { + [Test] + public void ApplyGoals_Basic() + { + var goals = new List() + { + new Goal() + { + Product = Product.Vehicle, + DepartmentId = 55, + EmployeeId = "11771", + Value = 1 + }, + new Goal() + { + Product = Product.CheckCard, + DepartmentId = 55, + EmployeeId = "11771", + Value = 34 + }, + // This department shouldn't have this goal, but they do... + new Goal() + { + Product = Product.CheckCard, + DepartmentId = 55, + EmployeeId = "11772", + Value = 44 + }, + }; + var user = new AppUser() + { + Role = Role.Admin, + AdEmail = "tester@test.com", + BaseRole = "Tester", + EmployeeId = "11771", + FullName = "Test McTester", + HomeDepartment = "HOME", + PersonId = 565, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 55 + } + } + }; + var users = new List() + { + user + }; + + var personnelGoals = new PersonnelGoals(55, DateTime.Now.Year, DateTime.Now.Month, users); + Assert.NotNull(personnelGoals.Personnel); + Assert.AreEqual(1, personnelGoals.Personnel.Count); + + Assert.AreEqual(0,personnelGoals.Goals["11771"].ProductGoals[Product.Vehicle] ); + Assert.AreEqual(0,personnelGoals.Goals["11771"].ProductGoals[Product.CheckCard] ); + personnelGoals.ApplyAll(goals); + Assert.AreEqual(1,personnelGoals.Goals["11771"].ProductGoals[Product.Vehicle]); + Assert.AreEqual(34,personnelGoals.Goals["11771"].ProductGoals[Product.CheckCard]); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/ProductDetailTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/ProductDetailTest.cs new file mode 100644 index 0000000..272b593 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/ProductDetailTest.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +using NUnit.Framework; + +namespace Application.UnitTests.Models +{ + public class ProductDetailTest + { + [Test] + public void AssertProductDetailList() + { + // Ensure every product has a detail + foreach (Product product in Enum.GetValues(typeof(Product))) + { + Assert.NotNull(ProductDetail.DetailsForProduct(product)); + } + + // Ensure there isn't more than one detail per product. + var checkList = new Dictionary(); + foreach (var detail in ProductDetail.ProductDetails) + { + Assert.False(checkList.ContainsKey(detail.Product)); + checkList[detail.Product] = detail; + } + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/SettingsTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/SettingsTest.cs new file mode 100644 index 0000000..90505aa --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Models/SettingsTest.cs @@ -0,0 +1,69 @@ +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using NUnit.Framework; + +namespace Application.UnitTests.Models +{ + public class SettingsTest + { + private Settings _settings; + + [Test] + public void Constructs_OrganizationSet_OK() + { + _settings = new Settings(); + + Assert.AreEqual(_settings.OrganizationSet.Count, 1); + Assert.Contains("MEMEXP", _settings.OrganizationSet.Keys); + Assert.Contains("Member Experience", _settings.OrganizationSet.Values); + } + + [Test] + public void Constructs_BusinessLineSet_OK() + { + _settings = new Settings(); + + Assert.AreEqual(_settings.BusinessLines.Count, 3); + Assert.Contains(BusinessLine.Operations, _settings.BusinessLines); + Assert.Contains(BusinessLine.BranchOperations, _settings.BusinessLines); + Assert.Contains(BusinessLine.MemberServices, _settings.BusinessLines); + } + + [Test] + public void Constructs_RegionSet_OK() + { + _settings = new Settings(); + + Assert.Contains(Region.North, _settings.Regions); + Assert.Contains(Region.South, _settings.Regions); + Assert.Contains(Region.East, _settings.Regions); + Assert.Contains(Region.West, _settings.Regions); + } + + [Test] + public void Constructs_PermissionSet_OK() + { + _settings = new Settings(); + + var permissionCount = Permission.GetAllPermissions().Count; + Assert.AreEqual(permissionCount, _settings.Permissions.Count); + Assert.Contains(Permission.Unknown, _settings.Permissions); + Assert.Contains(Permission.EligibleForReferrals, _settings.Permissions); + Assert.Contains(Permission.HaveSalesGoals, _settings.Permissions); + Assert.Contains(Permission.ViewDashboard, _settings.Permissions); + Assert.Contains(Permission.ViewPerformanceForDepartment, _settings.Permissions); + Assert.Contains(Permission.ViewPerformanceForRegion, _settings.Permissions); + Assert.Contains(Permission.ViewPerformanceForBusinessLine, _settings.Permissions); + Assert.Contains(Permission.ManageDepartmentGoals, _settings.Permissions); + Assert.Contains(Permission.ManageCommissions, _settings.Permissions); + Assert.Contains(Permission.CreateActivity, _settings.Permissions); + Assert.Contains(Permission.AssignPermissionsToAssociates, _settings.Permissions); + Assert.Contains(Permission.AssignAssociatesToDepartment, _settings.Permissions); + Assert.Contains(Permission.TransferActivitiesBetweenDepartments, _settings.Permissions); + Assert.Contains(Permission.TransferActivitiesBetweenAssociates, _settings.Permissions); + Assert.Contains(Permission.GeneratePayoutFiles, _settings.Permissions); + Assert.Contains(Permission.AssignRegionalManager, _settings.Permissions); + Assert.Contains(Permission.AccessTestEnvironments, _settings.Permissions); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/AccountingRepositoryTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/AccountingRepositoryTest.cs new file mode 100644 index 0000000..07029f5 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/AccountingRepositoryTest.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Microsoft.EntityFrameworkCore; +using Moq; +using NUnit.Framework; + +namespace Application.UnitTests.Repository +{ + public class AccountingRepositoryTest + { + private IAccountingRepository _repository; + private ApplicationDbContext _context; + + [SetUp] + public void Setup() + { + var mockDateTime = new DateTimeService(); + var mockCurrentUserService = Mock.Of(); + Mock.Get(mockCurrentUserService).Setup(s => s.GetCurrentUser()).Returns(new ApplicationUser() + { + Email = "JohnTest@test.com", + }); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .EnableSensitiveDataLogging(true) + .Options; + _context = new ApplicationDbContext(options, mockCurrentUserService, mockDateTime); + _repository = new AccountingRepository(_context); + } + + [Test] + public async Task Basic_Flow_Test() + { + var today = DateTime.Today; + var saved = await _repository.GetAccountingSummaryAsync(today); + Assert.IsNull(saved); + + var deptNames = new Dictionary(); + deptNames.Add(1, "Dept1"); + + var lineItem = new PayoutLineItem(Product.Mortgage, PayoutProfile.BMMortgage, new Goal(), + 100m, new List(), today); + lineItem.DepartmentId = 1; + lineItem.PayoutTotal = 1000; + List allNewPayouts = new List() + { + lineItem + }; + var summary = new AccountingSummary(today); + summary.Apply(allNewPayouts, deptNames); + + var newId = await _repository.SaveAccountingSummaryAsync(summary); + await _context.SaveChangesAsync(); + + saved = await _repository.GetAccountingSummaryAsync(today); + Assert.NotNull(saved); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/ActivityRepositoryTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/ActivityRepositoryTest.cs new file mode 100644 index 0000000..fdb59d7 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/ActivityRepositoryTest.cs @@ -0,0 +1,1947 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Microsoft.EntityFrameworkCore; +using Moq; +using NUnit.Framework; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace Application.UnitTests.Repository +{ + public class ActivityRepositoryTest + { + private ApplicationDbContext _context; + private ActivityRepository _repository; + private ActivityChangeRepository _repositoryActivityChange; + + [SetUp] + public void Setup() + { + var mockDateTime = new DateTimeService(); + var mockCurrentUserService = Mock.Of(); + Mock.Get(mockCurrentUserService).Setup(s => s.GetCurrentUser()).Returns(new ApplicationUser + { + Email = "JohnTest@test.com", + }); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .EnableSensitiveDataLogging() + .Options; + _context = new ApplicationDbContext(options, mockCurrentUserService, mockDateTime); + _repository = new ActivityRepository(_context); + _repositoryActivityChange = new ActivityChangeRepository(_context); + } + + [Test] + public async Task Basic_Flow_Test() + { + var saved = await _repository.GetActivitiesAsync(); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + await _repository.SaveActivityAsync(new Activity + { + EmployeeId = "1254", + DepartmentId = 5, + Product = Product.CheckCard, + SourceId = "1", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + MemberName = "Tester 1", + AgreementNumber = "234", + SubjectPersonNbr = "34" + } + }); + await _context.SaveChangesAsync(); + + saved = await _repository.GetActivitiesAsync(); + Assert.NotNull(saved); + Assert.IsNotEmpty(saved); + var first = saved.First(); + // Make sure our Auditable is working. + Assert.NotNull(first.CreatedBy); + Assert.NotNull(first.Created); + Assert.NotNull(first.LastUpdatedBy); + Assert.NotNull(first.LastUpdated); + + var savedActivity = await _repository.GetActivityById(saved.First().Id); + Assert.AreSame(saved.First(), savedActivity); + } + + [Test] + public async Task Basic_RangeFlow_Test() + { + var saved = await _repository.GetActivitiesAsync(); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + var activities = new List + { + new Activity + { + EmployeeId = "1254", + DepartmentId = 5, + Product = Product.CheckCard, + SourceId = "1", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + MemberName = "Tester 1", + AgreementNumber = "234", + SubjectPersonNbr = "34" + } + }, + new Activity + { + EmployeeId = "2134", + DepartmentId = 5, + Product = Product.CheckCard, + SourceId = "2", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + MemberName = "Tester 2", + AgreementNumber = "234", + SubjectPersonNbr = "34" + } + } + }; + await _repository.SaveActivityRangeAsync(activities); + await _context.SaveChangesAsync(); + + saved = await _repository.GetActivitiesAsync(); + Assert.NotNull(saved); + Assert.IsNotEmpty(saved); + Assert.AreEqual(2, saved.Count()); + var first = saved.First(); + // Make sure our Auditable is working. + Assert.NotNull(first.CreatedBy); + Assert.NotNull(first.Created); + Assert.NotNull(first.LastUpdatedBy); + Assert.NotNull(first.LastUpdated); + + var savedActivity = await _repository.GetActivityById(saved.First().Id); + Assert.AreSame(saved.First(), savedActivity); + } + + [Test] + public async Task FindMonthlyPersonnelActivity_None() + { + var department = new Department + { + DepartmentName = "Dept1", + Id = 55, + State = DepartmentState.Active + }; + var user = new AppUser + { + AdEmail = "tester@test.com", + BaseRole = "Tester", + EmployeeId = "11771", + FullName = "Test McTester", + HomeDepartment = "HOME", + PersonId = 565, + Departments = new List() + }; + var assignment = new UserDepartment + { + Id = 99, + Department = department, + Employee = user, + Role = Role.Admin, + IsHome = true, + StartDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1), + EndDate = null + }; + user.Departments.Add(assignment); + + var range = new MonthRange(2021, 1, 2021, 2); + var found = await _repository.FindEmployeeActivityByMonthRange(user, range); + Assert.NotNull(found); + Assert.IsEmpty(found); + } + + [Test] + public async Task FindMonthlyPersonnelActivity_Basic() + { + var activity1 = new Activity + { + DepartmentId = 55, + EmployeeId = "11771", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + SourceId = "1" + }; + await _repository.SaveActivityAsync(activity1); + var activity2 = new Activity + { + DepartmentId = 56, + EmployeeId = "11771", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + SourceId = "2" + }; + await _repository.SaveActivityAsync(activity2); + var activity3 = new Activity + { + DepartmentId = 55, + EmployeeId = "11772", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + SourceId = "3" + }; + await _repository.SaveActivityAsync(activity3); + var activity4 = new Activity + { + DepartmentId = 55, + EmployeeId = "11772", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMonths(2), + SourceId = "4" + }; + await _repository.SaveActivityAsync(activity4); + + await _context.SaveChangesAsync(); + + var department = new Department + { + Id = 55, + State = DepartmentState.Active, + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code + }; + var user = new AppUser + { + AdEmail = "tester@test.com", + BaseRole = "Tester", + EmployeeId = "11771", + FullName = "Test McTester", + HomeDepartment = "HOME", + PersonId = 11771, + Departments = new List() + }; + var assignment = new UserDepartment + { + Id = 99, + Department = department, + DepartmentId = 55, + Employee = user, + Role = Role.Admin, + IsHome = true, + StartDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1), + EndDate = null + }; + user.Departments.Add(assignment); + + var range = new MonthRange( + DateTime.Today.Year, + DateTime.Today.Month, + DateTime.Today.Year, + DateTime.Today.Month + ); + + var found = await _repository.FindEmployeeActivityByMonthRange(user, range); + Assert.NotNull(found); + Assert.IsNotEmpty(found); + Assert.AreEqual(2, found.Count); + } + + [Test] + public async Task GetActivitiesAsync_Paginated_OK() + { + var activity1 = new Activity + { + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + SourceId = "1", + State = ActivityState.Accepted, + Created = DateTime.Today.Subtract(TimeSpan.FromDays(7)) + }; + await _repository.SaveActivityAsync(activity1); + var activity2 = new Activity + { + DepartmentId = 56, + EmployeeId = "2", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + SourceId = "2", + State = ActivityState.Accepted, + Created = DateTime.Today.Subtract(TimeSpan.FromDays(6)) + }; + await _repository.SaveActivityAsync(activity2); + var activity3 = new Activity + { + DepartmentId = 55, + EmployeeId = "3", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + SourceId = "3", + State = ActivityState.Rejected, + Created = DateTime.Today.Subtract(TimeSpan.FromDays(6)) + }; + await _repository.SaveActivityAsync(activity3); + var activity4 = new Activity + { + DepartmentId = 55, + EmployeeId = "4", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + SourceId = "4", + State = ActivityState.Created, + Created = DateTime.Today.Subtract(TimeSpan.FromDays(5)) + }; + await _repository.SaveActivityAsync(activity4); + await _context.SaveChangesAsync(); + + var found = await _repository.GetActivitiesAsync(1, 3); + Assert.NotNull(found); + Assert.IsNotEmpty(found.Items); + Assert.AreEqual(3, found.Items.Count()); + Assert.AreEqual(4, found.Total); + Assert.True(found.Items.Any(x => x.EmployeeId == "4")); + Assert.False(found.Items.Any(x => x.EmployeeId == "1")); + Assert.True(found.Items.Any(x => x.EmployeeId == "2")); + Assert.True(found.Items.Any(x => x.EmployeeId == "3")); + + found = await _repository.GetActivitiesAsync(2, 3); + Assert.AreEqual(1, found.Items.Count()); + Assert.AreEqual(4, found.Total); + Assert.False(found.Items.Any(x => x.EmployeeId == "4")); + Assert.True(found.Items.Any(x => x.EmployeeId == "1")); + Assert.False(found.Items.Any(x => x.EmployeeId == "2")); + Assert.False(found.Items.Any(x => x.EmployeeId == "3")); + + found = await _repository.GetActivitiesAsync(100, 3); + Assert.AreEqual(4, found.Total); + Assert.IsEmpty(found.Items); + } + + [Test] + public async Task GetActivitiesByActivityProductAsync_OK() + { + var activity1 = new Activity + { + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-3), + SourceId = "1", + State = ActivityState.Accepted, + LastUpdated = new DateTime(2021, 1, 4) + }; + await _repository.SaveActivityAsync(activity1); + var activity2 = new Activity + { + DepartmentId = 56, + EmployeeId = "2", + Type = ActivityType.Sale, + Product = Product.NewChecking, + Accepted = DateTime.Today.AddMinutes(-2), + SourceId = "2", + State = ActivityState.Accepted, + LastUpdated = new DateTime(2021, 1, 3) + }; + await _repository.SaveActivityAsync(activity2); + var activity3 = new Activity + { + DepartmentId = 55, + EmployeeId = "3", + Type = ActivityType.Sale, + Product = Product.NewChecking, + Accepted = DateTime.Today.AddMinutes(-1), + SourceId = "3", + State = ActivityState.Rejected, + LastUpdated = new DateTime(2021, 1, 2) + }; + await _repository.SaveActivityAsync(activity3); + var activity4 = new Activity + { + DepartmentId = 55, + EmployeeId = "4", + Type = ActivityType.Sale, + Product = Product.NewChecking, + Accepted = DateTime.Today, + SourceId = "4", + State = ActivityState.Accepted, + LastUpdated = new DateTime(2021, 1, 1) + }; + await _repository.SaveActivityAsync(activity4); + await _context.SaveChangesAsync(); + + + var filter = new ActivityFilter + { + Page = 1, + Limit = 2, + Product = new List { Product.CheckCard } + }; + var found = await _repository.GetPagedFilteredActivities(filter); + Assert.NotNull(found); + Assert.IsNotEmpty(found.Items); + Assert.AreEqual(1, found.Items.Count()); + Assert.AreEqual(1, found.Total); + Assert.True(found.Items.Any(x => x.EmployeeId == "1")); + } + + [Test] + public async Task GetActivitiesByActivityTypeAsync_OK() + { + var activity1 = new Activity + { + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-3), + SourceId = "1", + State = ActivityState.Accepted, + LastUpdated = new DateTime(2021, 1, 4) + }; + await _repository.SaveActivityAsync(activity1); + var activity2 = new Activity + { + DepartmentId = 56, + EmployeeId = "2", + Type = ActivityType.Referral, + Product = Product.NewChecking, + Accepted = DateTime.Today.AddMinutes(-2), + SourceId = "2", + State = ActivityState.Accepted, + LastUpdated = new DateTime(2021, 1, 3) + }; + await _repository.SaveActivityAsync(activity2); + var activity3 = new Activity + { + DepartmentId = 55, + EmployeeId = "3", + Type = ActivityType.Referral, + Product = Product.NewChecking, + Accepted = DateTime.Today.AddMinutes(-1), + SourceId = "3", + State = ActivityState.Rejected, + LastUpdated = new DateTime(2021, 1, 2) + }; + await _repository.SaveActivityAsync(activity3); + var activity4 = new Activity + { + DepartmentId = 55, + EmployeeId = "4", + Type = ActivityType.Referral, + Product = Product.NewChecking, + Accepted = DateTime.Today, + SourceId = "4", + State = ActivityState.Accepted, + LastUpdated = new DateTime(2021, 1, 1) + }; + await _repository.SaveActivityAsync(activity4); + await _context.SaveChangesAsync(); + + + var filter = new ActivityFilter + { + Page = 1, + Limit = 2, + Type = new List { ActivityType.Sale } + }; + var found = await _repository.GetPagedFilteredActivities(filter); + Assert.NotNull(found); + Assert.IsNotEmpty(found.Items); + Assert.AreEqual(1, found.Items.Count()); + Assert.AreEqual(1, found.Total); + Assert.True(found.Items.Any(x => x.EmployeeId == "1")); + } + + [Test] + public async Task GetActivitiesByStateAsync_OK() + { + var activity1 = new Activity + { + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-3), + SourceId = "1", + State = ActivityState.Accepted, + LastUpdated = new DateTime(2021, 1, 4) + }; + await _repository.SaveActivityAsync(activity1); + var activity2 = new Activity + { + DepartmentId = 56, + EmployeeId = "2", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-2), + SourceId = "2", + State = ActivityState.Accepted, + LastUpdated = new DateTime(2021, 1, 3) + }; + await _repository.SaveActivityAsync(activity2); + var activity3 = new Activity + { + DepartmentId = 55, + EmployeeId = "3", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-1), + SourceId = "3", + State = ActivityState.Rejected, + LastUpdated = new DateTime(2021, 1, 2) + }; + await _repository.SaveActivityAsync(activity3); + var activity4 = new Activity + { + DepartmentId = 55, + EmployeeId = "4", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + SourceId = "4", + State = ActivityState.Accepted, + LastUpdated = new DateTime(2021, 1, 1) + }; + await _repository.SaveActivityAsync(activity4); + await _context.SaveChangesAsync(); + + + var filter = new ActivityFilter + { + Page = 1, + Limit = 2, + State = new List { ActivityState.Accepted } + }; + var found = await _repository.GetPagedFilteredActivities(filter); + Assert.NotNull(found); + Assert.IsNotEmpty(found.Items); + Assert.AreEqual(2, found.Items.Count()); + Assert.AreEqual(3, found.Total); + Assert.False(found.Items.Any(x => x.EmployeeId == "3")); + Assert.True(found.Items.Any(x => x.EmployeeId == "4")); + Assert.False(found.Items.Any(x => x.EmployeeId == "1")); // not on this page + Assert.True(found.Items.Any(x => x.EmployeeId == "2")); + + filter.Page = 2; + found = await _repository.GetPagedFilteredActivities(filter); + Assert.AreEqual(1, found.Items.Count()); + Assert.AreEqual(3, found.Total); + Assert.False(found.Items.Any(x => x.EmployeeId == "3")); + Assert.False(found.Items.Any(x => x.EmployeeId == "2")); + Assert.True(found.Items.Any(x => x.EmployeeId == "1")); + Assert.False(found.Items.Any(x => x.EmployeeId == "4")); + + var filter2 = new ActivityFilter + { + Page = 1, + Limit = 100, + State = new List { ActivityState.ManualReview } + }; + found = await _repository.GetPagedFilteredActivities(filter2); + Assert.AreEqual(0, found.Total); + Assert.IsEmpty(found.Items); + + filter2.Page = 20; + found = await _repository.GetPagedFilteredActivities(filter2); + Assert.AreEqual(0, found.Total); + Assert.IsEmpty(found.Items); + } + + [Test] + public async Task GetActivitiesByFilter_More() + { + var activity1 = new Activity + { + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-3), + SourceId = "1", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + MemberName = "John Doe" + } + }; + await _repository.SaveActivityAsync(activity1); + var activity2 = new Activity + { + DepartmentId = 56, + EmployeeId = "2", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-2), + SourceId = "2", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + SubjectPersonNbr = "1Jane3Doe" + } + }; + await _repository.SaveActivityAsync(activity2); + var activity3 = new Activity + { + DepartmentId = 55, + EmployeeId = "3", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-1), + SourceId = "3", + State = ActivityState.Rejected, + Detail = new ActivityDetail + { + AccountNumber = "1234" + } + }; + await _repository.SaveActivityAsync(activity3); + var activity4 = new Activity + { + DepartmentId = 55, + EmployeeId = "4", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + SourceId = "4", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "12DOE35" + } + }; + await _repository.SaveActivityAsync(activity4); + await _context.SaveChangesAsync(); + + var filter = new ActivityFilter + { + Page = 1, + Limit = 2, + Query = "DoE" + }; + var found = await _repository.GetPagedFilteredActivities(filter); + Assert.NotNull(found); + Assert.IsNotEmpty(found.Items); + Assert.AreEqual(2, found.Items.Count()); + Assert.AreEqual(3, found.Total); + Assert.False(found.Items.Any(x => x.EmployeeId == "3")); + Assert.True(found.Items.Any(x => x.EmployeeId == "4")); + Assert.False(found.Items.Any(x => x.EmployeeId == "1")); // not on this page + Assert.True(found.Items.Any(x => x.EmployeeId == "2")); + + filter.Page = 2; + found = await _repository.GetPagedFilteredActivities(filter); + Assert.AreEqual(1, found.Items.Count()); + Assert.AreEqual(3, found.Total); + Assert.False(found.Items.Any(x => x.EmployeeId == "3")); + Assert.False(found.Items.Any(x => x.EmployeeId == "2")); + Assert.True(found.Items.Any(x => x.EmployeeId == "1")); + Assert.False(found.Items.Any(x => x.EmployeeId == "4")); + } + + [Test] + public async Task GetActivitiesByFilter_Employee_Department() + { + var activity1 = new Activity + { + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-3), + SourceId = "1", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + MemberName = "John Doe" + } + }; + await _repository.SaveActivityAsync(activity1); + var activity2 = new Activity + { + DepartmentId = 56, + EmployeeId = "2", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-2), + SourceId = "2", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + SubjectPersonNbr = "1Jane3Doe" + } + }; + await _repository.SaveActivityAsync(activity2); + var activity3 = new Activity + { + DepartmentId = 55, + EmployeeId = "3", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-1), + SourceId = "3", + State = ActivityState.Rejected, + Detail = new ActivityDetail + { + AccountNumber = "1234" + } + }; + await _repository.SaveActivityAsync(activity3); + var activity4 = new Activity + { + DepartmentId = 55, + EmployeeId = "4", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + SourceId = "4", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "12DOE35" + } + }; + await _repository.SaveActivityAsync(activity4); + await _context.SaveChangesAsync(); + + var filter = new ActivityFilter + { + Page = 1, + Limit = 2, + DepartmentId = 55 + }; + var found = await _repository.GetPagedFilteredActivities(filter); + Assert.NotNull(found); + Assert.IsNotEmpty(found.Items); + Assert.AreEqual(2, found.Items.Count()); + Assert.AreEqual(3, found.Total); + Assert.True(found.Items.Any(x => x.EmployeeId == "3")); + Assert.True(found.Items.Any(x => x.EmployeeId == "4")); + Assert.False(found.Items.Any(x => x.EmployeeId == "1")); // not on this page + Assert.False(found.Items.Any(x => x.EmployeeId == "2")); + + filter.EmployeeId = "4"; + found = await _repository.GetPagedFilteredActivities(filter); + Assert.AreEqual(1, found.Items.Count()); + Assert.AreEqual(1, found.Total); + Assert.False(found.Items.Any(x => x.EmployeeId == "3")); + Assert.False(found.Items.Any(x => x.EmployeeId == "2")); + Assert.False(found.Items.Any(x => x.EmployeeId == "1")); + Assert.True(found.Items.Any(x => x.EmployeeId == "4")); + + filter.DepartmentId = 99; + found = await _repository.GetPagedFilteredActivities(filter); + Assert.AreEqual(0, found.Items.Count()); + Assert.AreEqual(0, found.Total); + } + + [Test] + public async Task GetActivitiesByFilter_LowDate_Accepted() + { + var activity1 = new Activity + { + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + SourceId = "1", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + MemberName = "John Doe" + } + }; + await _repository.SaveActivityAsync(activity1); + var activity2 = new Activity + { + DepartmentId = 56, + EmployeeId = "2", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddDays(-1), + SourceId = "2", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + SubjectPersonNbr = "1Jane3Doe" + } + }; + await _repository.SaveActivityAsync(activity2); + var activity3 = new Activity + { + DepartmentId = 55, + EmployeeId = "3", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + SourceId = "3", + State = ActivityState.Rejected, + Detail = new ActivityDetail + { + AccountNumber = "1234" + } + }; + await _repository.SaveActivityAsync(activity3); + await _context.SaveChangesAsync(); + + var filter = new ActivityFilter + { + Page = 1, + Limit = 2, + DateLow = DateTime.Today + }; + var found = await _repository.GetPagedFilteredActivities(filter); + Assert.NotNull(found); + Assert.IsNotEmpty(found.Items); + Assert.AreEqual(2, found.Items.Count()); + Assert.AreEqual(2, found.Total); + Assert.True(found.Items.Any(x => x.Accepted == DateTime.Today)); + Assert.False(found.Items.Any(x => x.Accepted != DateTime.Today)); // not on this page + } + + [Test] + public async Task GetActivitiesByFilter_Sort() + { + // You can't set last updated. So you'll get these back in reverse order you saved them. + var activity1 = new Activity + { + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Referral, + Product = Product.CheckCard, + Accepted = DateTime.Today, + SourceId = "1", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + MemberName = "John Doe" + } + }; + await _repository.SaveActivityAsync(activity1); + var activity2 = new Activity + { + DepartmentId = 55, + EmployeeId = "3", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + SourceId = "3", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + SubjectPersonNbr = "1Jane3Doe" + } + }; + await _repository.SaveActivityAsync(activity2); + var activity3 = new Activity + { + DepartmentId = 56, + EmployeeId = "4", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + SourceId = "4", + State = ActivityState.Rejected, + Detail = new ActivityDetail + { + AccountNumber = "1234" + } + }; + await _repository.SaveActivityAsync(activity3); + var activity4 = new Activity + { + DepartmentId = 55, + EmployeeId = "2", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + SourceId = "2", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "12DOE35" + } + }; + await _repository.SaveActivityAsync(activity4); + await _context.SaveChangesAsync(); + + var filter = new ActivityFilter + { + Page = 1, + Limit = 99, + DepartmentId = 55, + SortBy = "EmployeeId" + }; + var found = await _repository.GetPagedFilteredActivities(filter); + var expected = new[] { "1", "2", "3" }; + Assert.NotNull(found); + Assert.IsNotEmpty(found.Items); + Assert.AreEqual(3, found.Items.Count()); + Assert.AreEqual(3, found.Total); + Assert.AreEqual(expected, found.Items.Select(x => x.EmployeeId).ToArray()); + + filter.SortDescending = true; + found = await _repository.GetPagedFilteredActivities(filter); + expected = new[] { "3", "2", "1" }; + Assert.NotNull(found); + Assert.IsNotEmpty(found.Items); + Assert.AreEqual(3, found.Items.Count()); + Assert.AreEqual(3, found.Total); + Assert.AreEqual(expected, found.Items.Select(x => x.EmployeeId).ToArray()); + + filter.SortDescending = false; + filter.SortBy = null; + found = await _repository.GetPagedFilteredActivities(filter); + expected = new[] { "1", "3", "2" }; + Assert.NotNull(found); + Assert.IsNotEmpty(found.Items); + Assert.AreEqual(3, found.Items.Count()); + Assert.AreEqual(3, found.Total); + Assert.AreEqual(expected, found.Items.Select(x => x.EmployeeId).ToArray()); + } + + [Test] + public async Task FindUnmatchedReferrals_Basic() + { + var activity1 = new Activity + { + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Referral, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-3), + SourceId = "1", + State = ActivityState.PendingReferral, + Detail = new ActivityDetail + { + MemberName = "John Doe" + }, + MetaData = new List { new ActivityMetaData { MetaData = "1234567890", MetaDataType = MetaDataType.Phone } } + }; + await _repository.SaveActivityAsync(activity1); + var activity2 = new Activity + { + DepartmentId = 56, + EmployeeId = "2", + Type = ActivityType.Referral, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-2), + SourceId = "2", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + SubjectPersonNbr = "1Jane3Doe" + } + }; + await _repository.SaveActivityAsync(activity2); + var activity3 = new Activity + { + DepartmentId = 55, + EmployeeId = "3", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-1), + SourceId = "3", + State = ActivityState.Rejected, + Detail = new ActivityDetail + { + AccountNumber = "1234" + }, + MetaData = new List { new ActivityMetaData { MetaData = "1234567890", MetaDataType = MetaDataType.Phone } } + }; + await _repository.SaveActivityAsync(activity3); + var activity4 = new Activity + { + DepartmentId = 55, + EmployeeId = "4", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + SourceId = "4", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "12DOE35" + } + }; + await _repository.SaveActivityAsync(activity4); + await _context.SaveChangesAsync(); + + var found = await _repository.FindUnmatchedReferrals(); + Assert.NotNull(found); + Assert.IsNotEmpty(found); + Assert.AreEqual(1, found.Count); + } + + [Test] + public async Task FindMatchedSaleForReferral_Basic() + { + var activity1 = new Activity + { + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Referral, + Product = Product.CheckCard, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceId = "1", + State = ActivityState.PendingReferral, + Detail = new ActivityDetail + { + MemberName = "John Doe", + SubjectPersonNbr = "12345", + SubjectOrgNbr = "123" + } + }; + await _repository.SaveActivityAsync(activity1); + var activity2 = new Activity + { + DepartmentId = 56, + EmployeeId = "2", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + SourceCreated = DateTime.Today, + SourceId = "2", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + SubjectPersonNbr = "12345", + SubjectOrgNbr = "123" + } + }; + await _repository.SaveActivityAsync(activity2); + var activity3 = new Activity + { + DepartmentId = 55, + EmployeeId = "3", + Type = ActivityType.Sale, + Product = Product.CheckCard, + SourceCreated = DateTime.Today, + Accepted = DateTime.Today, + SourceId = "3", + State = ActivityState.Rejected, + Detail = new ActivityDetail + { + AccountNumber = "1234565", + SubjectPersonNbr = "1223423", + SubjectOrgNbr = "122343" + } + }; + await _repository.SaveActivityAsync(activity3); + var activity4 = new Activity + { + DepartmentId = 55, + EmployeeId = "4", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + SourceCreated = DateTime.Today, + SourceId = "4", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "12DOE35", + SubjectPersonNbr = "123234245", + SubjectOrgNbr = "122223" + } + }; + await _repository.SaveActivityAsync(activity4); + await _context.SaveChangesAsync(); + + var found = await _repository.FindUnmatchedSaleForReferral(activity1); + Assert.NotNull(found); + Assert.AreEqual(activity2, found); + } + + [TestCase("1234567", MetaDataType.Phone)] + [TestCase("user@mymail.com", MetaDataType.Email)] + [Test] + public async Task FindMatchedSaleForReferral_MatchOnMetaData_Basic(string metaDataValue, MetaDataType metaDataType) + { + var activity1 = new Activity + { + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Referral, + Product = Product.FirstMortgageReferral, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceId = "1", + State = ActivityState.PendingReferral, + Detail = new ActivityDetail + { + MemberName = "John Doe", + SubjectPersonNbr = "", // null or empty personNbr and orgNbr trigger match on metadata + SubjectOrgNbr = "" + }, + MetaData = new List + { + new ActivityMetaData() + { + Id = 1, + MetaData = metaDataValue, + MetaDataType = metaDataType + } + } + }; + await _repository.SaveActivityAsync(activity1); + var activity2 = new Activity + { + DepartmentId = 56, + EmployeeId = "2", + Type = ActivityType.Sale, + Product = Product.FirstMortgageReferral, + Accepted = DateTime.Today, + SourceCreated = DateTime.Today, + SourceId = "2", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + SubjectPersonNbr = "", + SubjectOrgNbr = "" + }, + MetaData = new List + { + new ActivityMetaData() + { + Id = 2, + MetaData = metaDataValue, + MetaDataType = metaDataType + } + } + }; + await _repository.SaveActivityAsync(activity2); + var activity3 = new Activity + { + DepartmentId = 55, + EmployeeId = "3", + Type = ActivityType.Sale, + Product = Product.FirstMortgageReferral, + SourceCreated = DateTime.Today, + Accepted = DateTime.Today, + SourceId = "3", + State = ActivityState.Rejected, // rejected state excludes activity from potentially matching + Detail = new ActivityDetail + { + AccountNumber = "1234565", + SubjectPersonNbr = null, + SubjectOrgNbr = null + }, + MetaData = new List() + { + new ActivityMetaData() + { + Id = 3, + MetaData = "user@user.com", + MetaDataType = MetaDataType.Email + } + } + }; + await _repository.SaveActivityAsync(activity3); + var activity4 = new Activity + { + DepartmentId = 55, + EmployeeId = "4", + Type = ActivityType.Sale, + Product = Product.FirstMortgageReferral, + Accepted = DateTime.Today, + SourceCreated = DateTime.Today, + SourceId = "4", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "12DOE35", + SubjectPersonNbr = null, + SubjectOrgNbr = null + }, + MetaData = new List() { } + }; + await _repository.SaveActivityAsync(activity4); + await _context.SaveChangesAsync(); + + var found = await _repository.FindUnmatchedSaleForReferral(activity1); + Assert.NotNull(found); + Assert.AreEqual(activity2, found); + } + + [Test] + public async Task FindMatchedSaleForReferral_AllSalesAlreadyAssociatedToStateChange_NoReturn() + { + var activity1 = new Activity + { + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Referral, + Product = Product.CheckCard, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceId = "1", + State = ActivityState.PendingReferral, + Detail = new ActivityDetail + { + MemberName = "John Doe", + SubjectPersonNbr = "12345", + SubjectOrgNbr = "123" + } + }; + + await _repository.SaveActivityAsync(activity1); + + var activity2 = new Activity + { + DepartmentId = 56, + EmployeeId = "2", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + SourceCreated = DateTime.Today, + SourceId = "2", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + SubjectPersonNbr = "12345", + SubjectOrgNbr = "123" + } + }; + await _repository.SaveActivityAsync(activity2); + var change2 = new ActivityStateChange + { + ActivityId = 2, + State = ActivityState.Accepted, + ActivityMatchId = activity2.Id + }; + await _repositoryActivityChange.SaveActivityStateChange(change2); + var activity3 = new Activity + { + DepartmentId = 55, + EmployeeId = "3", + Type = ActivityType.Sale, + Product = Product.CheckCard, + SourceCreated = DateTime.Today, + Accepted = DateTime.Today, + SourceId = "3", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "1234565", + SubjectPersonNbr = "1223423", + SubjectOrgNbr = "122343" + } + }; + await _repository.SaveActivityAsync(activity3); + var change3 = new ActivityStateChange + { + ActivityId = 3, + State = ActivityState.Accepted, + ActivityMatchId = activity3.Id + }; + await _repositoryActivityChange.SaveActivityStateChange(change3); + var activity4 = new Activity + { + DepartmentId = 55, + EmployeeId = "4", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + SourceCreated = DateTime.Today, + SourceId = "4", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "12DOE35", + SubjectPersonNbr = "123234245", + SubjectOrgNbr = "122223" + } + }; + await _repository.SaveActivityAsync(activity4); + var change4 = new ActivityStateChange + { + ActivityId = 4, + State = ActivityState.Accepted, + ActivityMatchId = activity4.Id + }; + await _repositoryActivityChange.SaveActivityStateChange(change4); + await _context.SaveChangesAsync(); + + var found = await _repository.FindUnmatchedSaleForReferral(activity1); + Assert.IsNull(found); + } + + [Test] + public async Task FindMatchedSaleForReferral_NoUnmatchedActivities_ReturnNull() + { + var activity1 = new Activity + { + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Referral, + Product = Product.CheckCard, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceId = "1", + State = ActivityState.PendingReferral, + Detail = new ActivityDetail + { + MemberName = "John Doe", + SubjectPersonNbr = "12345", + SubjectOrgNbr = "123" + }, + MetaData = new List + { + new ActivityMetaData { MetaData = "1234567890", MetaDataType = MetaDataType.Phone }, + new ActivityMetaData { MetaData = "TestTesterson@testerson.com", MetaDataType = MetaDataType.Email } + } + }; + + await _repository.SaveActivityAsync(activity1); + await _context.SaveChangesAsync(); + + var found = await _repository.FindUnmatchedSaleForReferral(activity1); + Assert.IsNull(found); + } + + [Test] + public async Task FindMatchedSaleForReferral_SameDay_NotSameTime() + { + var referral = new Activity + { + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Referral, + Product = Product.CheckCard, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceId = "1", + State = ActivityState.PendingReferral, + Detail = new ActivityDetail + { + MemberName = "John Doe", + SubjectPersonNbr = "12345", + SubjectOrgNbr = "123" + } + }; + + var sale = new Activity + { + DepartmentId = 56, + EmployeeId = "2", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceId = "2", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + SubjectPersonNbr = "12345", + SubjectOrgNbr = "123" + } + }; + + // Just make sure that the referral can happen after the sale, but the same day. + referral.SourceCreated = referral.SourceCreated.AddHours(12); + sale.SourceCreated = sale.SourceCreated.AddHours(2); + Assert.AreNotEqual(referral.SourceCreated, sale.SourceCreated); + Assert.AreEqual(referral.SourceCreated.Date, sale.SourceCreated.Date); + Assert.True(referral.SourceCreated > sale.SourceCreated); + await _repository.SaveActivityAsync(referral); + await _repository.SaveActivityAsync(sale); + await _context.SaveChangesAsync(); + + var found = await _repository.FindUnmatchedSaleForReferral(referral); + Assert.NotNull(found); + Assert.AreEqual(sale, found); + } + + [Test] + public async Task Duplicate_Detection_Basic_Sale_Product_Default() + { + var activity1 = new Activity + { + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceId = "1", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + MemberName = "John Doe", + SubjectPersonNbr = "12345", + SubjectOrgNbr = "123" + } + }; + await _repository.SaveActivityAsync(activity1); + await _context.SaveChangesAsync(); + var activity2 = new Activity + { + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + MemberName = "John Doe", + SubjectPersonNbr = "12345", + SubjectOrgNbr = "123" + } + }; + + // They are complete duplicates. + var found = await _repository.FindDuplicateSaleAsync(activity2); + Assert.NotNull(found); + Assert.AreEqual(activity1, found); + + // Change only source ID + Random r = new Random(); + activity2.SourceId = r.Next(10000, 50000) + "_SCID"; + activity2.Detail.SubjectPersonNbr = activity1.Detail.SubjectPersonNbr; + activity2.Detail.SubjectOrgNbr = activity1.Detail.SubjectOrgNbr; + found = await _repository.FindDuplicateSaleAsync(activity2); + Assert.Null(found); + + // Change SubjectPersonNbr Only. + activity2.SourceId = null; + activity2.Detail.SubjectPersonNbr = "12234333"; + activity2.Detail.SubjectOrgNbr = activity1.Detail.SubjectOrgNbr; + found = await _repository.FindDuplicateSaleAsync(activity2); + Assert.Null(found); + + // Change SubjectOrgNbr Only. + activity2.SourceId = null; + activity2.Detail.SubjectPersonNbr = activity1.Detail.SubjectPersonNbr; + activity2.Detail.SubjectOrgNbr = "3112333"; + found = await _repository.FindDuplicateSaleAsync(activity2); + Assert.Null(found); + } + + [Test] + public async Task FindActivityForStateUpdate_Default() + { + var existingActivity = new Activity + { + Id = 1, + DepartmentId = 55, + EmployeeId = "11774", + Type = ActivityType.Sale, + Product = Product.GAPInsurance, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceId = "1", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "867530986", + MemberName = "ORLANDO TESTCUSTOMER10" + } + }; + await _repository.SaveActivityAsync(existingActivity); + await _context.SaveChangesAsync(); + var updateStateActivity = new Activity + { + DepartmentId = 55, + EmployeeId = "11774", + Type = ActivityType.Sale, + Product = Product.GAPInsurance, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + State = ActivityState.Rejected, + Detail = new ActivityDetail + { + AccountNumber = "867530986", + MemberName = "ORLANDO TESTCUSTOMER10" + } + }; + + // They are complete duplicates. + var found = await _repository.FindActivityForStateUpdate(updateStateActivity); + Assert.NotNull(found); + Assert.AreEqual(existingActivity, found); + } + + [Test] + public async Task FindActivityForDetailUpdate_Default() + { + var existingActivity = new Activity + { + Id = 1, + DepartmentId = 55, + EmployeeId = "11774", + Type = ActivityType.Sale, + Product = Product.GAPInsurance, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceId = "1", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "867530986", + MemberName = "ORLANDO TESTCUSTOMER10", + SubAccountNumber = "8675309" + } + }; + await _repository.SaveActivityAsync(existingActivity); + await _context.SaveChangesAsync(); + var queryActivity = new Activity + { + DepartmentId = 55, + EmployeeId = "11774", + Type = ActivityType.Sale, + Product = Product.GAPInsurance, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + State = ActivityState.Rejected, + Detail = new ActivityDetail + { + AccountNumber = "867530986", + MemberName = "ORLANDO TESTCUSTOMER10", + SubAccountNumber = "8675309" + } + }; + + var found = await _repository.FindActivityForDetailUpdate(queryActivity); + Assert.NotNull(found); + Assert.AreEqual(existingActivity, found); + } + + [Test] + public async Task FindActivityForDetailUpdate_FallsBackToNotMatchOnSubAccountNumberAfterFindingNothing() + { + var existingActivity = new Activity + { + Id = 1, + DepartmentId = 55, + EmployeeId = "11774", + Type = ActivityType.Sale, + Product = Product.GAPInsurance, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceId = "1", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "867530986", + MemberName = "ORLANDO TESTCUSTOMER10" + } + }; + await _repository.SaveActivityAsync(existingActivity); + await _context.SaveChangesAsync(); + var queryActivity = new Activity + { + DepartmentId = 55, + EmployeeId = "11774", + Type = ActivityType.Sale, + Product = Product.GAPInsurance, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + State = ActivityState.Rejected, + Detail = new ActivityDetail + { + AccountNumber = "867530986", + MemberName = "ORLANDO TESTCUSTOMER10", + SubAccountNumber = "8675309" + } + }; + + var found = await _repository.FindActivityForDetailUpdate(queryActivity); + Assert.NotNull(found); + Assert.AreEqual(existingActivity, found); + } + + [Test] + public async Task Duplicate_Detection_Basic_Referral_Product_Default() + { + var activity1 = new Activity + { + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Referral, + Product = Product.CheckCard, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceId = "1", + State = ActivityState.PendingReferral, + Detail = new ActivityDetail + { + MemberName = "John Doe", + SubjectPersonNbr = "12345", + SubjectOrgNbr = "123" + } + }; + await _repository.SaveActivityAsync(activity1); + await _context.SaveChangesAsync(); + var activity2 = new Activity + { + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Referral, + Product = Product.CheckCard, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + State = ActivityState.PendingReferral, + Detail = new ActivityDetail + { + MemberName = "John Doe", + SubjectPersonNbr = "12345", + SubjectOrgNbr = "123" + } + }; + + // They are complete duplicates. + var found = await _repository.FindDuplicateReferralAsync(activity2); + Assert.NotNull(found); + Assert.AreEqual(activity1, found); + + // Change only source ID, Referrals do not care + Random r = new Random(); + activity2.SourceId = r.Next(10000, 50000) + "_SCID"; + activity2.Detail.SubjectPersonNbr = activity1.Detail.SubjectPersonNbr; + activity2.Detail.SubjectOrgNbr = activity1.Detail.SubjectOrgNbr; + found = await _repository.FindDuplicateReferralAsync(activity2); + Assert.NotNull(found); + Assert.AreEqual(activity1, found); + + // Change SubjectPersonNbr Only. + activity2.SourceId = null; + activity2.Detail.SubjectPersonNbr = "12234333"; + activity2.Detail.SubjectOrgNbr = activity1.Detail.SubjectOrgNbr; + found = await _repository.FindDuplicateReferralAsync(activity2); + Assert.Null(found); + + // Change SubjectOrgNbr Only. + activity2.SourceId = null; + activity2.Detail.SubjectPersonNbr = activity1.Detail.SubjectPersonNbr; + activity2.Detail.SubjectOrgNbr = "3112333"; + found = await _repository.FindDuplicateReferralAsync(activity2); + Assert.Null(found); + } + + [Test] + public async Task FindDepartmentActivityByMonthRange_Basic() + { + var activityInRange = new Activity + { + DepartmentId = 1, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = new DateTime(2021, 7, 3), + SourceId = "1", + State = ActivityState.Accepted + }; + await _repository.SaveActivityAsync(activityInRange); + var activityNotInRange = new Activity + { + DepartmentId = 1, + EmployeeId = "2", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = new DateTime(2021, 6, 3), + SourceId = "2", + State = ActivityState.Accepted + }; + await _repository.SaveActivityAsync(activityNotInRange); + var activityForOtherDepartment = new Activity + { + DepartmentId = 2, + EmployeeId = "3", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = new DateTime(2021, 7, 3), + SourceId = "3", + State = ActivityState.Accepted + }; + await _repository.SaveActivityAsync(activityForOtherDepartment); + await _context.SaveChangesAsync(); + + var range = new MonthRange(2021, 7, 2021, 8); + var found = await _repository.FindDepartmentActivityByMonthRange(1, range); + + Assert.AreEqual(1, found.Count()); + Assert.AreEqual("1", found.First().EmployeeId); + } + + [Test] + public async Task GetActivityIncludingPayouts_Basic() + { + var department = new Department() + { + DepartmentName = "Dept1", + Id = 55, + State = DepartmentState.Active + }; + + var user = new AppUser + { + AdEmail = "AbrahamTestAssociate1@test.com", + EmployeeId = "11771", + FullName = "Abraham TestAssociate1", + Departments = new List + { + new UserDepartment() + { + Id = 99, + Department = department, + Employee = null, + Role = Role.BM, + IsHome = true, + StartDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1), + EndDate = null + } + } + }; + + var activityWithPayouts = new Activity + { + Id = 11, + DepartmentId = 1, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = new DateTime(2021, 7, 3), + SourceId = "1", + State = ActivityState.Accepted, + Detail = new ActivityDetail { Id = 22 }, + ActivityPayouts = new List + { + new ActivityPayoutLineItem + { + ActivityId = 11, + PayoutLineItemId = 33, + PayoutLineItem = new PayoutLineItem { Id = 33, EmployeeId = "1", Role = Role.BM } + } + } + }; + + await _repository.SaveActivityAsync(activityWithPayouts); + await _context.SaveChangesAsync(); + + var found = await _repository.GetActivityIncludingPayouts(11); + + Assert.NotNull(found); + Assert.AreEqual(11, found.Id); + Assert.AreEqual(22, found.Detail.Id); + Assert.AreEqual(11, found.ActivityPayouts.First().ActivityId); + Assert.AreEqual(33, found.ActivityPayouts.First().PayoutLineItemId); + Assert.AreEqual(33, found.ActivityPayouts.First().PayoutLineItem.Id); + } + + [Test] + public async Task FindGapInsuranceEligible() + { + var sourceId = CreateId(); + var accepted = DateTime.UtcNow; + + var activities = new List() + { + new() // Eligible + { + EmployeeId = CreateId(), + SourceId = sourceId, + Accepted = accepted, + Detail = new ActivityDetail + { + IsGapInsuranceEligible = true + } + }, + new() // Detail is null + { + EmployeeId = CreateId(), + SourceId = sourceId, + Accepted = accepted + }, + new() // Accepted is not in range + { + EmployeeId = CreateId(), + SourceId = sourceId, + Accepted = accepted.AddMonths(-1), + Detail = new ActivityDetail + { + IsGapInsuranceEligible = true + } + }, + new() // Is not GAP insurance eligible + { + EmployeeId = CreateId(), + SourceId = sourceId, + Accepted= accepted, + Detail = new ActivityDetail + { + IsGapInsuranceEligible = false + } + } + }; + + await _repository.SaveActivityRangeAsync(new List(activities)); + await _context.SaveChangesAsync(); + var eligibles = await _repository.FindGapInsuranceEligible(new MonthRange(accepted, accepted)); + Assert.AreEqual(1, eligibles.Count); + } + + [Test] + public async Task PropertyChangesIsGapEligible_VehicleProduct() + { + var sourceId = CreateId(); + var accepted = DateTime.UtcNow; + + var activities = new List() + { + new() // Eligible + { + EmployeeId = CreateId(), + SourceId = sourceId, + Accepted = accepted, + Detail = new ActivityDetail + { + Id = 3, + IsGapInsuranceEligible = true + } + }, + new() // Detail is null + { + EmployeeId = CreateId(), + SourceId = sourceId, + Accepted = accepted + }, + new() // Accepted is not in range + { + EmployeeId = CreateId(), + SourceId = sourceId, + Accepted = accepted.AddMonths(-1), + Detail = new ActivityDetail + { + Id = 1, + IsGapInsuranceEligible = true + } + }, + new() // Is not GAP insurance eligible + { + EmployeeId = CreateId(), + SourceId = sourceId, + Accepted= accepted, + Detail = new ActivityDetail + { + Id = 2, + IsGapInsuranceEligible = false + } + } + }; + + await _repository.SaveActivityRangeAsync(new List(activities)); + await _context.SaveChangesAsync(); + var eligibles = await _repository.FindGapInsuranceEligible(new MonthRange(accepted, accepted)); + Assert.AreEqual(true, eligibles[0].Detail.IsGapInsuranceEligible); + Assert.AreEqual(1, eligibles.Count); + } + + [Test] + public async Task FindLatestBySourceId() + { + var sourceId1 = CreateId(); + var sourceId2 = CreateId(); + var created = DateTime.UtcNow; + + var activities = new List() + { + new() // Latest Source ID 1 + { + EmployeeId = CreateId(), + SourceId = sourceId1, + Created = created + }, + new() + { + EmployeeId = CreateId(), + SourceId = sourceId1, + Created = created.AddDays(-1) + }, + new() // Latest Source ID 2 + { + EmployeeId = CreateId(), + SourceId = sourceId2, + Created = created + }, + new() + { + EmployeeId = CreateId(), + SourceId = sourceId2, + Created = created.AddDays(-1) + } + }; + + + await _repository.SaveActivityRangeAsync(new List(activities)); + await _context.SaveChangesAsync(); + + var latest = await _repository.FindLatestBySourceId(new List + { + sourceId1, + sourceId2 + }); + + Assert.AreEqual(2, latest.Count); + Assert.NotNull(latest.SingleOrDefault(activity => activity.EmployeeId == activities[0].EmployeeId)); + Assert.NotNull(latest.SingleOrDefault(activity => activity.EmployeeId == activities[2].EmployeeId)); + Assert.False((await _repository.FindLatestBySourceId(new List())).Any()); + } + + [Test] + public async Task FindById() + { + var activities = new List() + { + new() + { + EmployeeId = CreateId(), + SourceId = CreateId() + }, + new() + { + EmployeeId = CreateId(), + SourceId = CreateId() + } + }; + + + await _repository.SaveActivityRangeAsync(new List(activities)); + await _context.SaveChangesAsync(); + + var latest = await _repository.FindLatestBySourceId(new List + { + activities[0].SourceId, + activities[1].SourceId + }); + + Assert.AreEqual(2, latest.Count); + var found = await _repository.FindById(latest.Select(activity => activity.Id).ToList()); + Assert.AreEqual(2, found.Count); + Assert.NotNull(found.SingleOrDefault(activity => activity.Id == latest[0].Id)); + Assert.NotNull(found.SingleOrDefault(activity => activity.Id == latest[1].Id)); + Assert.False((await _repository.FindById(new List())).Any()); + } + + private static string CreateId() => Guid.NewGuid().ToString("D"); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/ActivityTransferActionRepositoryTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/ActivityTransferActionRepositoryTest.cs new file mode 100644 index 0000000..7d9bb8e --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/ActivityTransferActionRepositoryTest.cs @@ -0,0 +1,265 @@ +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Microsoft.EntityFrameworkCore; +using Moq; +using NUnit.Framework; +using System; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApiLib; + +namespace Application.UnitTests.Repository +{ + public class ActivityTransferActionRepositoryTest + { + private ApplicationDbContext _context; + private ActivityTransferActionRepository _repository; + private Mock _mockDateTime; + + [SetUp] + public void Setup() + { + _mockDateTime = new Mock(); + var mockCurrentUserService = Mock.Of(); + Mock.Get(mockCurrentUserService).Setup(s => s.GetCurrentUser()).Returns(new ApplicationUser() + { + Email = "JohnTest@test.com", + }); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .EnableSensitiveDataLogging(true) + .Options; + _context = new ApplicationDbContext(options, mockCurrentUserService, _mockDateTime.Object); + _repository = new ActivityTransferActionRepository(_context); + } + + [Test] + public async Task SaveTransferAsync_Basic() + { + _mockDateTime.SetupGet(mock => mock.Now).Returns(new DateTime(2021, 9, 10, 1, 2, 3, 456)); + + var activityToTransfer = new Activity() + { + Id = 2, + EmployeeId = "C0000", + DepartmentId = 5, + Product = Product.CheckCard, + SourceId = "2", + State = ActivityState.Accepted, + Detail = new ActivityDetail() + { + MemberName = "Tester 1", + AgreementNumber = "234" + } + }; + _context.Add(activityToTransfer); + await _context.SaveChangesAsync(); + + var activtyTransferAction = new ActivityTransferAction + { + Id = 0, + ActivityStateBeforeTransfer = ActivityState.Accepted, + RequestedActivityId = 2, + State = ActivityTransferState.Requested, + NewActivityDepartmentId = 3, + NewActivityEmployeeId = "C0000" + }; + + var savedId = await _repository.SaveTransferAsync(activtyTransferAction); + await _context.SaveChangesAsync(); + + var found = await _context.ActivityTransferActions.FindAsync(savedId); + + Assert.NotNull(found); + Assert.AreEqual(ActivityState.Accepted, found.ActivityStateBeforeTransfer); + Assert.AreEqual(2, found.RequestedActivityId); + Assert.AreEqual(ActivityTransferState.Requested, found.State); + Assert.AreEqual(3, found.NewActivityDepartmentId); + Assert.IsNull(found.NewActivityId); + Assert.AreEqual("C0000", found.NewActivityEmployeeId); + Assert.AreEqual(new DateTime(2021, 9, 10, 1, 2, 3, 456), found.Created); + Assert.AreEqual("JohnTest@test.com", found.CreatedBy); + Assert.AreEqual(UserType.Associate, found.CreatedByType); + Assert.AreEqual(new DateTime(2021, 9, 10, 1, 2, 3, 456), found.LastUpdated); + Assert.AreEqual("JohnTest@test.com", found.LastUpdatedBy); + Assert.AreEqual(UserType.Associate, found.LastUpdatedByType); + } + + [Test] + public async Task GetTransferActionById_Basic() + { + var activityToTransfer = new Activity() + { + Id = 2, + EmployeeId = "C0000", + DepartmentId = 5, + Product = Product.CheckCard, + SourceId = "2", + State = ActivityState.Accepted, + Detail = new ActivityDetail() + { + MemberName = "Tester 1", + AgreementNumber = "234" + } + }; + _context.Add(activityToTransfer); + await _context.SaveChangesAsync(); + + var activtyTransferAction1 = new ActivityTransferAction + { + Id = 1, + ActivityStateBeforeTransfer = ActivityState.Accepted, + RequestedActivityId = 2, + State = ActivityTransferState.Accepted, + NewActivityDepartmentId = 3, + NewActivityEmployeeId = "C0001" + }; + + var activtyTransferAction2 = new ActivityTransferAction + { + Id = 2, + ActivityStateBeforeTransfer = ActivityState.Rejected, + RequestedActivityId = 2, + State = ActivityTransferState.Requested, + NewActivityDepartmentId = 4, + NewActivityEmployeeId = "C0002" + }; + _context.Add(activtyTransferAction1); + _context.Add(activtyTransferAction2); + await _context.SaveChangesAsync(); + + var found = await _repository.GetTransferActionById(2); + + Assert.AreEqual(2, found.Id); + Assert.AreEqual(ActivityState.Rejected, found.ActivityStateBeforeTransfer); + Assert.AreEqual(2, found.RequestedActivityId); + Assert.AreEqual(ActivityTransferState.Requested, found.State); + Assert.AreEqual(4, found.NewActivityDepartmentId); + Assert.AreEqual("C0002", found.NewActivityEmployeeId); + } + + [Test] + public async Task GetRequestedTransferActionByActivityId_Basic() + { + var activityToTransfer = new Activity() + { + Id = 2, + EmployeeId = "C0000", + DepartmentId = 5, + Product = Product.CheckCard, + SourceId = "2", + State = ActivityState.Accepted, + Detail = new ActivityDetail() + { + MemberName = "Tester 1", + AgreementNumber = "234" + } + }; + _context.Add(activityToTransfer); + await _context.SaveChangesAsync(); + + var activtyTransferAction1 = new ActivityTransferAction + { + Id = 1, + ActivityStateBeforeTransfer = ActivityState.Accepted, + RequestedActivityId = 2, + State = ActivityTransferState.Accepted, + NewActivityDepartmentId = 3, + NewActivityEmployeeId = "C0001" + }; + + var activtyTransferAction2 = new ActivityTransferAction + { + Id = 2, + ActivityStateBeforeTransfer = ActivityState.Rejected, + RequestedActivityId = 2, + State = ActivityTransferState.Requested, + NewActivityDepartmentId = 4, + NewActivityEmployeeId = "C0002" + }; + _context.Add(activtyTransferAction1); + _context.Add(activtyTransferAction2); + await _context.SaveChangesAsync(); + + var found = await _repository.GetRequestedTransferActionByActivityId(2); + + Assert.AreEqual(2, found.Id); + Assert.AreEqual(ActivityState.Rejected, found.ActivityStateBeforeTransfer); + Assert.AreEqual(2, found.RequestedActivityId); + Assert.AreEqual(ActivityTransferState.Requested, found.State); + Assert.AreEqual(4, found.NewActivityDepartmentId); + Assert.AreEqual("C0002", found.NewActivityEmployeeId); + } + + [Test] + public async Task GetActiveTransfersByState_Basic() + { + var activityToTransfer = new Activity() + { + Id = 2, + EmployeeId = "C0000", + DepartmentId = 5, + Product = Product.CheckCard, + SourceId = "2", + State = ActivityState.Accepted, + Detail = new ActivityDetail() + { + MemberName = "Tester 1", + AgreementNumber = "234" + } + }; + _context.Add(activityToTransfer); + var activityToTransfer2 = new Activity() + { + Id = 3, + EmployeeId = "C0000", + DepartmentId = 5, + Product = Product.CheckCard, + SourceId = "3", + State = ActivityState.PendingReferral, + Detail = new ActivityDetail() + { + MemberName = "Tester 1", + AgreementNumber = "234" + } + }; + _context.Add(activityToTransfer); + _context.Add(activityToTransfer2); + await _context.SaveChangesAsync(); + + var activityTransferAction1 = new ActivityTransferAction + { + Id = 1, + ActivityStateBeforeTransfer = ActivityState.Accepted, + RequestedActivityId = 2, + State = ActivityTransferState.Requested, + NewActivityDepartmentId = 3, + NewActivityEmployeeId = "C0001", + RequestedActivity = activityToTransfer + }; + + var activityTransferAction2 = new ActivityTransferAction + { + Id = 2, + ActivityStateBeforeTransfer = ActivityState.PendingReferral, + RequestedActivityId = 3, + State = ActivityTransferState.Requested, + NewActivityDepartmentId = 4, + NewActivityEmployeeId = "C0002", + RequestedActivity = activityToTransfer2 + }; + _context.Add(activityTransferAction1); + _context.Add(activityTransferAction2); + await _context.SaveChangesAsync(); + + var found = await _repository.GetActiveTransfersForStateAsync(ActivityState.Accepted); + + Assert.AreEqual(1, found.Count()); + Assert.AreEqual(1, found.First().Id); + Assert.AreEqual(ActivityState.Accepted, found.First().ActivityStateBeforeTransfer); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/CommissionRepositoryTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/CommissionRepositoryTest.cs new file mode 100644 index 0000000..de9ada5 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/CommissionRepositoryTest.cs @@ -0,0 +1,191 @@ +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Microsoft.EntityFrameworkCore; +using Moq; +using NUnit.Framework; +using System; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +namespace Application.UnitTests.Repository +{ + public class CommissionRepositoryTest + { + private ApplicationDbContext _context; + private CommissionRepository _repository; + private Mock _mockDateTime; + + [SetUp] + public void Setup() + { + _mockDateTime = new Mock(); + var mockCurrentUserService = Mock.Of(); + Mock.Get(mockCurrentUserService).Setup(s => s.GetCurrentUser()).Returns(new ApplicationUser() + { + Email = "JohnTest@test.com", + }); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .EnableSensitiveDataLogging(true) + .Options; + _context = new ApplicationDbContext(options, mockCurrentUserService, _mockDateTime.Object); + _repository = new CommissionRepository(_context); + } + + [Test] + public async Task FindExistingCommission_FoundById() + { + var commission = new Commission + { + Id = 1, + Product = Product.NewChecking, + RatePerProduct = 2, + Role = Role.Teller + }; + await _repository.SaveCommission(commission); + await _context.SaveChangesAsync(); + + var found = await _repository.FindExistingCommission(1); + + Assert.NotNull(found); + Assert.AreEqual(1, found.Id); + } + + [Test] + public async Task FindExistingCommission_FoundByRole() + { + var commission = new Commission + { + Id = 1, + Product = Product.NewChecking, + RatePerProduct = 2, + Role = Role.Teller + }; + await _repository.SaveCommission(commission); + await _context.SaveChangesAsync(); + + var found = await _repository.FindProductCommissions(Role.Teller); + + Assert.NotNull(found); + Assert.IsNotEmpty(found); + Assert.AreEqual(1, found.First().Id); + Assert.AreEqual(Role.Teller, found.First().Role); + } + + [Test] + public async Task FindExistingCommission_NotFoundById() + { + var commission = new Commission + { + Id = 1, + Product = Product.NewChecking, + RatePerProduct = 2, + Role = Role.Teller + }; + await _repository.SaveCommission(commission); + await _context.SaveChangesAsync(); + + var found = await _repository.FindExistingCommission(2); + + Assert.Null(found); + } + + [Test] + public async Task FindExistingCommission_FoundByRoleAndProduct() + { + var commission = new Commission + { + Id = 1, + Product = Product.NewChecking, + RatePerProduct = 2, + Role = Role.Teller + }; + await _repository.SaveCommission(commission); + await _context.SaveChangesAsync(); + + var found = await _repository.FindExistingCommission(Role.Teller, Product.NewChecking); + + Assert.NotNull(found); + Assert.AreEqual(Role.Teller, found.Role); + Assert.AreEqual(Product.NewChecking, found.Product); + } + + [Test] + public async Task FindExistingCommission_NotFoundByRoleAndProduct() + { + var commission = new Commission + { + Id = 1, + Product = Product.NewChecking, + RatePerProduct = 2, + Role = Role.Teller + }; + await _repository.SaveCommission(commission); + await _context.SaveChangesAsync(); + + var found = await _repository.FindExistingCommission(Role.Admin, Product.NewChecking); + + Assert.Null(found); + } + + [Test] + public async Task SaveCommission_Basic() + { + _mockDateTime.SetupGet(mock => mock.Now).Returns(new DateTime(2021, 7, 9, 1, 2, 3, 456)); + var commission = new Commission + { + Id = 1, + Product = Product.ATMCard, + RatePerProduct = 3, + Role = Role.BM + }; + await _repository.SaveCommission(commission); + await _context.SaveChangesAsync(); + + var found = await _repository.FindExistingCommission(1); + + Assert.NotNull(found); + Assert.AreEqual(1, found.Id); + Assert.AreEqual(Product.ATMCard, found.Product); + Assert.AreEqual(3, found.RatePerProduct); + Assert.AreEqual(Role.BM, found.Role); + Assert.AreEqual(new DateTime(2021, 7, 9, 1, 2, 3, 456), found.Created); + Assert.AreEqual("JohnTest@test.com", found.CreatedBy); + Assert.AreEqual(UserType.Associate, found.CreatedByType); + Assert.AreEqual(new DateTime(2021, 7, 9, 1, 2, 3, 456), found.LastUpdated); + Assert.AreEqual("JohnTest@test.com", found.LastUpdatedBy); + Assert.AreEqual(UserType.Associate, found.LastUpdatedByType); + } + + [Test] + public async Task GetCommissionsAsync_Basic() + { + var commission1 = new Commission + { + Id = 1, + Product = Product.NewChecking, + RatePerProduct = 2, + Role = Role.Teller + }; + var commission2 = new Commission + { + Id = 2, + Product = Product.ATMCard, + RatePerProduct = 3, + Role = Role.BM + }; + await _repository.SaveCommission(commission1); + await _repository.SaveCommission(commission2); + await _context.SaveChangesAsync(); + + var found = await _repository.GetCommissionsAsync(); + + Assert.NotNull(found); + Assert.IsNotEmpty(found); + Assert.AreEqual(2, found.Count); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/DeadActivityRepositoryTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/DeadActivityRepositoryTest.cs new file mode 100644 index 0000000..0b38171 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/DeadActivityRepositoryTest.cs @@ -0,0 +1,714 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Microsoft.EntityFrameworkCore; +using Moq; +using NUnit.Framework; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace Application.UnitTests.Repository +{ + public class DeadActivityRepositoryTest + { + private ApplicationDbContext _context; + private DeadActivityRepository _repository; + + [SetUp] + public void Setup() + { + var mockDateTime = new DateTimeService(); + var mockCurrentUserService = Mock.Of(); + Mock.Get(mockCurrentUserService).Setup(s => s.GetCurrentUser()).Returns(new ApplicationUser + { + Email = "JohnTest@test.com", + }); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .EnableSensitiveDataLogging() + .Options; + _context = new ApplicationDbContext(options, mockCurrentUserService, mockDateTime); + _repository = new DeadActivityRepository(_context); + } + + [Test] + public async Task GetActivitiesAsync_Paginated_OK() + { + var activity1 = new DeadActivity(new Activity + { + Detail = new ActivityDetail(), + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + State = ActivityState.Accepted, + Created = DateTime.Today.Subtract(TimeSpan.FromDays(7)) + }, DeadActivityReason.MissingRequiredField, "Test"); + await _repository.SaveDeadActivity(activity1); + + var activity2 = new DeadActivity(new Activity + { + Detail = new ActivityDetail(), + DepartmentId = 56, + EmployeeId = "2", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + State = ActivityState.Accepted, + Created = DateTime.Today.Subtract(TimeSpan.FromDays(6)) + }, DeadActivityReason.MissingRequiredField, "Test"); + await _repository.SaveDeadActivity(activity2); + + var activity3 = new DeadActivity(new Activity + { + Detail = new ActivityDetail(), + DepartmentId = 55, + EmployeeId = "3", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + State = ActivityState.Rejected, + Created = DateTime.Today.Subtract(TimeSpan.FromDays(6)) + }, DeadActivityReason.MissingRequiredField, "Test"); + await _repository.SaveDeadActivity(activity3); + + var activity4 = new DeadActivity(new Activity + { + Detail = new ActivityDetail(), + DepartmentId = 55, + EmployeeId = "4", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + State = ActivityState.Created, + Created = DateTime.Today.Subtract(TimeSpan.FromDays(5)) + }, DeadActivityReason.MissingRequiredField, "Test"); + await _repository.SaveDeadActivity(activity4); + await _context.SaveChangesAsync(); + + + var found = await _repository.GetPagedDeadActivities(1, 3); + Assert.NotNull(found); + Assert.IsNotEmpty(found.Items); + Assert.AreEqual(3, found.Items.Count()); + Assert.AreEqual(4, found.Total); + Assert.True(found.Items.Any(x => x.EmployeeId == "4")); + Assert.False(found.Items.Any(x => x.EmployeeId == "1")); + Assert.True(found.Items.Any(x => x.EmployeeId == "2")); + Assert.True(found.Items.Any(x => x.EmployeeId == "3")); + + found = await _repository.GetPagedDeadActivities(2, 3); + Assert.AreEqual(1, found.Items.Count()); + Assert.AreEqual(4, found.Total); + Assert.False(found.Items.Any(x => x.EmployeeId == "4")); + Assert.True(found.Items.Any(x => x.EmployeeId == "1")); + Assert.False(found.Items.Any(x => x.EmployeeId == "2")); + Assert.False(found.Items.Any(x => x.EmployeeId == "3")); + + found = await _repository.GetPagedDeadActivities(100, 3); + Assert.AreEqual(4, found.Total); + Assert.IsEmpty(found.Items); + } + + [Test] + public async Task GetDeadActivitiesByActivityProductAsync_OK() + { + var activity1 = new DeadActivity(new Activity + { + Detail = new ActivityDetail(), + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-3), + State = ActivityState.Accepted, + LastUpdated = new DateTime(2021, 1, 4) + }, DeadActivityReason.MissingRequiredField, "Test"); + + await _repository.SaveDeadActivity(activity1); + var activity2 = new DeadActivity(new Activity + { + Detail = new ActivityDetail(), + DepartmentId = 56, + EmployeeId = "2", + Type = ActivityType.Sale, + Product = Product.NewChecking, + Accepted = DateTime.Today.AddMinutes(-2), + State = ActivityState.Accepted, + LastUpdated = new DateTime(2021, 1, 3) + }, DeadActivityReason.MissingRequiredField, "Test"); + await _repository.SaveDeadActivity(activity2); + + var activity3 = new DeadActivity(new Activity + { + Detail = new ActivityDetail(), + DepartmentId = 55, + EmployeeId = "3", + Type = ActivityType.Sale, + Product = Product.NewChecking, + Accepted = DateTime.Today.AddMinutes(-1), + State = ActivityState.Rejected, + LastUpdated = new DateTime(2021, 1, 2) + }, DeadActivityReason.MissingRequiredField, "Test"); + await _repository.SaveDeadActivity(activity3); + + var activity4 = new DeadActivity(new Activity + { + Detail = new ActivityDetail(), + DepartmentId = 55, + EmployeeId = "4", + Type = ActivityType.Sale, + Product = Product.NewChecking, + Accepted = DateTime.Today, + State = ActivityState.Accepted, + LastUpdated = new DateTime(2021, 1, 1) + }, DeadActivityReason.MissingRequiredField, "Test"); + await _repository.SaveDeadActivity(activity4); + await _context.SaveChangesAsync(); + + var filter = new DeadActivityFilter + { + Page = 1, + Limit = 2, + Product = new List { Product.CheckCard } + }; + var found = await _repository.GetFilteredDeadActivities(filter); + Assert.NotNull(found); + Assert.IsNotEmpty(found.Items); + Assert.AreEqual(1, found.Items.Count()); + Assert.AreEqual(1, found.Total); + Assert.True(found.Items.Any(x => x.EmployeeId == "1")); + } + + [Test] + public async Task GetDeadActivitiesByActivityTypeAsync_OK() + { + var activity1 = new DeadActivity(new Activity + { + Detail = new ActivityDetail(), + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-3), + State = ActivityState.Accepted, + LastUpdated = new DateTime(2021, 1, 4) + }, DeadActivityReason.MissingRequiredField, "Test"); + await _repository.SaveDeadActivity(activity1); + + var activity2 = new DeadActivity(new Activity + { + Detail = new ActivityDetail(), + DepartmentId = 56, + EmployeeId = "2", + Type = ActivityType.Referral, + Product = Product.NewChecking, + Accepted = DateTime.Today.AddMinutes(-2), + State = ActivityState.Accepted, + LastUpdated = new DateTime(2021, 1, 3) + }, DeadActivityReason.MissingRequiredField, "Test"); + await _repository.SaveDeadActivity(activity2); + + var activity3 = new DeadActivity(new Activity + { + Detail = new ActivityDetail(), + DepartmentId = 55, + EmployeeId = "3", + Type = ActivityType.Referral, + Product = Product.NewChecking, + Accepted = DateTime.Today.AddMinutes(-1), + State = ActivityState.Rejected, + LastUpdated = new DateTime(2021, 1, 2) + }, DeadActivityReason.MissingRequiredField, "Test"); + await _repository.SaveDeadActivity(activity3); + + var activity4 = new DeadActivity(new Activity + { + Detail = new ActivityDetail(), + DepartmentId = 55, + EmployeeId = "4", + Type = ActivityType.Referral, + Product = Product.NewChecking, + Accepted = DateTime.Today, + State = ActivityState.Accepted, + LastUpdated = new DateTime(2021, 1, 1) + }, DeadActivityReason.MissingRequiredField, "Test"); + await _repository.SaveDeadActivity(activity4); + await _context.SaveChangesAsync(); + + var filter = new DeadActivityFilter + { + Page = 1, + Limit = 2, + Type = new List { ActivityType.Sale } + }; + var found = await _repository.GetFilteredDeadActivities(filter); + Assert.NotNull(found); + Assert.IsNotEmpty(found.Items); + Assert.AreEqual(1, found.Items.Count()); + Assert.AreEqual(1, found.Total); + Assert.True(found.Items.Any(x => x.EmployeeId == "1")); + } + + [Test] + public async Task GetFilteredDeadActivities_OK() + { + // You can't set last updated. So you'll get these back in reverse order you saved them. + var activity1 = new DeadActivity(new Activity + { + Detail = new ActivityDetail(), + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-5), + State = ActivityState.Accepted, + Created = DateTime.Today.Subtract(TimeSpan.FromDays(7)) + }, DeadActivityReason.MissingRequiredField, "Test"); + await _repository.SaveDeadActivity(activity1); + + var activity2 = new DeadActivity(new Activity + { + Detail = new ActivityDetail(), + DepartmentId = 56, + EmployeeId = "2", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-4), + State = ActivityState.Rejected, + Created = DateTime.Today.Subtract(TimeSpan.FromDays(6)) + }, DeadActivityReason.MissingRequiredField, "Test"); + await _repository.SaveDeadActivity(activity2); + + var activity3 = new DeadActivity(new Activity + { + Detail = new ActivityDetail(), + DepartmentId = 55, + EmployeeId = "3", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-3), + State = ActivityState.Accepted, + Created = DateTime.Today.Subtract(TimeSpan.FromDays(6)) + }, DeadActivityReason.MissingRequiredField, "Test"); + await _repository.SaveDeadActivity(activity3); + + var activity4 = new DeadActivity(new Activity + { + Detail = new ActivityDetail(), + DepartmentId = 55, + EmployeeId = "4", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-2), + State = ActivityState.Created, + Created = DateTime.Today.Subtract(TimeSpan.FromDays(5)) + }, DeadActivityReason.MissingRequiredField, "Test"); + await _repository.SaveDeadActivity(activity4); + + //null state + var activity5 = new DeadActivity(new Activity + { + Detail = new ActivityDetail(), + DepartmentId = 55, + EmployeeId = "5", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-1), + Created = DateTime.Today.Subtract(TimeSpan.FromDays(6)) + }, DeadActivityReason.MissingRequiredField, "Test"); + await _repository.SaveDeadActivity(activity5); + + var activity6 = new DeadActivity(new Activity + { + Detail = new ActivityDetail(), + DepartmentId = 56, + EmployeeId = "6", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + State = ActivityState.Accepted, + Created = DateTime.Today.Subtract(TimeSpan.FromDays(6)) + }, DeadActivityReason.MissingRequiredField, "Test"); + await _repository.SaveDeadActivity(activity6); + + await _context.SaveChangesAsync(); + + var filter = new DeadActivityFilter + { + Page = 1, + Limit = 2, + State = new List { ActivityState.Accepted } + }; + var found = await _repository.GetFilteredDeadActivities(filter); + Assert.NotNull(found); + Assert.IsNotEmpty(found.Items); + Assert.AreEqual(2, found.Items.Count()); + Assert.AreEqual(3, found.Total); + Assert.True(found.Items.Any(x => x.EmployeeId == "3")); + Assert.False(found.Items.Any(x => x.EmployeeId == "4")); // not accepted + Assert.False(found.Items.Any(x => x.EmployeeId == "1")); // not on this page + Assert.False(found.Items.Any(x => x.EmployeeId == "5")); // not accepted + Assert.True(found.Items.Any(x => x.EmployeeId == "6")); + Assert.False(found.Items.Any(x => x.EmployeeId == "2")); // not accepted + filter.Page = 2; + found = await _repository.GetFilteredDeadActivities(filter); + Assert.AreEqual(1, found.Items.Count()); + Assert.AreEqual(3, found.Total); + Assert.False(found.Items.Any(x => x.EmployeeId == "3")); + Assert.False(found.Items.Any(x => x.EmployeeId == "2")); + Assert.True(found.Items.Any(x => x.EmployeeId == "1")); + Assert.False(found.Items.Any(x => x.EmployeeId == "4")); + + var filter2 = new DeadActivityFilter + { + Page = 1, + Limit = 100, + State = new List { ActivityState.ManualReview } + }; + found = await _repository.GetFilteredDeadActivities(filter2); + Assert.AreEqual(0, found.Total); + Assert.IsEmpty(found.Items); + + filter2.Page = 20; + found = await _repository.GetFilteredDeadActivities(filter2); + Assert.AreEqual(0, found.Total); + Assert.IsEmpty(found.Items); + } + + [Test] + public async Task GetFilteredDeadActivities_More() + { + // You can't set last updated. So you'll get these back in reverse order you saved them. + var activity1 = new DeadActivity(new Activity + { + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-4), + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + MemberName = "John Doe" + } + }, DeadActivityReason.Other, "Test1"); + await _repository.SaveDeadActivity(activity1); + var activity2 = new DeadActivity(new Activity + { + DepartmentId = 56, + EmployeeId = "2", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-3), + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + SubjectPersonNbr = "1Jane3Doe" + } + }, DeadActivityReason.Other, "Test2"); + await _repository.SaveDeadActivity(activity2); + var activity3 = new DeadActivity(new Activity + { + DepartmentId = 55, + EmployeeId = "3", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-2), + State = ActivityState.Rejected, + Detail = new ActivityDetail + { + AccountNumber = "1234" + } + }, DeadActivityReason.Other, "Test3"); + await _repository.SaveDeadActivity(activity3); + var activity4 = new DeadActivity(new Activity + { + DepartmentId = 55, + EmployeeId = "4", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-1), + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "12DOE35" + } + }, DeadActivityReason.Other, "Test4"); + await _repository.SaveDeadActivity(activity4); + + //Everything in Query is null. should not match, should not error + var activity5 = new DeadActivity(new Activity + { + DepartmentId = 55, + EmployeeId = "4", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + State = ActivityState.Accepted, + Detail = new ActivityDetail() + }, DeadActivityReason.Other, "Test4"); + await _repository.SaveDeadActivity(activity5); + + await _context.SaveChangesAsync(); + + var filter = new DeadActivityFilter + { + Page = 1, + Limit = 2, + Query = "DoE" + }; + var found = await _repository.GetFilteredDeadActivities(filter); + Assert.NotNull(found); + Assert.IsNotEmpty(found.Items); + Assert.AreEqual(2, found.Items.Count()); + Assert.AreEqual(3, found.Total); + Assert.False(found.Items.Any(x => x.EmployeeId == "3")); + Assert.True(found.Items.Any(x => x.EmployeeId == "4")); + Assert.False(found.Items.Any(x => x.EmployeeId == "1")); // not on this page + Assert.True(found.Items.Any(x => x.EmployeeId == "2")); + + filter.Page = 2; + found = await _repository.GetFilteredDeadActivities(filter); + Assert.AreEqual(1, found.Items.Count()); + Assert.AreEqual(3, found.Total); + Assert.False(found.Items.Any(x => x.EmployeeId == "3")); + Assert.False(found.Items.Any(x => x.EmployeeId == "2")); + Assert.True(found.Items.Any(x => x.EmployeeId == "1")); + Assert.False(found.Items.Any(x => x.EmployeeId == "4")); + } + + [Test] + public async Task GetActivitiesByFilter_Employee_Department() + { + var activity1 = new DeadActivity(new Activity + { + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-4), + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + MemberName = "John Doe" + } + }, DeadActivityReason.Other, "Test1"); + await _repository.SaveDeadActivity(activity1); + var activity2 = new DeadActivity(new Activity + { + DepartmentId = 56, + EmployeeId = "2", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-3), + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + SubjectPersonNbr = "1Jane3Doe" + } + }, DeadActivityReason.Other, "Test2"); + await _repository.SaveDeadActivity(activity2); + var activity3 = new DeadActivity(new Activity + { + DepartmentId = 55, + EmployeeId = "3", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-2), + State = ActivityState.Rejected, + Detail = new ActivityDetail + { + AccountNumber = "1234" + } + }, DeadActivityReason.Other, "Test3"); + await _repository.SaveDeadActivity(activity3); + var activity4 = new DeadActivity(new Activity + { + DepartmentId = 55, + EmployeeId = "4", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-1), + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "12DOE35" + } + }, DeadActivityReason.Other, "Test4"); + await _repository.SaveDeadActivity(activity4); + + //Everything in Query is null. should not match, should not error + var activity5 = new DeadActivity(new Activity + { + EmployeeId = "4", + DepartmentId = -1, + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + State = ActivityState.Accepted, + Detail = new ActivityDetail() + }, DeadActivityReason.Other, "Test5"); + await _repository.SaveDeadActivity(activity5); + + await _context.SaveChangesAsync(); + + var filter = new DeadActivityFilter + { + Page = 1, + Limit = 2, + DepartmentId = 55 + }; + var found = await _repository.GetFilteredDeadActivities(filter); + Assert.NotNull(found); + Assert.IsNotEmpty(found.Items); + Assert.AreEqual(2, found.Items.Count()); + Assert.AreEqual(3, found.Total); + Assert.True(found.Items.Any(x => x.EmployeeId == "3")); + Assert.True(found.Items.Any(x => x.EmployeeId == "4")); + Assert.False(found.Items.Any(x => x.EmployeeId == "1")); // not on this page + Assert.False(found.Items.Any(x => x.EmployeeId == "2")); + + filter.EmployeeId = "4"; + found = await _repository.GetFilteredDeadActivities(filter); + Assert.AreEqual(1, found.Items.Count()); + Assert.AreEqual(1, found.Total); + Assert.False(found.Items.Any(x => x.EmployeeId == "3")); + Assert.False(found.Items.Any(x => x.EmployeeId == "2")); + Assert.False(found.Items.Any(x => x.EmployeeId == "1")); + Assert.True(found.Items.Any(x => x.EmployeeId == "4")); + + filter.DepartmentId = 99; + found = await _repository.GetFilteredDeadActivities(filter); + Assert.AreEqual(0, found.Items.Count()); + Assert.AreEqual(0, found.Total); + } + + [Test] + public async Task GetActivitiesByFilter_Sort() + { + var activity1 = new DeadActivity(new Activity + { + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-4), + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + MemberName = "John Doe" + } + }, DeadActivityReason.Other, "Test1"); + await _repository.SaveDeadActivity(activity1); + var activity2 = new DeadActivity(new Activity + { + DepartmentId = 56, + EmployeeId = "2", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-3), + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + SubjectPersonNbr = "1Jane3Doe" + } + }, DeadActivityReason.Other, "Test2"); + await _repository.SaveDeadActivity(activity2); + var activity3 = new DeadActivity(new Activity + { + DepartmentId = 55, + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-2), + State = ActivityState.Rejected, + Detail = new ActivityDetail + { + AccountNumber = "1234" + } + }, DeadActivityReason.Other, "Test3"); + await _repository.SaveDeadActivity(activity3); + var activity4 = new DeadActivity(new Activity + { + DepartmentId = 55, + EmployeeId = "4", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.AddMinutes(-1), + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "12DOE35" + } + }, DeadActivityReason.Other, "Test4"); + await _repository.SaveDeadActivity(activity4); + //Everything in Query is null. should not match, should not error + var activity5 = new DeadActivity(new Activity + { + DepartmentId = -1, + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + State = ActivityState.Accepted, + Detail = new ActivityDetail() + }, DeadActivityReason.Other, "Test5"); + await _repository.SaveDeadActivity(activity5); + await _context.SaveChangesAsync(); + //Everything in Query is null. should not match, should not error + var activity6 = new DeadActivity(new Activity + { + DepartmentId = -1, + EmployeeId = "6", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + State = ActivityState.Accepted, + Detail = new ActivityDetail() + }, DeadActivityReason.Other, "Test6"); + await _repository.SaveDeadActivity(activity6); + var filter = new DeadActivityFilter + { + Page = 1, + Limit = 99, + DepartmentId = 55, + SortBy = "EmployeeId" + }; + //NOTE: Null EmployeeId first + var found = await _repository.GetFilteredDeadActivities(filter); + var expected = new[] { "Test3", "Test1", "Test4" }; + Assert.NotNull(found); + Assert.IsNotEmpty(found.Items); + Assert.AreEqual(3, found.Items.Count()); + Assert.AreEqual(3, found.Total); + var ordered = found.Items.Select(x => x.ReasonNote).ToArray(); + Assert.AreEqual(expected, ordered); + + //NOTE: Null EmployeeId Last + filter.SortDescending = true; + found = await _repository.GetFilteredDeadActivities(filter); + expected = new[] { "Test4", "Test1", "Test3" }; + Assert.NotNull(found); + Assert.IsNotEmpty(found.Items); + Assert.AreEqual(3, found.Items.Count()); + Assert.AreEqual(3, found.Total); + ordered = found.Items.Select(x => x.ReasonNote).ToArray(); + Assert.AreEqual(expected, ordered); + + filter.SortDescending = false; + filter.SortBy = null; + found = await _repository.GetFilteredDeadActivities(filter); + expected = new[] { "Test4", "Test3", "Test1" }; + Assert.NotNull(found); + Assert.IsNotEmpty(found.Items); + Assert.AreEqual(3, found.Items.Count()); + Assert.AreEqual(3, found.Total); + ordered = found.Items.Select(x => x.ReasonNote).ToArray(); + Assert.AreEqual(expected, ordered); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/DepartmentRepositoryTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/DepartmentRepositoryTest.cs new file mode 100644 index 0000000..32eaba3 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/DepartmentRepositoryTest.cs @@ -0,0 +1,1154 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Results; +using Microsoft.EntityFrameworkCore; +using Moq; +using NUnit.Framework; + +namespace Application.UnitTests.Repository +{ + public class DepartmentRepositoryTest + { + private ApplicationDbContext _context; + private DepartmentRepository _repository; + private Mock _mockDateTime; + + [SetUp] + public void Setup() + { + _mockDateTime = new Mock(); + var mockCurrentUserService = Mock.Of(); + Mock.Get(mockCurrentUserService) + .Setup(s => s.GetCurrentUser()) + .Returns(new ApplicationUser() + { + Email = "JohnTest@test.com", + }); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .EnableSensitiveDataLogging(true) + .Options; + _context = new ApplicationDbContext(options, mockCurrentUserService, _mockDateTime.Object); + _repository = new DepartmentRepository(_context); + } + + [Test] + public async Task Basic_Flow_test() + { + var mockDate = new DateTime(2021, 8, 17, 1, 2, 3, 456); + _mockDateTime.SetupGet(mock => mock.Now).Returns(mockDate); + var saved = await _repository.GetAllDepartmentsAsync(); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + var newDepartment = new Department + { + DayforceDepartmentName = "Test", + DayForceGlCode = "1", + DnaOrgNbr = "1", + State = DepartmentState.Active, + DepartmentName = "TestDept1", + BusinessLineCode = BusinessLine.Operations.Code, + RegionCode = Region.North.Code + }; + + var newId = await _repository.SaveDepartmentAsync(newDepartment); + await _context.SaveChangesAsync(); + + saved = await _repository.GetAllDepartmentsAsync(); + Assert.NotNull(saved); + Assert.IsNotEmpty(saved); + var first = saved.First(); + Assert.AreEqual(DepartmentState.Active, first.State); + Assert.AreEqual("TestDept1", first.DepartmentName); + Assert.AreEqual(BusinessLine.Operations.Code, first.BusinessLineCode); + Assert.AreEqual(Region.North.Code, first.RegionCode); + // Make sure our Auditable is working. + Assert.True(first.Created.Equals(mockDate)); + Assert.AreEqual("JohnTest@test.com", first.CreatedBy); + Assert.AreEqual(UserType.Associate, first.CreatedByType); + Assert.True(first.LastUpdated.Equals(mockDate)); + Assert.AreEqual("JohnTest@test.com", first.LastUpdatedBy); + Assert.AreEqual(UserType.Associate, first.LastUpdatedByType); + } + + [Test] + public async Task Find_Or_Create_Existing_Department_Existing() + { + var allDepartments = await _repository.GetAllDepartmentsAsync(); + Assert.NotNull(allDepartments); + Assert.IsEmpty(allDepartments); + + var department = new Department() + { + DayforceDepartmentName = "Test", + DayForceGlCode = "1", + DnaOrgNbr = "1", + State = DepartmentState.Active, + DepartmentName = "Operations", + BusinessLineCode = BusinessLine.Operations.Code + }; + var rawData = new Dictionary(); + rawData["EmploymentStatus_ShortName"] = "Active"; + rawData["Employee_DisplayName"] = "Doe, Test"; + rawData["EmployeeEmploymentStatus_EmployeeNumber"] = "11771"; + rawData["IdLibrary_LoginName"] = "testdoe@landmarkcu.com"; + rawData["OrgUnit_ShortName"] = "Test1 - OPERATIONS"; + rawData["Job_ShortName"] = "TELLER II"; + rawData["Employee_HireDate"] = "2019-09-23T00:00:00.0000000"; + rawData["OrgUnit_LedgerCode"] = "100"; + var employee = new ActiveEmployeeResult(rawData); + var saved = await _repository.SaveDepartmentAsync(department); + Assert.NotNull(saved); + await _context.SaveChangesAsync(); + + var found = await _repository.FindOrCreateDepartment(employee); + await _context.SaveChangesAsync(); + Assert.NotNull(found); + + allDepartments = await _repository.GetAllDepartmentsAsync(); + Assert.NotNull(allDepartments); + Assert.AreEqual(1, allDepartments.Count); + } + + [Test] + public async Task Find_Or_Create_Existing_Department_Create() + { + var allDepartments = await _repository.GetAllDepartmentsAsync(); + Assert.NotNull(allDepartments); + Assert.IsEmpty(allDepartments); + + var department = new Department() + { + DayforceDepartmentName = "Test", + DayForceGlCode = "1", + DnaOrgNbr = "1", + State = DepartmentState.Active, + DepartmentName = "Big Department", + BusinessLineCode = BusinessLine.Operations.Code + }; + var rawData = new Dictionary(); + rawData["EmploymentStatus_ShortName"] = "Active"; + rawData["Employee_DisplayName"] = "Doe, Test"; + rawData["EmployeeEmploymentStatus_EmployeeNumber"] = "11771"; + rawData["IdLibrary_LoginName"] = "testdoe@landmarkcu.com"; + rawData["OrgUnit_ShortName"] = "HEADQUARTERS - OPERATIONS"; + rawData["Job_ShortName"] = "TELLER II"; + rawData["Employee_HireDate"] = "2019-09-23T00:00:00.0000000"; + rawData["OrgUnit_LedgerCode"] = "101"; + var employee = new ActiveEmployeeResult(rawData); + var saved = await _repository.SaveDepartmentAsync(department); + Assert.NotNull(saved); + await _context.SaveChangesAsync(); + + // This should cause the department to be created. + var found = await _repository.FindOrCreateDepartment(employee); + Assert.AreEqual("101", found.DayForceGlCode); + Assert.AreEqual("1101", found.DnaOrgNbr); + await _context.SaveChangesAsync(); + Assert.NotNull(found); + + allDepartments = await _repository.GetAllDepartmentsAsync(); + Assert.NotNull(allDepartments); + Assert.AreEqual(2, allDepartments.Count); + } + + [TestCase("HEADQUARTERS - SECURITY & FRAUD", "SECURITY & FRAUD")] + [TestCase("HEADQUARTERS - OPERATIONS", "OPERATIONS")] + [TestCase("headquarters - operations", "operations")] + [TestCase("GERMANTOWN - BRANCH", "GERMANTOWN")] + [TestCase("GERMANTOWN", "GERMANTOWN")] + [TestCase("NEW BERLIN - MORTGAGE ORIGINATION", "MORTGAGE ORIGINATION")] + [TestCase("WAUKESHA - LIC", "LIC")] + [TestCase(null, null)] + [TestCase(" ", null)] + public void DetermineDepartmentName(string rawName, string expectedResult) + { + Assert.AreEqual(DepartmentRepository.DetermineDepartmentName(rawName), expectedResult); + } + + [Test] + public async Task Find_Department_Assignments_Basic() + { + var department = new Department() + { + DayforceDepartmentName = "Test", + DayForceGlCode = "1", + DnaOrgNbr = "1", + State = DepartmentState.Active, + DepartmentName = "TestDept1", + BusinessLineCode = BusinessLine.Operations.Code + }; + var department2 = new Department() + { + DayforceDepartmentName = "Test", + DayForceGlCode = "1", + DnaOrgNbr = "1", + State = DepartmentState.Active, + DepartmentName = "TestDept2", + BusinessLineCode = BusinessLine.Operations.Code + }; + await _repository.SaveDepartmentAsync(department); + await _repository.SaveDepartmentAsync(department2); + + await _context.SaveChangesAsync(); + + var user1 = new AppUser() + { + Id = Guid.NewGuid(), + Role = Role.Admin, + FullName = "Test", + AdEmail = "test1@test.com", + BaseRole = "myrole", + HomeDepartment = "Test Dept", + EmployeeId = "123", + PersonId = 1 + }; + var user2 = new AppUser() + { + Id = Guid.NewGuid(), + Role = Role.Admin, + FullName = "Test", + AdEmail = "test2@test.com", + BaseRole = "myrole", + HomeDepartment = "Test Dept", + EmployeeId = "456", + PersonId = 1 + }; + var user3 = new AppUser() + { + Id = Guid.NewGuid(), + Role = Role.Admin, + FullName = "Test", + AdEmail = "test3@test.com", + BaseRole = "myrole", + HomeDepartment = "Test Dept", + EmployeeId = "789", + PersonId = 1 + }; + await _context.AppUsers.AddAsync(user1); + await _context.AppUsers.AddAsync(user2); + await _context.AppUsers.AddAsync(user3); + await _context.SaveChangesAsync(); + + var inThePast = new DateTime(2010, 12, 1); + var recent = new DateTime(2021, 10, 1); + + var assignment1 = new UserDepartment() + { + Department = department, + Employee = user1, + Note = "Default!", + IsHome = true, + StartDate = inThePast, + Role = Role.BM + }; + var assignment2 = new UserDepartment() + { + Department = department, + Employee = user2, + Note = "Default!", + IsHome = true, + StartDate = inThePast, + Role = Role.BM + }; + var assignment3 = new UserDepartment() + { + Department = department2, + Employee = user2, + Note = "Default!", + IsHome = true, + StartDate = inThePast, + Role = Role.BM + }; + _context.DepartmentAssignments.Add(assignment1); + _context.DepartmentAssignments.Add(assignment2); + _context.DepartmentAssignments.Add(assignment3); + await _context.SaveChangesAsync(); + + var query = new DepartmentPersonnelSearchRequest + { + DepartmentId = department.Id.Value, + Year = recent.Year, + Month = recent.Month + }; + + var found = await _repository.FindDepartmentAssignments(query); + Assert.NotNull(found); + Assert.AreEqual(2, found.Count); + + assignment1.Role = Role.MISSING_ROLE; + await _context.SaveChangesAsync(); + found = await _repository.FindDepartmentAssignments(query); + Assert.NotNull(found); + Assert.AreEqual(1, found.Count); + } + + [Test] + public async Task Find_Department_Assignments_NonMatch_OpenEnded() + { + var department = new Department() + { + DayforceDepartmentName = "Test", + DayForceGlCode = "1", + DnaOrgNbr = "1", + State = DepartmentState.Active, + DepartmentName = "TestDept1", + BusinessLineCode = BusinessLine.Operations.Code + }; + await _repository.SaveDepartmentAsync(department); + + await _context.SaveChangesAsync(); + + var user1 = new AppUser() + { + Id = Guid.NewGuid(), + FullName = "Test", + Role = Role.Admin, + AdEmail = "test1@test.com", + BaseRole = "myrole", + HomeDepartment = "Test Dept", + EmployeeId = "123", + PersonId = 1 + }; + var user2 = new AppUser() + { + Id = Guid.NewGuid(), + FullName = "Test", + Role = Role.Admin, + AdEmail = "test2@test.com", + BaseRole = "myrole", + HomeDepartment = "Test Dept", + EmployeeId = "456", + PersonId = 1 + }; + await _context.AppUsers.AddAsync(user1); + await _context.AppUsers.AddAsync(user2); + await _context.SaveChangesAsync(); + + var inThePast = DateTime.Today.Subtract(TimeSpan.FromDays(3)); + + var assignment1 = new UserDepartment() + { + Department = department, + Employee = user1, + Note = "Default!", + IsHome = true, + StartDate = inThePast, + Role = Role.BM + }; + var assignment2 = new UserDepartment() + { + Department = department, + Employee = user2, + Note = "Default!", + IsHome = true, + StartDate = inThePast, + Role = Role.BM + }; + _context.DepartmentAssignments.Add(assignment1); + _context.DepartmentAssignments.Add(assignment2); + await _context.SaveChangesAsync(); + + var searchRequest = new DepartmentPersonnelSearchRequest + { + DepartmentId = department.Id.Value, + Year = inThePast.Year, + Month = inThePast.Month + }; + var found = await _repository.FindDepartmentAssignments(searchRequest); + Assert.NotNull(found); + Assert.AreEqual(2, found.Count); + + var searchRequest2 = new DepartmentPersonnelSearchRequest + { + DepartmentId = department.Id.Value, + Year = inThePast.Subtract(TimeSpan.FromDays(400)).Year, + Month = inThePast.Month + }; + found = await _repository.FindDepartmentAssignments(searchRequest2); + Assert.NotNull(found); + Assert.AreEqual(0, found.Count); + + var searchRequest3 = new DepartmentPersonnelSearchRequest + { + DepartmentId = department.Id.Value, + Year = inThePast.Add(TimeSpan.FromDays(400)).Year, + Month = inThePast.Month + }; + found = await _repository.FindDepartmentAssignments(searchRequest3); + Assert.NotNull(found); + Assert.AreEqual(2, found.Count); + } + + [Test] + public async Task Find_Department_Assignments_NonMatch_CloseEnded() + { + var department = new Department() + { + DayforceDepartmentName = "Test", + DayForceGlCode = "1", + DnaOrgNbr = "1", + State = DepartmentState.Active, + DepartmentName = "TestDept1", + BusinessLineCode = BusinessLine.Operations.Code + }; + await _repository.SaveDepartmentAsync(department); + + await _context.SaveChangesAsync(); + + var user1 = new AppUser() + { + Id = Guid.NewGuid(), + FullName = "Test", + Role = Role.Admin, + AdEmail = "test1@test.com", + BaseRole = "myrole", + HomeDepartment = "Test Dept", + EmployeeId = "123", + PersonId = 1 + }; + var user2 = new AppUser() + { + Id = Guid.NewGuid(), + FullName = "Test", + Role = Role.Admin, + AdEmail = "test2@test.com", + BaseRole = "myrole", + HomeDepartment = "Test Dept", + EmployeeId = "456", + PersonId = 1 + }; + await _context.AppUsers.AddAsync(user1); + await _context.AppUsers.AddAsync(user2); + await _context.SaveChangesAsync(); + + var inThePast = DateTime.Today.Subtract(TimeSpan.FromDays(30)); + var inTheFuture = DateTime.Today.Add(TimeSpan.FromDays(30)); + + var assignment1 = new UserDepartment() + { + Department = department, + Employee = user1, + Note = "Default!", + IsHome = true, + StartDate = inThePast, + EndDate = inTheFuture, + Role = Role.BM + }; + var assignment2 = new UserDepartment() + { + Department = department, + Employee = user2, + Note = "Default!", + IsHome = true, + StartDate = inThePast, + Role = Role.BM + }; + _context.DepartmentAssignments.Add(assignment1); + _context.DepartmentAssignments.Add(assignment2); + await _context.SaveChangesAsync(); + + var searchRequest = new DepartmentPersonnelSearchRequest + { + DepartmentId = department.Id.Value, + Year = inThePast.Year, + Month = inThePast.Month + }; + var found = await _repository.FindDepartmentAssignments(searchRequest); + Assert.NotNull(found); + Assert.AreEqual(2, found.Count); + + var searchRequest2 = new DepartmentPersonnelSearchRequest + { + DepartmentId = department.Id.Value, + Year = inThePast.Subtract(TimeSpan.FromDays(400)).Year, + Month = inThePast.Month + }; + found = await _repository.FindDepartmentAssignments(searchRequest2); + Assert.NotNull(found); + Assert.AreEqual(0, found.Count); + + var searchRequest3 = new DepartmentPersonnelSearchRequest + { + DepartmentId = department.Id.Value, + Year = inThePast.Add(TimeSpan.FromDays(400)).Year, + Month = inThePast.Month + }; + found = await _repository.FindDepartmentAssignments(searchRequest3); + Assert.NotNull(found); + Assert.AreEqual(1, found.Count); + } + + /// + /// Tests searching for long periods + /// + /// When the employee department assignment starts + /// When the employee department assignment ends + /// when the search starts + /// when the search ends + /// should this assignment be found? + // Test Case 1, Ends before search starts + [TestCase("01/01/2020", "12/31/2020", "01/01/2021", "12/31/2021", false)] + // Test Case 2, Starts before search, ends during + [TestCase("12/01/2020", "01/31/2021", "01/01/2021", "12/31/2021", true)] + // Test Case 3, Starts during, ends during + [TestCase("01/02/2021", "11/30/2021", "01/01/2021", "12/31/2021", true)] + // Test Case 4, Starts during, ends after + [TestCase("01/01/2021", "01/31/2022", "01/01/2021", "12/31/2021", true)] + // Test Case 5, Starts after + [TestCase("01/01/2022", "12/31/2022", "01/01/2021", "12/31/2021", false)] + // Test Case 6, Starts before, no assignment end + [TestCase("01/01/2021", "", "01/01/2021", "12/31/2021", true)] + [TestCase("12/31/2020", "", "01/01/2021", "12/31/2021", true)] + // Test Case 7, Starts during, no assignment end + [TestCase("07/01/2021", "", "01/01/2021", "12/31/2021", true)] + // Test Case 8, starts after, no assignment end, also check "on" which IS included... + [TestCase("12/31/2021", "", "01/01/2021", "12/31/2021", true)] + [TestCase("01/01/2022", "", "01/01/2021", "12/31/2021", false)] + public async Task Find_Department_Assignments_ForMonthRange_Long( + string assignStart, + string assignEnd, + string searchStart, + string searchEnd, + bool included + ) + { + var department = new Department() + { + DayforceDepartmentName = "Test", + DayForceGlCode = "1", + DnaOrgNbr = "1", + State = DepartmentState.Active, + DepartmentName = "TestDept1", + BusinessLineCode = BusinessLine.Operations.Code + }; + await _repository.SaveDepartmentAsync(department); + await _context.SaveChangesAsync(); + + var user1 = new AppUser() + { + Id = Guid.NewGuid(), + FullName = "Test", + Role = Role.Admin, + AdEmail = "test1@test.com", + BaseRole = "myrole", + HomeDepartment = "Test Dept", + EmployeeId = "123", + PersonId = 1 + }; + await _context.AppUsers.AddAsync(user1); + await _context.SaveChangesAsync(); + + var assignment1 = new UserDepartment() + { + Department = department, + Employee = user1, + Note = "Default!", + IsHome = true, + StartDate = DateTime.Parse(assignStart), + Role = Role.BM + }; + if (!string.IsNullOrWhiteSpace(assignEnd)) + { + assignment1.EndDate = DateTime.Parse(assignEnd); + } + + _context.DepartmentAssignments.Add(assignment1); + await _context.SaveChangesAsync(); + + var start = DateTime.Parse(searchStart); + var end = DateTime.Parse(searchEnd); + var range = new MonthRange( + start.Year, + start.Month, + end.Year, + end.Month + ); + var found = await _repository.FindDepartmentAssignmentsForRange(department.Id.Value, range); + Assert.NotNull(found); + if (included) + { + Assert.IsNotEmpty(found); + Assert.AreEqual(1, found.Count); + } + else + { + Assert.IsEmpty(found); + } + } + + /// + /// Tests searching for just single months + /// + /// When the employee department assignment starts + /// When the employee department assignment ends + /// when the search starts + /// when the search ends + /// should this assignment be found? + // Test Case 1, Ends before assignment starts + [TestCase("05/01/2021", "05/31/2021", "06/01/2021", false)] + // Test Case 2, Starts before assignment, ends during + [TestCase("05/01/2021", "06/30/2021", "06/01/2021", true)] + // Test Case 3, Starts during, ends during + [TestCase("06/10/2021", "06/24/2021", "06/01/2021", true)] + // Test Case 4, Starts during, ends after + [TestCase("06/05/2021", "12/31/2021", "06/01/2021", true)] + // Test Case 5, Starts after + [TestCase("07/01/2021", "12/31/2021", "06/01/2021", false)] + // Test Case 6, Starts before, no assignment end + [TestCase("06/01/2021", "", "06/01/2021", true)] + [TestCase("05/15/2021", "", "06/01/2021", true)] + // Test Case 7, Starts during, no assignment end + [TestCase("06/05/2021", "", "06/01/2021", true)] + // Test Case 8, starts after, no assignment end, also check "on" which IS included... + [TestCase("06/30/2021", "", "06/01/2021", true)] + [TestCase("07/01/2021", "", "06/01/2021", false)] + public async Task Find_Department_Assignments_ForMonthRange_Short(string assignStart, string assignEnd, string searchStart, bool included) + { + var department = new Department() + { + DayforceDepartmentName = "Test", + DayForceGlCode = "1", + DnaOrgNbr = "1", + State = DepartmentState.Active, + DepartmentName = "TestDept1", + BusinessLineCode = BusinessLine.Operations.Code + }; + await _repository.SaveDepartmentAsync(department); + await _context.SaveChangesAsync(); + + var user1 = new AppUser() + { + Id = Guid.NewGuid(), + FullName = "Test", + Role = Role.Admin, + AdEmail = "test1@test.com", + BaseRole = "myrole", + HomeDepartment = "Test Dept", + EmployeeId = "123", + PersonId = 1 + }; + await _context.AppUsers.AddAsync(user1); + await _context.SaveChangesAsync(); + + var assignment1 = new UserDepartment() + { + Department = department, + Employee = user1, + Note = "Default!", + IsHome = true, + StartDate = DateTime.Parse(assignStart), + Role = Role.BM + }; + if (!string.IsNullOrWhiteSpace(assignEnd)) + { + assignment1.EndDate = DateTime.Parse(assignEnd); + } + + _context.DepartmentAssignments.Add(assignment1); + await _context.SaveChangesAsync(); + + var start = DateTime.Parse(searchStart); + var range = new MonthRange(start.Year, start.Month); + var found = await _repository.FindDepartmentAssignmentsForRange(department.Id.Value, range); + Assert.NotNull(found); + if (included) + { + Assert.IsNotEmpty(found); + Assert.AreEqual(1, found.Count); + } + else + { + Assert.IsEmpty(found); + } + } + + public async Task Find_Department_Assignments_ForMonthRange_Multi() + { + var department = new Department() + { + DayforceDepartmentName = "Test", + DayForceGlCode = "1", + DnaOrgNbr = "1", + State = DepartmentState.Active, + DepartmentName = "TestDept1", + BusinessLineCode = BusinessLine.Operations.Code + }; + await _repository.SaveDepartmentAsync(department); + await _context.SaveChangesAsync(); + + var user1 = new AppUser() + { + Id = Guid.NewGuid(), + AdEmail = "test1@test.com", + EmployeeId = "123" + }; + await _context.AppUsers.AddAsync(user1); + await _context.SaveChangesAsync(); + + var assignment1 = new UserDepartment() + { + Department = department, + Employee = user1, + Note = "Default!", + IsHome = true, + StartDate = DateTime.Parse("06/02/2021"), + EndDate = DateTime.Parse("06/14/2021"), + Role = Role.BM + }; + + var assignment2 = new UserDepartment() + { + Department = department, + Employee = user1, + Note = "Default!", + IsHome = true, + StartDate = DateTime.Parse("06/16/2021"), + EndDate = DateTime.Parse("06/30/2021"), + Role = Role.BM + }; + + _context.DepartmentAssignments.Add(assignment1); + _context.DepartmentAssignments.Add(assignment2); + await _context.SaveChangesAsync(); + + var start = DateTime.Parse("06/01/2021"); + var range = new MonthRange(start.Year, start.Month); + var found = await _repository.FindDepartmentAssignmentsForRange(department.Id.Value, range); + Assert.NotNull(found); + Assert.IsNotEmpty(found); + Assert.AreEqual(1, found.Count); + + var search = new DepartmentPersonnelSearchRequest() + { + Month = start.Month, + Year = start.Year, + DepartmentId = department.Id.Value, + IncludeAllPersonnel = true + }; + var users = await _repository.FindDepartmentAssignments(search); + Assert.NotNull(users); + Assert.IsNotEmpty(users); + Assert.AreEqual(1, users.Count); + } + + [Test] + public async Task Find_Department_Assignments_With_Sales_Goal_Permission() + { + var department = new Department() + { + DayforceDepartmentName = "Test", + DayForceGlCode = "1", + DnaOrgNbr = "1", + State = DepartmentState.Active, + DepartmentName = "TestDept1", + BusinessLineCode = BusinessLine.Operations.Code + }; + await _repository.SaveDepartmentAsync(department); + + await _context.SaveChangesAsync(); + + var userWithNoSalesGoalsPermission = new AppUser() + { + Id = Guid.NewGuid(), + FullName = "Test", + Role = Role.Admin, + AdEmail = "test1@test.com", + BaseRole = "myrole", + HomeDepartment = "Test Dept", + EmployeeId = "123", + PersonId = 1 + }; + + var userWithSalesGoalsRolePermission = new AppUser() + { + Id = Guid.NewGuid(), + FullName = "Test", + Role = Role.Admin, + AdEmail = "test2@test.com", + BaseRole = "myrole", + HomeDepartment = "Test Dept", + EmployeeId = "456", + PersonId = 2 + }; + + var userWithSalesGoalsAdditionalPermission = new AppUser() + { + Id = Guid.NewGuid(), + FullName = "Test", + Role = Role.Admin, + AdEmail = "test3@test.com", + BaseRole = "myrole", + HomeDepartment = "Test Dept", + EmployeeId = "789", + PersonId = 3, + AdditionalPermissions = new List + { + new UserPermission + { + Permission = Permission.HaveSalesGoals + } + } + }; + + await _context.AppUsers.AddAsync(userWithNoSalesGoalsPermission); + await _context.AppUsers.AddAsync(userWithSalesGoalsRolePermission); + await _context.AppUsers.AddAsync(userWithSalesGoalsAdditionalPermission); + await _context.SaveChangesAsync(); + + var inThePast = DateTime.Today.Subtract(TimeSpan.FromDays(3)); + + var assignment1 = new UserDepartment() + { + Department = department, + Employee = userWithNoSalesGoalsPermission, + Note = "Default!", + IsHome = true, + StartDate = inThePast, + Role = Role.Teller + }; + var assignment2 = new UserDepartment() + { + Department = department, + Employee = userWithSalesGoalsRolePermission, + Note = "Default!", + IsHome = true, + StartDate = inThePast, + Role = Role.BM + }; + var assignment3 = new UserDepartment() + { + Department = department, + Employee = userWithSalesGoalsAdditionalPermission, + Note = "Default!", + IsHome = true, + StartDate = inThePast, + Role = Role.Teller + }; + _context.DepartmentAssignments.Add(assignment1); + _context.DepartmentAssignments.Add(assignment2); + _context.DepartmentAssignments.Add(assignment3); + await _context.SaveChangesAsync(); + + var searchRequest3 = new DepartmentPersonnelSearchRequest + { + DepartmentId = department.Id.Value, + Year = inThePast.Year, + Month = inThePast.Month + }; + + var found = await _repository.FindDepartmentAssignments(searchRequest3); + Assert.NotNull(found); + Assert.True(found.Exists(x => x.PersonId == 2)); + Assert.True(found.Exists(x => x.PersonId == 3)); + } + + [Test] + public async Task Find_User_Home_Department_OK() + { + var department = new Department() + { + DayforceDepartmentName = "Test", + DayForceGlCode = "1", + DnaOrgNbr = "1", + State = DepartmentState.Active, + DepartmentName = "TestDept1", + BusinessLineCode = BusinessLine.Operations.Code + }; + var department2 = new Department() + { + DayforceDepartmentName = "Test", + DayForceGlCode = "1", + DnaOrgNbr = "1", + State = DepartmentState.Active, + DepartmentName = "TestDept2", + BusinessLineCode = BusinessLine.Operations.Code + }; + await _repository.SaveDepartmentAsync(department); + await _repository.SaveDepartmentAsync(department2); + + await _context.SaveChangesAsync(); + + var user1 = new AppUser() + { + Id = Guid.NewGuid(), + FullName = "Test", + Role = Role.Admin, + AdEmail = "test1@test.com", + BaseRole = "myrole", + HomeDepartment = "Test Dept", + EmployeeId = "123", + PersonId = 1 + }; + var user2 = new AppUser() + { + Id = Guid.NewGuid(), + FullName = "Test", + Role = Role.Admin, + AdEmail = "test2@test.com", + BaseRole = "myrole", + HomeDepartment = "Test Dept", + EmployeeId = "456", + PersonId = 1 + }; + var user3 = new AppUser() + { + Id = Guid.NewGuid(), + FullName = "Test", + Role = Role.Admin, + AdEmail = "test3@test.com", + BaseRole = "myrole", + HomeDepartment = "Test Dept", + EmployeeId = "789", + PersonId = 1 + }; + var user4 = new AppUser() + { + Id = Guid.NewGuid(), + FullName = "Test", + Role = Role.Admin, + AdEmail = "test3@test.com", + BaseRole = "myrole", + HomeDepartment = "Test Dept", + EmployeeId = "101", + PersonId = 1 + }; + await _context.AppUsers.AddAsync(user1); + await _context.AppUsers.AddAsync(user2); + await _context.AppUsers.AddAsync(user3); + await _context.AppUsers.AddAsync(user4); + await _context.SaveChangesAsync(); + + var inThePast = DateTime.Today.Subtract(TimeSpan.FromDays(3)); + + var assignment1 = new UserDepartment() + { + Role = Role.Admin, + Department = department, + Employee = user1, + Note = "Default!", + IsHome = true, + StartDate = inThePast + }; + var assignment2 = new UserDepartment() + { + Role = Role.Admin, + Department = department, + Employee = user2, + Note = "Default!", + IsHome = true, + StartDate = inThePast + }; + var assignment5 = new UserDepartment() + { + Role = Role.Admin, + Department = department2, + Employee = user2, + Note = "Default!", + IsHome = true, + StartDate = inThePast + }; + var assignment3 = new UserDepartment() + { + Role = Role.Admin, + Department = department2, + Employee = user3, + Note = "Default!", + IsHome = true, + StartDate = inThePast + }; + var assignment4 = new UserDepartment() + { + Role = Role.Admin, + Department = department, + Employee = user3, + Note = "Default!", + IsHome = false, + StartDate = inThePast + }; + var assignment6 = new UserDepartment() + { + Role = Role.Admin, + Department = department, + Employee = user4, + Note = "Default!", + IsHome = true, + StartDate = inThePast, + EndDate = inThePast + }; + var assignment7 = new UserDepartment() + { + Role = Role.Admin, + Department = department2, + Employee = user4, + Note = "Default!", + IsHome = true, + StartDate = inThePast, + EndDate = null + }; + _context.DepartmentAssignments.Add(assignment1); + _context.DepartmentAssignments.Add(assignment2); + _context.DepartmentAssignments.Add(assignment3); + _context.DepartmentAssignments.Add(assignment4); + _context.DepartmentAssignments.Add(assignment5); + _context.DepartmentAssignments.Add(assignment6); + _context.DepartmentAssignments.Add(assignment7); + await _context.SaveChangesAsync(); + + //Ok + var res = await _repository.FindUserHomeDepartment(user3); + Assert.AreEqual(res, department2); + + res = await _repository.FindUserHomeDepartment(user1); + Assert.AreEqual(res, department); + + //Non-existing user + res = await _repository.FindUserHomeDepartment(new AppUser() + { + AdEmail = "notfound@test.com", + BaseRole = "newrole", + HomeDepartment = "Other Dept", + EmployeeId = "56565", + PersonId = 100 + }); + Assert.Null(res); + + //User with multiple non-current home departments + res = await _repository.FindUserHomeDepartment(user2); + Assert.Null(res); + + //User with multiple home departments, one current, one non-current + res = await _repository.FindUserHomeDepartment(user4); + Assert.AreEqual(res, department2); + } + + [Test] + public async Task FindDepartmentByBranchName_OK() + { + var department = new Department() + { + DayforceDepartmentName = "Test", + DayForceGlCode = "1", + DnaOrgNbr = "1", + State = DepartmentState.Active, + DepartmentName = "MEQUON", + BusinessLineCode = BusinessLine.Operations.Code + }; + var department2 = new Department() + { + DayforceDepartmentName = "Test", + DayForceGlCode = "1", + DnaOrgNbr = "1", + State = DepartmentState.Active, + DepartmentName = "WAUKESHA MAIN", + BusinessLineCode = BusinessLine.Operations.Code + }; + await _repository.SaveDepartmentAsync(department); + await _repository.SaveDepartmentAsync(department2); + await _context.SaveChangesAsync(); + + var res = await _repository.FindDepartmentByBranchName("Mequon"); + Assert.AreEqual(department, res); + + res = await _repository.FindDepartmentByBranchName("Waukesha main"); + Assert.AreEqual(department2, res); + + res = await _repository.FindDepartmentByBranchName("Mars base 1"); + Assert.Null(res); + } + + [Test] + public async Task FindCurrentDepartmentAssignments_OK() + { + var user1 = new AppUser + { + Id = Guid.NewGuid(), + AdEmail = "user1@landmarkcu.com", EmployeeId = "C0001", PersonId = 1, BaseRole = "Role", FullName = "Test", HomeDepartment = "Test", + Role = Role.Admin + }; + var user2 = new AppUser + { + Id = Guid.NewGuid(), + AdEmail = "user2@landmarkcu.com", EmployeeId = "C0002", PersonId = 2, BaseRole = "Role", FullName = "Test", HomeDepartment = "Test", + Role = Role.Admin + }; + var yesterday = DateTime.Now.AddDays(-1); + + var testDepartment = (new Department() + { + BusinessLineCode = BusinessLine.Operations.Code, + DayforceDepartmentName = "Test", + DayForceGlCode = "1", + DnaOrgNbr = "1", + DepartmentName = "TestName", + Assignments = new List + { + new UserDepartment { Role = Role.Admin, Id = 11, StartDate = yesterday, Employee = user1 }, + new UserDepartment { Role = Role.Admin, Id = 12, StartDate = yesterday, Employee = user2 } + } + }); + + await _repository.SaveDepartmentAsync(testDepartment); + await _context.SaveChangesAsync(); + + var result = await _repository.FindCurrentDepartmentAssignments(1); + + Assert.AreEqual(2, result.Count); + Assert.AreEqual(11, result.ElementAt(0).Id); + Assert.AreEqual("C0001", result.ElementAt(0).Employee.EmployeeId); + Assert.AreEqual(12, result.ElementAt(1).Id); + Assert.AreEqual("C0002", result.ElementAt(1).Employee.EmployeeId); + } + + [Test] + public async Task FindCurrentDepartmentAssignments_EndDatePassed() + { + var user1 = new AppUser + { + Id = Guid.NewGuid(), + AdEmail = "user1@landmarkcu.com", EmployeeId = "C0001", PersonId = 1, BaseRole = "Role", FullName = "Test", HomeDepartment = "Test", + Role = Role.Admin + }; + var user2 = new AppUser + { + Id = Guid.NewGuid(), + AdEmail = "user2@landmarkcu.com", EmployeeId = "C0002", PersonId = 2, BaseRole = "Role", FullName = "Test", HomeDepartment = "Test", + Role = Role.Admin + }; + var yesterday = DateTime.Now.AddDays(-1); + var lastMonth = DateTime.Now.AddMonths(-1); + + var testDepartment = (new Department() + { + BusinessLineCode = BusinessLine.Operations.Code, + DayforceDepartmentName = "Test", + DayForceGlCode = "1", + DnaOrgNbr = "1", + DepartmentName = "TestName", + Assignments = new List + { + new UserDepartment { Role = Role.Admin, Id = 11, StartDate = lastMonth, EndDate = lastMonth, Employee = user1 }, + new UserDepartment { Role = Role.Admin, Id = 12, StartDate = yesterday, Employee = user2 } + } + }); + + await _repository.SaveDepartmentAsync(testDepartment); + await _context.SaveChangesAsync(); + + var result = await _repository.FindCurrentDepartmentAssignments(1); + + Assert.AreEqual(1, result.Count); + Assert.AreEqual(12, result.ElementAt(0).Id); + Assert.AreEqual("C0002", result.ElementAt(0).Employee.EmployeeId); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/GoalChangeRepositoryTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/GoalChangeRepositoryTest.cs new file mode 100644 index 0000000..261994b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/GoalChangeRepositoryTest.cs @@ -0,0 +1,55 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using Microsoft.EntityFrameworkCore; +using Moq; +using NUnit.Framework; + +namespace Application.UnitTests.Repository +{ + public class GoalChangeRepositoryTest + { + private ApplicationDbContext _context; + private GoalChangeRepository _repository; + + [SetUp] + public void Setup() + { + var mockDateTime = new DateTimeService(); + var mockCurrentUserService = Mock.Of(); + Mock.Get(mockCurrentUserService).Setup(s => s.GetCurrentUser()).Returns(new ApplicationUser() + { + Email = "JohnTest@test.com", + }); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .EnableSensitiveDataLogging(true) + .Options; + _context = new ApplicationDbContext(options, mockCurrentUserService, mockDateTime); + _repository = new GoalChangeRepository(_context); + } + + [Test] + public async Task Basic_Flow_Test() + { + var saved = await _repository.GetGoalChangesAsync(); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + var newId = await _repository.SaveGoalChangeAsync(new GoalChange()); + await _context.SaveChangesAsync(); + + saved = await _repository.GetGoalChangesAsync(); + Assert.NotNull(saved); + Assert.IsNotEmpty(saved); + var first = saved.First(); + // Make sure our Auditable is working. + Assert.NotNull(first.CreatedBy); + Assert.NotNull(first.Created); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/GoalRepositoryTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/GoalRepositoryTest.cs new file mode 100644 index 0000000..de12c56 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/GoalRepositoryTest.cs @@ -0,0 +1,762 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApi.Extensions; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using Microsoft.EntityFrameworkCore; +using Moq; +using NUnit.Framework; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace Application.UnitTests.Repository +{ + public class GoalRepositoryTest + { + private ApplicationDbContext _context; + private GoalRepository _repository; + + [SetUp] + public void Setup() + { + var mockDateTime = new DateTimeService(); + var mockCurrentUserService = Mock.Of(); + Mock.Get(mockCurrentUserService).Setup(s => s.GetCurrentUser()).Returns(new ApplicationUser() + { + Email = "JohnTest@test.com", + }); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .EnableSensitiveDataLogging(true) + .Options; + _context = new ApplicationDbContext(options, mockCurrentUserService, mockDateTime); + _repository = new GoalRepository(_context); + } + + [Test] + public async Task Basic_Flow_Test() + { + var saved = await _repository.GetGoalsAsync(new GoalSearchRequest()); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + var newId = await _repository.SaveGoalAsync(new Goal() + { + DepartmentId = 6, + StartDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1), + EndDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1).AddMonths(1).Subtract(TimeSpan.FromDays(1)), + EmployeeId = null + }); + await _context.SaveChangesAsync(); + + saved = await _repository.GetGoalsAsync(new GoalSearchRequest() + { + IncludePersonnel = false + } + ); + Assert.NotNull(saved); + Assert.IsNotEmpty(saved); + var first = saved.First(); + // Make sure our Auditable is working. + Assert.NotNull(first.CreatedBy); + Assert.NotNull(first.Created); + } + + [Test] + public async Task Basic_Search_Test() + { + var saved = await _repository.GetGoalsAsync(new GoalSearchRequest()); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + var startDate = new DateTime(2021, 3, 1); + var endDate = (new DateTime(2021, 3, 1)).EndOfMonth(); + + + var goalPersonnel = new Goal() + { + Product = Product.NewChecking, + Value = 10, + DepartmentId = 1, + StartDate = startDate, + EndDate = endDate, + EmployeeId = "programming@landmarkcu.com" + }; + + var goal = new Goal() + { + Product = Product.NewChecking, + Value = 10, + DepartmentId = 1, + StartDate = startDate, + EndDate = endDate + }; + + var goal2 = new Goal() + { + Product = Product.NewChecking, + Value = 8, + DepartmentId = 2, + StartDate = startDate, + EndDate = endDate + }; + + var goal3 = new Goal() + { + Product = Product.NewChecking, + Value = 11, + DepartmentId = 1, + StartDate = new DateTime(2021, 4, 1), + EndDate = (new DateTime(2021, 4, 1)).EndOfMonth() + }; + + await _repository.SaveGoalAsync(goalPersonnel); + await _repository.SaveGoalAsync(goal); + await _repository.SaveGoalAsync(goal2); + await _repository.SaveGoalAsync(goal3); + await _context.SaveChangesAsync(); + + saved = await _repository.GetGoalsAsync(new GoalSearchRequest() + { + IncludePersonnel = false + } + ); + Assert.NotNull(saved); + Assert.IsNotEmpty(saved); + Assert.AreEqual(3, saved.Count); + + saved = await _repository.GetGoalsAsync(new GoalSearchRequest() + { + IncludePersonnel = true + } + ); + Assert.NotNull(saved); + Assert.IsNotEmpty(saved); + Assert.AreEqual(1, saved.Count); + + saved = await _repository.GetGoalsAsync(new GoalSearchRequest() + { + IncludePersonnel = false, + departmentId = 1 + } + ); + Assert.NotNull(saved); + Assert.IsNotEmpty(saved); + Assert.AreEqual(2, saved.Count); + + + saved = await _repository.GetGoalsAsync(new GoalSearchRequest() + { + IncludePersonnel = true, + departmentId = 1 + } + ); + Assert.NotNull(saved); + Assert.IsNotEmpty(saved); + Assert.AreEqual(1, saved.Count); + + saved = await _repository.GetGoalsAsync(new GoalSearchRequest() + { + IncludePersonnel = false, + DateRange = new MonthRange(2021,3) + } + ); + Assert.NotNull(saved); + Assert.IsNotEmpty(saved); + Assert.AreEqual(2, saved.Count); + } + + [Test] + public async Task Multi_Year_Search_Test() + { + var saved = await _repository.GetGoalsAsync(new GoalSearchRequest()); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + var startDate = new DateTime(2021, 6, 1); + var endDate = (new DateTime(2021, 6, 1)).EndOfMonth(); + + var goal = new Goal() + { + Product = Product.NewChecking, + Value = 10, + DepartmentId = 1, + StartDate = startDate, + EndDate = endDate + }; + + var goal2 = new Goal() + { + Product = Product.NewChecking, + Value = 11, + DepartmentId = 1, + StartDate = new DateTime(2022, 4, 1), + EndDate = (new DateTime(2022, 4, 1)).EndOfMonth() + }; + + await _repository.SaveGoalAsync(goal); + await _repository.SaveGoalAsync(goal2); + await _context.SaveChangesAsync(); + + saved = await _repository.GetGoalsAsync(new GoalSearchRequest() + { + DateRange = new MonthRange(2021,6, 2022,6), + IncludePersonnel = false + } + ); + Assert.NotNull(saved); + Assert.IsNotEmpty(saved); + Assert.AreEqual(2, saved.Count); + } + + [Test] + public async Task Find_Goal_Basic() + { + var saved = await _repository.GetGoalsAsync(new GoalSearchRequest()); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + var goalChange = new GoalChange() + { + Product = Product.NewChecking, + Value = 10, + DepartmentId = 1, + StartDate = new DateTime(2021, 3, 1), + EndDate = (new DateTime(2021, 3, 1)).EndOfMonth(), + PersonId = "programming@landmarkcu.com" + }; + + var goal = new Goal() + { + Product = Product.NewChecking, + Value = 10, + DepartmentId = 1, + StartDate = new DateTime(2021, 3, 1), + EndDate = (new DateTime(2021, 3, 1)).EndOfMonth(), + EmployeeId = "programming@landmarkcu.com" + }; + + var newId = await _repository.SaveGoalAsync(goal); + await _context.SaveChangesAsync(); + + var foundGoal = await _repository.FindExistingGoal(goalChange); + Assert.NotNull(foundGoal); + // Make sure our Auditable is working. + Assert.NotNull(foundGoal.CreatedBy); + Assert.NotNull(foundGoal.Created); + } + + [Test] + public async Task Find_Goal_UsesDate() + { + var saved = await _repository.GetGoalsAsync(new GoalSearchRequest()); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + var goalChange = new GoalChange() + { + Product = Product.NewChecking, + Value = 10, + DepartmentId = 1, + StartDate = new DateTime(2021, 3, 1), + EndDate = (new DateTime(2021, 3, 1)).EndOfMonth(), + PersonId = "programming@landmarkcu.com" + }; + + var goal = new Goal() + { + Product = Product.NewChecking, + Value = 10, + DepartmentId = 1, + StartDate = new DateTime(2021, 3, 1), + EndDate = (new DateTime(2021, 3, 1)).AddMonths(1).AddMilliseconds(-1), + EmployeeId = "programming@landmarkcu.com" + }; + + var newId = await _repository.SaveGoalAsync(goal); + await _context.SaveChangesAsync(); + + var foundGoal = await _repository.FindExistingGoal(goalChange); + Assert.NotNull(foundGoal); + // Make sure our Auditable is working. + Assert.NotNull(foundGoal.CreatedBy); + Assert.NotNull(foundGoal.Created); + } + + + [Test] + public async Task Find_Goal_Department_Basic() + { + var saved = await _repository.GetGoalsAsync(new GoalSearchRequest()); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + var startDate = new DateTime(2021, 3, 1); + var endDate = (new DateTime(2021, 3, 1)).EndOfMonth(); + + // This personnel goal shouldn't be returned. + var goalPersonnel = new Goal() + { + Product = Product.NewChecking, + Value = 10, + DepartmentId = 1, + StartDate = startDate, + EndDate = endDate, + EmployeeId = "programming@landmarkcu.com" + }; + + var goal = new Goal() + { + Product = Product.NewChecking, + Value = 10, + DepartmentId = 1, + StartDate = startDate, + EndDate = endDate + }; + + var goal2 = new Goal() + { + Product = Product.NewChecking, + Value = 8, + DepartmentId = 2, + StartDate = startDate, + EndDate = endDate + }; + + var goal3 = new Goal() + { + Product = Product.NewChecking, + Value = 11, + DepartmentId = 1, + StartDate = new DateTime(2021, 4, 1), + EndDate = (new DateTime(2021, 4, 1)).EndOfMonth() + }; + + await _repository.SaveGoalAsync(goalPersonnel); + await _repository.SaveGoalAsync(goal); + await _repository.SaveGoalAsync(goal2); + await _repository.SaveGoalAsync(goal3); + await _context.SaveChangesAsync(); + + var foundGoals = await _repository.FindDepartmentGoals(1, startDate, endDate); + Assert.NotNull(foundGoals); + Assert.IsNotEmpty(foundGoals); + Assert.AreEqual(1, foundGoals.Count); + Assert.AreEqual(10, foundGoals.First().Value); + } + + [Test] + public async Task Find_Goal_Department_Annual() + { + var saved = await _repository.GetGoalsAsync(new GoalSearchRequest()); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + var startDate = new DateTime(2021, 3, 1); + var endDate = (new DateTime(2021, 3, 1)).EndOfMonth(); + + // This personnel goal shouldn't be returned. + var goalPersonnel = new Goal() + { + Product = Product.NewChecking, + Value = 7, + DepartmentId = 1, + StartDate = startDate, + EndDate = endDate, + EmployeeId = "programming@landmarkcu.com" + }; + + var goal = new Goal() + { + Product = Product.NewChecking, + Value = 10, + DepartmentId = 1, + StartDate = startDate, + EndDate = endDate + }; + + var goal2 = new Goal() + { + Product = Product.NewChecking, + Value = 8, + DepartmentId = 2, + StartDate = startDate, + EndDate = endDate + }; + + var goal3 = new Goal() + { + Product = Product.NewChecking, + Value = 11, + DepartmentId = 1, + StartDate = new DateTime(2021, 4, 1), + EndDate = (new DateTime(2021, 3, 1)).EndOfMonth() + }; + + await _repository.SaveGoalAsync(goalPersonnel); + await _repository.SaveGoalAsync(goal); + await _repository.SaveGoalAsync(goal2); + await _repository.SaveGoalAsync(goal3); + await _context.SaveChangesAsync(); + + var foundGoals = await _repository.FindAnnualDepartmentGoals(1, startDate.Year); + Assert.NotNull(foundGoals); + Assert.IsNotEmpty(foundGoals); + Assert.AreEqual(2, foundGoals.Count); + Assert.AreEqual(10, foundGoals.First().Value); + Assert.AreEqual(11, foundGoals.ElementAt(1).Value); + } + + [Test] + public async Task Find_Goal_Personnel_Basic() + { + var saved = await _repository.GetGoalsAsync(new GoalSearchRequest()); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + var startDate = new DateTime(2021, 3, 1); + var endDate = (new DateTime(2021, 3, 1)).EndOfMonth(); + + // This department goal shouldn't be returned + var departmentGoal = new Goal() + { + Product = Product.NewChecking, + Value = 10, + DepartmentId = 1, + StartDate = startDate, + EndDate = endDate + }; + + var goal = new Goal() + { + Product = Product.NewChecking, + Value = 10, + DepartmentId = 1, + StartDate = startDate, + EndDate = endDate, + EmployeeId = "programming@landmarkcu.com" + }; + + var goalx = new Goal() + { + Product = Product.NewChecking, + Value = 10, + DepartmentId = 1, + StartDate = startDate, + EndDate = endDate, + EmployeeId = "xyz@landmarkcu.com" + }; + + var goal2 = new Goal() + { + Product = Product.NewChecking, + Value = 8, + DepartmentId = 2, + StartDate = startDate, + EndDate = endDate, + EmployeeId = "programming@landmarkcu.com" + }; + + var goal3 = new Goal() + { + Product = Product.NewChecking, + Value = 11, + DepartmentId = 1, + StartDate = new DateTime(2021, 4, 1), + EndDate = (new DateTime(2021, 4, 1)).EndOfMonth(), + EmployeeId = "programming@landmarkcu.com" + }; + + await _repository.SaveGoalAsync(departmentGoal); + await _repository.SaveGoalAsync(goal); + await _repository.SaveGoalAsync(goalx); + await _repository.SaveGoalAsync(goal2); + await _repository.SaveGoalAsync(goal3); + await _context.SaveChangesAsync(); + + var users = new List() + { + new AppUser() + { + AdEmail = "programming@landmarkcu.com", + EmployeeId = "programming@landmarkcu.com", + FullName = "John McTester", + HomeDepartment = "QA", + BaseRole = "Tester", + Role = Role.Developer, + } + }; + var foundGoals = await _repository.FindPersonnelGoals(1, users, startDate, endDate); + Assert.NotNull(foundGoals); + Assert.IsNotEmpty(foundGoals); + Assert.AreEqual(1, foundGoals.Count); + Assert.AreEqual(10, foundGoals.First().Value); + } + + [Test] + public async Task Find_Goal_Personnel_Monthly() + { + var saved = await _repository.GetGoalsAsync(new GoalSearchRequest()); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + var startDate = new DateTime(2021, 3, 1); + var endDate = (new DateTime(2021, 3, 1)).EndOfMonth(); + + // This department goal shouldn't be returned + var departmentGoal = new Goal() + { + Product = Product.NewChecking, + Value = 10, + DepartmentId = 1, + StartDate = startDate, + EndDate = endDate + }; + + var goal = new Goal() + { + Product = Product.NewChecking, + Value = 10, + DepartmentId = 1, + StartDate = startDate, + EndDate = endDate, + EmployeeId = "programming@landmarkcu.com" + }; + + // Not the same person. + var goalx = new Goal() + { + Product = Product.NewChecking, + Value = 10, + DepartmentId = 1, + StartDate = startDate, + EndDate = endDate, + EmployeeId = "xyz@landmarkcu.com" + }; + + // Not the same department + var goal2 = new Goal() + { + Product = Product.NewChecking, + Value = 8, + DepartmentId = 2, + StartDate = startDate, + EndDate = endDate, + EmployeeId = "programming@landmarkcu.com" + }; + + // not the same month + var goal3 = new Goal() + { + Product = Product.NewChecking, + Value = 11, + DepartmentId = 1, + StartDate = new DateTime(2021, 4, 1), + EndDate = (new DateTime(2021, 4, 1)).EndOfMonth(), + EmployeeId = "programming@landmarkcu.com" + }; + + await _repository.SaveGoalAsync(departmentGoal); + await _repository.SaveGoalAsync(goal); + await _repository.SaveGoalAsync(goalx); + await _repository.SaveGoalAsync(goal2); + await _repository.SaveGoalAsync(goal3); + await _context.SaveChangesAsync(); + + var users = new List() + { + new AppUser() + { + AdEmail = "programming@landmarkcu.com", + EmployeeId = "programming@landmarkcu.com", + FullName = "John McTester", + HomeDepartment = "QA", + BaseRole = "Tester", + Role = Role.Developer, + } + }; + var foundGoals = await _repository.FindMonthlyPersonnelGoals(1, users, startDate.Year, startDate.Month); + Assert.NotNull(foundGoals); + Assert.IsNotEmpty(foundGoals); + Assert.AreEqual(1, foundGoals.Count); + Assert.AreEqual(10, foundGoals.First().Value); + } + + [Test] + public async Task Find_Goal_Personnel_Month_Range() + { + var saved = await _repository.GetGoalsAsync(new GoalSearchRequest()); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + var startDate = new DateTime(2021, 3, 1); + var endDate = (new DateTime(2021, 3, 1)).EndOfMonth(); + + // This department goal shouldn't be returned + var departmentGoal = new Goal() + { + Product = Product.NewChecking, + Value = 10, + DepartmentId = 1, + StartDate = startDate, + EndDate = endDate + }; + + var goal = new Goal() + { + Product = Product.NewChecking, + Value = 10, + DepartmentId = 1, + StartDate = startDate, + EndDate = endDate, + EmployeeId = "programming@landmarkcu.com" + }; + + // Not the same person. + var goalx = new Goal() + { + Product = Product.NewChecking, + Value = 10, + DepartmentId = 1, + StartDate = startDate, + EndDate = endDate, + EmployeeId = "xyz@landmarkcu.com" + }; + + // Not the same department + var goal2 = new Goal() + { + Product = Product.NewChecking, + Value = 8, + DepartmentId = 2, + StartDate = startDate, + EndDate = endDate, + EmployeeId = "programming@landmarkcu.com" + }; + + // not the same month + var goal3 = new Goal() + { + Product = Product.NewChecking, + Value = 11, + DepartmentId = 1, + StartDate = new DateTime(2021, 4, 1), + EndDate = (new DateTime(2021, 4, 1)).EndOfMonth(), + EmployeeId = "programming@landmarkcu.com" + }; + + // not in month range + var goal4 = new Goal() + { + Product = Product.NewChecking, + Value = 12, + DepartmentId = 1, + StartDate = new DateTime(2021, 5, 1), + EndDate = (new DateTime(2021, 5, 1)).EndOfMonth(), + EmployeeId = "programming@landmarkcu.com" + }; + + await _repository.SaveGoalAsync(departmentGoal); + await _repository.SaveGoalAsync(goal); + await _repository.SaveGoalAsync(goalx); + await _repository.SaveGoalAsync(goal2); + await _repository.SaveGoalAsync(goal3); + await _repository.SaveGoalAsync(goal4); + await _context.SaveChangesAsync(); + + var users = new List() + { + new AppUser() + { + AdEmail = "programming@landmarkcu.com", + EmployeeId = "programming@landmarkcu.com", + FullName = "John McTester", + HomeDepartment = "QA", + BaseRole = "Tester", + Role = Role.Developer, + } + }; + var range = new MonthRange + ( + startDate.Year, + startDate.Month, + endDate.Year, + endDate.Month + 1 + ); + var foundGoals = await _repository.FindPersonnelGoalsByMonthRange(1, users, range); + Assert.NotNull(foundGoals); + Assert.IsNotEmpty(foundGoals); + Assert.AreEqual(2, foundGoals.Count); + Assert.AreEqual(10, foundGoals.First().Value); + Assert.AreEqual(11, foundGoals.ElementAt(1).Value); + + } + + [Test] + public async Task FindDepartmentGoalsByMonthRange() + { + var saved = await _repository.GetGoalsAsync(new GoalSearchRequest()); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + var startDate = new DateTime(2021, 3, 1); + var endDate = (new DateTime(2021, 3, 1)).EndOfMonth(); + + var goalInRange = new Goal() + { + Product = Product.NewChecking, + Value = 10, + DepartmentId = 1, + StartDate = startDate, + EndDate = endDate + }; + + var goalNotInRange = new Goal() + { + Product = Product.ATMCard, + Value = 2, + DepartmentId = 1, + StartDate = startDate.AddMonths(-2), + EndDate = endDate.AddMonths(-2) + }; + + var goalWithPersonId = new Goal() + { + Product = Product.CheckCard, + Value = 2, + DepartmentId = 1, + StartDate = startDate, + EndDate = endDate, + EmployeeId = "E1" + }; + + var goalForOtherDepartment = new Goal() + { + Product = Product.CreditCard, + Value = 2, + DepartmentId = 2, + StartDate = startDate, + EndDate = endDate + }; + + var range = new MonthRange(2021, 3, 2021, 4); + + await _repository.SaveGoalAsync(goalInRange); + await _repository.SaveGoalAsync(goalNotInRange); + await _repository.SaveGoalAsync(goalWithPersonId); + await _repository.SaveGoalAsync(goalForOtherDepartment); + await _context.SaveChangesAsync(); + + var found = await _repository.FindDepartmentGoalsByMonthRange(1, range); + + Assert.NotNull(found); + Assert.IsNotEmpty(found); + Assert.AreEqual(1, found.Count); + Assert.AreEqual(10, found.First().Value); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/PayoutRepositoryTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/PayoutRepositoryTest.cs new file mode 100644 index 0000000..2018be4 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/PayoutRepositoryTest.cs @@ -0,0 +1,233 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Microsoft.EntityFrameworkCore; +using Moq; +using NUnit.Framework; + +namespace Application.UnitTests.Repository +{ + public class PayoutRepositoryTest + { + private ApplicationDbContext _context; + private PayoutRepository _repository; + + [SetUp] + public void Setup() + { + var mockDateTime = new DateTimeService(); + var mockCurrentUserService = Mock.Of(); + Mock.Get(mockCurrentUserService).Setup(s => s.GetCurrentUser()).Returns(new ApplicationUser() + { + Email = "JohnTest@test.com", + }); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .EnableSensitiveDataLogging(true) + .Options; + _context = new ApplicationDbContext(options, mockCurrentUserService, mockDateTime); + _repository = new PayoutRepository(_context); + } + + [Test] + public async Task Basic_Flow_Test() + { + var period = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); + var saved = await _repository.GetPayoutLineItemsForPeriodAsync(period); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + var newId = await _repository.SavePayoutLineItemAsync(new PayoutLineItem() + { + Product = Product.CheckCard, + Role = Role.Teller, + EmployeeId = "11771", + DepartmentId = 999, + ActivityTotal = 1, + PayoutTotal = 10, + CommissionId = 99, + CommissionValue = 10, + ActivityPeriod = period + }); + await _context.SaveChangesAsync(); + + saved = await _repository.GetPayoutLineItemsForPeriodAsync(period); + Assert.NotNull(saved); + Assert.IsNotEmpty(saved); + var first = saved.First(); + // Make sure our Auditable is working. + Assert.NotNull(first.CreatedBy); + Assert.NotNull(first.Created); + + var savedPayout = await _repository.GetPayoutLineItemById(saved.First().Id); + Assert.AreSame(saved.First(), savedPayout); + } + + [Test] + public async Task Basic_RangeFlow_Test() + { + var period = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); + var saved = await _repository.GetPayoutLineItemsForPeriodAsync(period); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + var lineItems = new List() + { + new PayoutLineItem() + { + Product = Product.CheckCard, + Role = Role.Teller, + EmployeeId = "11771", + DepartmentId = 999, + ActivityTotal = 1, + PayoutTotal = 10, + CommissionId = 99, + CommissionValue = 10, + ActivityPeriod = period + }, + new PayoutLineItem() + { + Product = Product.CheckCard, + Role = Role.Teller, + EmployeeId = "11772", + DepartmentId = 999, + ActivityTotal = 1, + PayoutTotal = 10, + CommissionId = 99, + CommissionValue = 10, + ActivityPeriod = period + } + }; + await _repository.SavePayoutLineItemRangeAsync(lineItems); + await _context.SaveChangesAsync(); + + saved = await _repository.GetPayoutLineItemsForPeriodAsync(period); + Assert.NotNull(saved); + Assert.IsNotEmpty(saved); + Assert.AreEqual(2, saved.Count()); + var first = saved.First(); + // Make sure our Auditable is working. + Assert.NotNull(first.CreatedBy); + Assert.NotNull(first.Created); + + var savedPayout = await _repository.GetPayoutLineItemById(saved.First().Id); + Assert.AreSame(saved.First(), savedPayout); + } + + [Test] + public async Task GetEmployeePayoutsForPeriod_None() + { + var user = new AppUser() + { + Role = Role.Admin, + AdEmail = "tester@test.com", + BaseRole = "Tester", + EmployeeId = "11771", + FullName = "Test McTester", + HomeDepartment = "HOME", + PersonId = 565 + }; + + var period = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); + var found = await _repository.GetEmployeePayoutsForPeriod(user, period); + Assert.NotNull(found); + Assert.IsEmpty(found); + } + + [Test] + public async Task GetEmployeePayoutsForPeriod_Basic() + { + var period = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); + var period2 = new DateTime(DateTime.Today.Year + 1, DateTime.Today.Month, 1); + var payout1 = new PayoutLineItem() + { + Product = Product.CheckCard, + Role = Role.Teller, + EmployeeId = "11771", + DepartmentId = 999, + ActivityTotal = 1, + PayoutTotal = 10, + CommissionId = 99, + CommissionValue = 10, + ActivityPeriod = period + }; + await _repository.SavePayoutLineItemAsync(payout1); + var payout2 = new PayoutLineItem() + { + Product = Product.CheckCard, + Role = Role.Teller, + EmployeeId = "11772", + DepartmentId = 999, + ActivityTotal = 1, + PayoutTotal = 10, + CommissionId = 99, + CommissionValue = 10, + ActivityPeriod = period + }; + await _repository.SavePayoutLineItemAsync(payout2); + var payout3 = new PayoutLineItem() + { + Product = Product.CheckCard, + Role = Role.Teller, + EmployeeId = "11771", + DepartmentId = 999, + ActivityTotal = 1, + PayoutTotal = 10, + CommissionId = 99, + CommissionValue = 10, + ActivityPeriod = period + }; + await _repository.SavePayoutLineItemAsync(payout3); + var payout4 = new PayoutLineItem() + { + Product = Product.CheckCard, + Role = Role.Teller, + EmployeeId = "11771", + DepartmentId = 999, + ActivityTotal = 1, + PayoutTotal = 10, + CommissionId = 99, + CommissionValue = 10, + ActivityPeriod = period2 + }; + await _repository.SavePayoutLineItemAsync(payout4); + await _context.SaveChangesAsync(); + + var user = new AppUser() + { + Role = Role.Admin, + AdEmail = "tester@test.com", + BaseRole = "Tester", + EmployeeId = "11771", + FullName = "Test McTester", + HomeDepartment = "HOME", + PersonId = 11771, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 55, + Department = new Department() + { + Id = 55, + State = DepartmentState.Active, + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code + } + } + } + }; + + var found = await _repository.GetEmployeePayoutsForPeriod(user, period); + Assert.NotNull(found); + Assert.IsNotEmpty(found); + Assert.AreEqual(2, found.Count); + } + + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/ProcessorHistoryRepositoryTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/ProcessorHistoryRepositoryTest.cs new file mode 100644 index 0000000..e9d2f5d --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/ProcessorHistoryRepositoryTest.cs @@ -0,0 +1,91 @@ +using System; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using Microsoft.EntityFrameworkCore; +using Moq; +using NUnit.Framework; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace Application.UnitTests.Repository +{ + public class ProcessorHistoryRepositoryTest + { + private ApplicationDbContext _context; + private ProcessorHistoryRepository _repository; + + [SetUp] + public void Setup() + { + var mockDateTime = new DateTimeService(); + var mockCurrentUserService = Mock.Of(); + Mock.Get(mockCurrentUserService).Setup(s => s.GetCurrentUser()).Returns(new ApplicationUser() + { + Email = "JohnTest@test.com", + }); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .EnableSensitiveDataLogging(true) + .Options; + _context = new ApplicationDbContext(options, mockCurrentUserService, mockDateTime); + _repository = new ProcessorHistoryRepository(_context); + } + + [Test] + public async Task Basic_Flow_test() + { + var res = await _repository.SaveProcessorRunHistory(new ProcessorHistoryRecord() + { + Process = Process.ImportCheckingAccountSalesFromDna, + LastSuccessfulRunTime = DateTime.Now, + LastProcessedRecordId = 5, + LastProcessedRecordTimestamp = DateTime.Now, + }); + await _context.SaveChangesAsync(); + Assert.NotNull(res); + Assert.AreEqual(res, Process.ImportCheckingAccountSalesFromDna); + + res = await _repository.SaveProcessorRunHistory(new ProcessorHistoryRecord() + { + Process = Process.ImportGapMbpFromAlliedFile, + LastSuccessfulRunTime = DateTime.Parse("2021-04-05"), + LastProcessedRecordId = 3, + LastProcessedRecordTimestamp = DateTime.Parse("2021-04-05") + }); + await _context.SaveChangesAsync(); + Assert.NotNull(res); + Assert.AreEqual(Process.ImportGapMbpFromAlliedFile, res); + + var list = await _repository.GetAllProcessorRunHistory(); + Assert.NotNull(list); + Assert.AreEqual(2, list.Count); + + //Saving existing Process causes update + res = await _repository.SaveProcessorRunHistory(new ProcessorHistoryRecord() + { + Process = Process.ImportGapMbpFromAlliedFile, + LastSuccessfulRunTime = DateTime.Parse("2021-05-08"), + LastProcessedRecordId = 200, + LastProcessedRecordTimestamp = DateTime.Parse("2021-05-07"), + }); + await _context.SaveChangesAsync(); + + list = await _repository.GetAllProcessorRunHistory(); + + Assert.NotNull(list); + Assert.AreEqual(2, list.Count); + + var updatedHistoryRecord = list.Find(x => x.Process == Process.ImportGapMbpFromAlliedFile); + Assert.AreEqual(200, updatedHistoryRecord.LastProcessedRecordId); + Assert.AreEqual(DateTime.Parse("2021-05-08"), updatedHistoryRecord.LastSuccessfulRunTime); + Assert.AreEqual(DateTime.Parse("2021-05-07"), updatedHistoryRecord.LastProcessedRecordTimestamp); + + updatedHistoryRecord = await _repository.GetProcessorRunHistory(Process.ImportGapMbpFromAlliedFile); + Assert.AreEqual(200, updatedHistoryRecord.LastProcessedRecordId); + Assert.AreEqual(DateTime.Parse("2021-05-08"), updatedHistoryRecord.LastSuccessfulRunTime); + Assert.AreEqual(DateTime.Parse("2021-05-07"), updatedHistoryRecord.LastProcessedRecordTimestamp); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/UserAssignmentRepositoryTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/UserAssignmentRepositoryTest.cs new file mode 100644 index 0000000..871c744 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/UserAssignmentRepositoryTest.cs @@ -0,0 +1,115 @@ +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Microsoft.EntityFrameworkCore; +using Moq; +using NUnit.Framework; +using System; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +namespace Application.UnitTests.Repository +{ + public class UserAssignmentRepositoryTest + { + private ApplicationDbContext _context; + private UserAssignmentRepository _repository; + private Mock _mockDateTime; + + + [SetUp] + public void Setup() + { + _mockDateTime = new Mock(); + var mockCurrentUserService = Mock.Of(); + Mock.Get(mockCurrentUserService).Setup(s => s.GetCurrentUser()).Returns(new ApplicationUser() + { + Email = "JohnTest@test.com", + }); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .EnableSensitiveDataLogging(true) + .Options; + _context = new ApplicationDbContext(options, mockCurrentUserService, _mockDateTime.Object); + _repository = new UserAssignmentRepository(_context); + } + + [Test] + public async Task Basic_Flow_test() + { + var mockDate = new DateTime(2021, 8, 17, 1, 2, 3, 456); + _mockDateTime.SetupGet(mock => mock.Now).Returns(mockDate); + var saved = await _repository.GetAllUserAssignmentsAsync(); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + var newAssignment = new UserAssignment + { + Id = 1, + Key = "key", + Type = AssignmentType.RegionalManager, + AppUser = new AppUser { + BaseRole = "Role", + FullName = "Test", + HomeDepartment = "Test", + Role = Role.Admin, + EmployeeId = "123", + AdEmail = "123@landmarkcu.com", + PersonId = 456 + } + }; + + var newId = await _context.AddAsync(newAssignment); + await _context.SaveChangesAsync(); + + saved = await _repository.GetAllUserAssignmentsAsync(); + Assert.NotNull(saved); + Assert.IsNotEmpty(saved); + var first = saved.First(); + } + + [Test] + public async Task FindExistingAssignment_Match() + { + var mockDate = new DateTime(2021, 8, 17, 1, 2, 3, 456); + _mockDateTime.SetupGet(mock => mock.Now).Returns(mockDate); + var saved = await _repository.GetAllUserAssignmentsAsync(); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + var newAssignment = new UserAssignment + { + Id = 1, + Key = "NORTH", + Type = AssignmentType.RegionalManager, + AppUser = new AppUser + { + BaseRole = "Role", + FullName = "Test", + HomeDepartment = "Test", + Role = Role.Admin, + EmployeeId = "123", + AdEmail = "123@landmarkcu.com", + PersonId = 456 + } + }; + + var newId = await _context.AddAsync(newAssignment); + await _context.SaveChangesAsync(); + + var notFound = await _repository.FindExistingAssignmentAsync(AssignmentType.RegionalManager, "CENTRAL"); + Assert.Null(notFound); + + var found = await _repository.FindExistingAssignmentAsync(AssignmentType.RegionalManager, "NORTH"); + Assert.NotNull(found); + Assert.AreEqual(1, found.Id); + Assert.AreEqual("NORTH", found.Key); + Assert.AreEqual(AssignmentType.RegionalManager, found.Type); + Assert.AreEqual("123", found.AppUser.EmployeeId); + Assert.AreEqual("123@landmarkcu.com", found.AppUser.AdEmail); + Assert.AreEqual(456, found.AppUser.PersonId); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/UserRepositoryTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/UserRepositoryTest.cs new file mode 100644 index 0000000..54ceb6d --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Repository/UserRepositoryTest.cs @@ -0,0 +1,1033 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using Microsoft.EntityFrameworkCore; +using Moq; +using NUnit.Framework; +using System.Collections.Generic; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApiLib; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace Application.UnitTests.Repository +{ + public class UserRepositoryTest + { + private ApplicationDbContext _context; + private UserRepository _repository; + + [SetUp] + public void Setup() + { + var mockDateTime = new DateTimeService(); + var mockCurrentUserService = Mock.Of(); + Mock.Get(mockCurrentUserService) + .Setup(s => s.GetCurrentUser()) + .Returns(new ApplicationUser() + { + Email = "JohnTest@test.com", + }); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .EnableSensitiveDataLogging(true) + .Options; + _context = new ApplicationDbContext(options, mockCurrentUserService, mockDateTime); + _repository = new UserRepository(_context); + } + + [Test] + public async Task Save_Load_Test() + { + var userA = await _repository.SaveUserAsync(new AppUser() + { + AdEmail = "me1@test.com", + PersonId = 321, + EmployeeId = "ABC", + BaseRole = "Developer", + Role = Role.Admin, + HomeDepartment = "Branch-44", + FullName = "Tester, Jane", + Departments = new List() + }); + userA.Departments.Add(new UserDepartment() + { + IsHome = true, + StartDate = DateTime.Today, + Note = "Auto Generated", + Employee = userA, + Role = Role.BusinessLineManager, + Department = new Department() + { + DayforceDepartmentName = "Test", + DayForceGlCode = "Test", + DepartmentName = "Test", + DnaOrgNbr = "1", + BusinessLineCode = BusinessLine.Operations.Code, + State = DepartmentState.Active + } + } + ); + userA.Assignments.Add(new UserAssignment + { + Id = 0, + Key = "ABC", + Type = AssignmentType.RegionalManager + }); + await _context.SaveChangesAsync(); + Assert.NotNull(userA); + + var allUsers = await _repository.GetAllUsersAsync(); + Assert.AreEqual(1, allUsers.Count); + + var user = await _repository.FindUserAsync("ABC", "me1@test.com"); + Assert.NotNull(user); + Assert.AreEqual(user.AdEmail, "me1@test.com"); + Assert.NotNull(user.Departments); + Assert.IsNotEmpty(user.Departments); + Assert.NotNull(user.Assignments); + Assert.IsNotEmpty(user.Assignments); + Assert.NotNull(user.Created); + Assert.NotNull(user.CreatedBy); + Assert.AreEqual(1, user.Assignments.Count); + } + + [Test] + public async Task Basic_Flow_test() + { + var userA = await _repository.SaveUserAsync(new AppUser() + { + AdEmail = "me1@test.com", + PersonId = 321, + EmployeeId = "ABC", + BaseRole = "Developer", + Role = Role.Admin, + HomeDepartment = "Branch-44", + FullName = "Tester, Jane" + }); + await _context.SaveChangesAsync(); + Assert.NotNull(userA); + + var userList = await _repository.SaveUserRangeAsync(new List() + { + new() + { + Id = Guid.NewGuid(), + AdEmail = "me2@test.com", FullName = "Test", + PersonId = 123, + EmployeeId = "CBA", + BaseRole = "Developer", + Role = Role.UNKNOWN_ROLE, + HomeDepartment = "Branch-44", + }, + new() + { + Id = Guid.NewGuid(), + AdEmail = "me3@test.com", FullName = "Test", + PersonId = 456, + EmployeeId = "DEF", + BaseRole = "Developer", + Role = Role.Admin, + HomeDepartment = "Branch-44" + }, + new() + { + Id = Guid.NewGuid(), + AdEmail = "me4@test.com", + FullName = "Test", + PersonId = 789, + EmployeeId = "CONTRACTOR", + BaseRole = "QA", + Role = Role.Teller, + HomeDepartment = "Branch-44" + }, + new() + { + Id = Guid.NewGuid(), + AdEmail = "me5@test.com", + FullName = "Test", + PersonId = 900, + EmployeeId = "CONTRACTOR", + BaseRole = "QA", + Role = Role.BM, + HomeDepartment = "Branch-0" + } + }); + await _context.SaveChangesAsync(); + Assert.NotNull(userList); + Assert.AreEqual(4, userList.Count); + + var allUsers = await _repository.GetAllUsersAsync(); + Assert.AreEqual(5, allUsers.Count); + + var user = await _repository.FindUserAsync("CBA", "me2@test.com"); + Assert.NotNull(user); + Assert.AreEqual(user.AdEmail, "me2@test.com"); + + user = await _repository.FindUserByAdEmailAsync("me5@test.com"); + Assert.NotNull(user); + Assert.IsTrue(user.Role == Role.BM); + + user = await _repository.FindUserByAdEmailAsync("noemail@email.com"); + Assert.Null(user); + user = await _repository.FindUserByEmployeeIdAsync("DEF"); + Assert.NotNull(user); + Assert.IsTrue(user.Role == Role.Admin); + + var users = await _repository.FindUsersByBaseRolesAsync(new List() { "QA" }); + Assert.NotNull(users); + Assert.AreEqual(2, users.Count); + Assert.AreEqual(2, users.Count(x => x.EmployeeId == "CONTRACTOR")); + + users = await _repository.FindUsersByHomeDepartmentsAsync(new List() { "Branch-44" }); + Assert.NotNull(users); + Assert.AreEqual(4, users.Count); + Assert.True(users.Any(x => x.AdEmail != "me5@test.com")); + } + + [Test] + public async Task Find_None_Test() + { + var userList = await _repository.SaveUserRangeAsync(new List() + { + new() + { + Id = Guid.NewGuid(), + AdEmail = "me2@test.com", FullName = "Test", + PersonId = 123, + EmployeeId = "ABC", + BaseRole = "Developer", + Role = Role.UNKNOWN_ROLE, + HomeDepartment = "Branch-44" + }, + new() + { + Id = Guid.NewGuid(), + AdEmail = "me3@test.com", FullName = "Test", + PersonId = 456, + EmployeeId = "DEF", + BaseRole = "Developer", + Role = Role.Admin, + HomeDepartment = "Branch-44" + }, + new() + { + Id = Guid.NewGuid(), + AdEmail = "me4@test.com", FullName = "Test", + PersonId = 789, + EmployeeId = "CONTRACTOR1", + BaseRole = "QA", + Role = Role.Teller, + HomeDepartment = "Branch-44" + }, + new() + { + Id = Guid.NewGuid(), + AdEmail = "me5@test.com", FullName = "Test", + PersonId = 900, + EmployeeId = "CONTRACTOR2", + BaseRole = "QA", + Role = Role.BM, + HomeDepartment = "Branch-0" + } + }); + + await _context.SaveChangesAsync(); + + var results = await _repository.FindUserAsync("CONTRACTOR", "me2@test.com"); + + Assert.Null(results); + } + + [Test] + public async Task search_test() + { + var userList = await _repository.SaveUserRangeAsync(new List() + { + new() + { + Id = Guid.NewGuid(), + AdEmail = "abcQ@test.com", + PersonId = 123, + EmployeeId = "99x", + BaseRole = "Developer", + FullName = "John Doe", + HomeDepartment = "Branch-44", + Role = Role.BusinessLineManager, + Departments = new List() + { + new() + { + Department = new Department() + { + DnaOrgNbr = "51", + DayforceDepartmentName = "Test", + DayForceGlCode = "Test", + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code, + }, + DepartmentId = 3, + Role = Role.UNKNOWN_ROLE + } + } + }, + new() + { + Id = Guid.NewGuid(), + AdEmail = "defQ@test.com", + PersonId = 456, + EmployeeId = "99y", + BaseRole = "Developer", + FullName = "Jane Doe", + HomeDepartment = "Branch-44", + Role = Role.BusinessLineManager, + Departments = new List() + { + new() + { + Department = new Department() + { + DnaOrgNbr = "52", + DayforceDepartmentName = "Test", + DayForceGlCode = "Test", + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code, + }, + DepartmentId = 3, + Role = Role.Admin + } + } + }, + new() + { + Id = Guid.NewGuid(), + AdEmail = "ghiQ@test.com", + PersonId = 789, + EmployeeId = "99z", + BaseRole = "QA", + FullName = "John Deer", + HomeDepartment = "Branch-44", + Role = Role.BusinessLineManager, + Departments = new List() + { + new() + { + Department = new Department() + { + DnaOrgNbr = "53", + DayforceDepartmentName = "Test", + DayForceGlCode = "Test", + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code, + }, + DepartmentId = 3, + Role = Role.Teller + } + } + }, + new() + { + Id = Guid.NewGuid(), + AdEmail = "jklQ@test.com", + PersonId = 900, + EmployeeId = "CONTRACTOR", + FullName = "Emily Random", + BaseRole = "QA", + Role = Role.BusinessLineManager, + Departments = new List() + { + new() + { + Department = new Department() + { + DnaOrgNbr = "54", + DayforceDepartmentName = "Test", + DayForceGlCode = "Test", + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code, + }, + DepartmentId = 3, + Role = Role.BM + } + }, + HomeDepartment = "Branch-0" + }, + new() + { + Id = Guid.NewGuid(), + AdEmail = "JohnDWayne@test.com", + PersonId = 910, + EmployeeId = "314159", + FullName = "Wayne, John D", + BaseRole = "QA", + HomeDepartment = "Branch-0", + Role = Role.BusinessLineManager, + Departments = new List() + { + new() + { + Department = new Department() + { + DnaOrgNbr = "55", + DayforceDepartmentName = "Test", + DayForceGlCode = "Test", + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code, + }, + DepartmentId = 3, + Role = Role.Developer + } + } + } + }); + + await _context.SaveChangesAsync(); + Assert.NotNull(userList); + Assert.AreEqual(5, userList.Count); + + var allUsers = await _repository.GetAllUsersAsync(); + Assert.AreEqual(5, allUsers.Count); + + var searchParameters = new UserSearchRequest { Query = "abc" }; + var userResults = await _repository.SearchUsersAsync(searchParameters); + Assert.NotNull(userResults); + Assert.AreEqual(1, userResults.Count); + Assert.AreEqual(userResults.First().AdEmail, "abcQ@test.com"); + + searchParameters = new UserSearchRequest { Query = "CONTRACTOR" }; + userResults = await _repository.SearchUsersAsync(searchParameters); + Assert.NotNull(userResults); + Assert.AreEqual(1, userResults.Count); + Assert.AreEqual(userResults.First().AdEmail, "jklQ@test.com"); + + searchParameters = new UserSearchRequest { Query = " q " }; + userResults = await _repository.SearchUsersAsync(searchParameters); + Assert.NotNull(userResults); + Assert.AreEqual(4, userResults.Count); + + searchParameters = new UserSearchRequest { Query = " 99 " }; + userResults = await _repository.SearchUsersAsync(searchParameters); + Assert.NotNull(userResults); + Assert.AreEqual(3, userResults.Count); + + searchParameters = new UserSearchRequest { Query = " doe " }; + userResults = await _repository.SearchUsersAsync(searchParameters); + Assert.NotNull(userResults); + Assert.AreEqual(2, userResults.Count); + Assert.AreEqual(userResults.First().FullName, "John Doe"); + + searchParameters = new UserSearchRequest { Role = "UNKNOWN" }; + userResults = await _repository.SearchUsersAsync(searchParameters); + Assert.NotNull(userResults); + Assert.AreEqual(1, userResults.Count); + Assert.AreEqual(userResults.First().FullName, "John Doe"); + + searchParameters = new UserSearchRequest { Role = " BM " }; + userResults = await _repository.SearchUsersAsync(searchParameters); + Assert.NotNull(userResults); + Assert.AreEqual(1, userResults.Count); + Assert.AreEqual(userResults.First().FullName, "Emily Random"); + + searchParameters = new UserSearchRequest { Role = "Manager" }; + userResults = await _repository.SearchUsersAsync(searchParameters); + Assert.NotNull(userResults); + Assert.AreEqual(1, userResults.Count); + Assert.AreEqual(userResults.First().FullName, "Emily Random"); + + searchParameters = new UserSearchRequest { Query = "Fq, De" }; + userResults = await _repository.SearchUsersAsync(searchParameters); + Assert.NotNull(userResults); + Assert.AreEqual(1, userResults.Count); + Assert.AreEqual(userResults.First().FullName, "Jane Doe"); + + searchParameters = new UserSearchRequest { DnaOrgNbr = "6" }; + userResults = await _repository.SearchUsersAsync(searchParameters); + Assert.NotNull(userResults); + Assert.AreEqual(0, userResults.Count); + + searchParameters = new UserSearchRequest { DnaOrgNbr = "55" }; + userResults = await _repository.SearchUsersAsync(searchParameters); + Assert.NotNull(userResults); + Assert.AreEqual(1, userResults.Count); + Assert.AreEqual(userResults.First().FullName, "Wayne, John D"); + + searchParameters = new UserSearchRequest { FirstName = "John", LastName = "Wayne", DnaOrgNbr = "55" }; + userResults = await _repository.SearchUsersAsync(searchParameters); + Assert.NotNull(userResults); + Assert.AreEqual(1, userResults.Count); + Assert.AreEqual(userResults.First().FullName, "Wayne, John D"); + + searchParameters = new UserSearchRequest { FirstName = "John", MiddleInit = "D" }; + userResults = await _repository.SearchUsersAsync(searchParameters); + Assert.NotNull(userResults); + Assert.AreEqual(1, userResults.Count); + Assert.AreEqual(userResults.First().FullName, "Wayne, John D"); + + searchParameters = new UserSearchRequest { Query = "," }; + userResults = await _repository.SearchUsersAsync(searchParameters); + Assert.NotNull(userResults); + Assert.AreEqual(5, userResults.Count); + } + + [Test] + public async Task users_for_payouts() + { + var startDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); + var range = new MonthRange(DateTime.Today.Year, DateTime.Today.Month); + var users = new List() + { + new() + { + Id = Guid.NewGuid(), + AdEmail = "abcQ@test.com", + PersonId = 123, + EmployeeId = "99x", + BaseRole = "TERMINATED - No", + FullName = "John Terminated", + HomeDepartment = "Branch-44", + Terminated = DateTime.Today.Subtract(TimeSpan.FromDays(30)), + Role = Role.BusinessLineManager, + Departments = new List() + { + new() + { + Department = new Department() + { + DnaOrgNbr = "51", + DayforceDepartmentName = "Test", + DayForceGlCode = "Test", + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code, + }, + DepartmentId = 3, + Role = Role.UNKNOWN_ROLE + } + } + }, + new() + { + Id = Guid.NewGuid(), + AdEmail = "defQ@test.com", + PersonId = 456, + EmployeeId = "99y", + BaseRole = "Developer New This Month - Yes", + FullName = "Jane Active", + HomeDepartment = "Branch-44", + Role = Role.BusinessLineManager, + Departments = new List() + { + new() + { + Department = new Department() + { + DnaOrgNbr = "52", + DayforceDepartmentName = "Test", + DayForceGlCode = "Test", + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code, + }, + DepartmentId = 3, + Role = Role.Admin, + StartDate = startDate, + EndDate = null + } + } + }, + new() + { + Id = Guid.NewGuid(), + AdEmail = "ghiQ@test.com", + PersonId = 789, + EmployeeId = "99z", + BaseRole = "QA Active 90 days - Yes", + FullName = "John Deer", + HomeDepartment = "Branch-44", + Role = Role.BusinessLineManager, + Departments = new List() + { + new() + { + Department = new Department() + { + DnaOrgNbr = "53", + DayforceDepartmentName = "Test", + DayForceGlCode = "Test", + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code, + }, + DepartmentId = 3, + Role = Role.Teller, + StartDate = startDate.Subtract(TimeSpan.FromDays(90)), + EndDate = range.EndTime + } + } + }, + new() + { + Id = Guid.NewGuid(), + AdEmail = "jklQ@test.com", + PersonId = 900, + EmployeeId = "CONTRACTOR", + FullName = "Emily Random", + BaseRole = "QA Not Active Yet? - No", + Role = Role.BusinessLineManager, + Departments = new List() + { + new() + { + Department = new Department() + { + DnaOrgNbr = "54", + DayforceDepartmentName = "Test", + DayForceGlCode = "Test", + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code, + }, + DepartmentId = 3, + Role = Role.BM, + StartDate = startDate.Add(TimeSpan.FromDays(90)), + EndDate = null + } + }, + HomeDepartment = "Branch-0" + }, + new() + { + Id = Guid.NewGuid(), + AdEmail = "JohnDWayne@test.com", + PersonId = 910, + EmployeeId = "314159", + FullName = "Wayne, John D", + BaseRole = "QA Exact - Yes", + HomeDepartment = "Branch-0", + Role = Role.BusinessLineManager, + Departments = new List() + { + new() + { + Department = new Department() + { + DnaOrgNbr = "55", + DayforceDepartmentName = "Test", + DayForceGlCode = "Test", + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code, + }, + DepartmentId = 3, + Role = Role.Developer, + StartDate = startDate, + EndDate = startDate.AddMonths(1).Subtract(TimeSpan.FromSeconds(1)) + } + } + } + }; + var userList = await _repository.SaveUserRangeAsync(users); + + await _context.SaveChangesAsync(); + Assert.NotNull(userList); + Assert.AreEqual(5, userList.Count); + + var allUsers = await _repository.GetAllUsersToPayoutAsync(range); + Assert.AreEqual(3, allUsers.Count); + var selected = allUsers.Select(x => x.EmployeeId).ToList(); + var expected = new List() + { + "99y", "99z", "314159" + }; + Assert.AreEqual(selected, expected); + } + + [Test] + public async Task TerminatedUsers() + { + var startDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); + var inThePast = DateTime.Today.Subtract(TimeSpan.FromDays(DateTime.DaysInMonth(DateTime.Now.Year, DateTime.Now.Month) != 31 ? 30 : 31)); + var range = new MonthRange(inThePast.Year, inThePast.Month); + var users = new List() + { + new() + { + Id = Guid.NewGuid(), + AdEmail = "abcQ@test.com", + PersonId = 123, + EmployeeId = "99x", + BaseRole = "TERMINATED - No", + FullName = "John Terminated", + HomeDepartment = "Branch-44", + Terminated = DateTime.Today.Subtract(TimeSpan.FromDays(DateTime.DaysInMonth(DateTime.Now.Year, DateTime.Now.Month) != 31 ? 30 : 31)), + Role = Role.BusinessLineManager, + Departments = new List() + { + new() + { + Department = new Department() + { + DnaOrgNbr = "51", + DayforceDepartmentName = "Test", + DayForceGlCode = "Test", + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code, + }, + DepartmentId = 3, + Role = Role.UNKNOWN_ROLE + } + } + }, + new() + { + Id = Guid.NewGuid(), + AdEmail = "defQ@test.com", + PersonId = 456, + EmployeeId = "99y", + BaseRole = "Developer New This Month - Yes", + FullName = "Jane Active", + HomeDepartment = "Branch-44", + Terminated = DateTime.Today, + Role = Role.BusinessLineManager, + Departments = new List() + { + new() + { + Department = new Department() + { + DnaOrgNbr = "52", + DayforceDepartmentName = "Test", + DayForceGlCode = "Test", + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code, + }, + DepartmentId = 3, + Role = Role.Admin, + StartDate = startDate, + EndDate = null + } + } + }, + new() + { + Id = Guid.NewGuid(), + AdEmail = "ghiQ@test.com", + PersonId = 789, + EmployeeId = "99z", + BaseRole = "QA Active 90 days - Yes", + FullName = "John Deer", + HomeDepartment = "Branch-44", + Role = Role.BusinessLineManager, + Departments = new List() + { + new() + { + Department = new Department() + { + DnaOrgNbr = "53", + DayforceDepartmentName = "Test", + DayForceGlCode = "Test", + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code, + }, + DepartmentId = 3, + Role = Role.Teller, + StartDate = startDate.Subtract(TimeSpan.FromDays(90)), + EndDate = range.EndTime + } + } + }, + new() + { + Id = Guid.NewGuid(), + AdEmail = "jklQ@test.com", + PersonId = 900, + EmployeeId = "CONTRACTOR", + FullName = "Emily Random", + BaseRole = "QA Not Active Yet? - No", + Role = Role.BusinessLineManager, + Departments = new List() + { + new() + { + Department = new Department() + { + DnaOrgNbr = "54", + DayforceDepartmentName = "Test", + DayForceGlCode = "Test", + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code, + }, + DepartmentId = 3, + Role = Role.BM, + StartDate = startDate.Add(TimeSpan.FromDays(90)), + EndDate = null + } + }, + HomeDepartment = "Branch-0" + }, + new() + { + Id = Guid.NewGuid(), + AdEmail = "JohnDWayne@test.com", + PersonId = 910, + EmployeeId = "314159", + FullName = "Wayne, John D", + BaseRole = "QA Exact - Yes", + HomeDepartment = "Branch-0", + Role = Role.BusinessLineManager, + Departments = new List() + { + new() + { + Department = new Department() + { + DnaOrgNbr = "55", + DayforceDepartmentName = "Test", + DayForceGlCode = "Test", + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code, + }, + DepartmentId = 3, + Role = Role.Developer, + StartDate = startDate, + EndDate = startDate.AddMonths(1).Subtract(TimeSpan.FromSeconds(1)) + } + } + } + }; + var userList = await _repository.SaveUserRangeAsync(users); + await _context.SaveChangesAsync(); + Assert.NotNull(userList); + Assert.AreEqual(5, userList.Count); + + var termList = _repository.GetTerminatedUsersAsync(range); + Assert.AreEqual(1, termList.Count); + var selected = termList.Select(x => x.EmployeeId).ToList(); + var expected = new List() + { + "99x" + }; + Assert.AreEqual(selected, expected); + + range = new MonthRange(DateTime.Today.Year, DateTime.Today.Month); + termList = _repository.GetTerminatedUsersAsync(range); + Assert.AreEqual(1, termList.Count); + selected = termList.Select(x => x.EmployeeId).ToList(); + expected = new List() + { + "99y" + }; + Assert.AreEqual(selected, expected); + + range = new MonthRange(inThePast.Year, inThePast.Month, DateTime.Today.Year, DateTime.Today.Month); + Assert.Throws(() => { _repository.GetTerminatedUsersAsync(range); }); + } + + [Test] + public async Task GetActiveEmployeeReturnsNothingWhenOnlyMatchingInfoAppUserHasNonNullTermDate() + { + var startDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); + var inThePast = DateTime.Today.Subtract(TimeSpan.FromDays(30)); + var range = new MonthRange(inThePast.Year, inThePast.Month); + + const string termedEmployeeQueryEmail = "defQ@test.com"; + const string termedEmployeeQueryEmployeeId = "99y"; + var laterTermedEmployeeRecordGuid = Guid.NewGuid(); + var users = new List() + { + new() + { + Id = Guid.NewGuid(), + AdEmail = "abcQ@test.com", + PersonId = 123, + EmployeeId = "99x", + BaseRole = "TERMINATED - No", + FullName = "John Terminated", + HomeDepartment = "Branch-44", + Terminated = DateTime.Today.Subtract(TimeSpan.FromDays(30)), + Role = Role.BusinessLineManager, + Departments = new List() + { + new() + { + Department = new Department() + { + DnaOrgNbr = "51", + DayforceDepartmentName = "Test", + DayForceGlCode = "Test", + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code, + }, + DepartmentId = 3, + Role = Role.UNKNOWN_ROLE + } + } + }, + new() + { + Id = laterTermedEmployeeRecordGuid, + AdEmail = termedEmployeeQueryEmail, + PersonId = 456, + EmployeeId = termedEmployeeQueryEmployeeId, + BaseRole = "Developer New This Month - Yes", + FullName = "Jane Active", + HomeDepartment = "Branch-44", + Terminated = DateTime.Today, + Role = Role.BusinessLineManager, + Departments = new List() + { + new() + { + Department = new Department() + { + DnaOrgNbr = "52", + DayforceDepartmentName = "Test", + DayForceGlCode = "Test", + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code, + }, + DepartmentId = 3, + Role = Role.Admin, + StartDate = startDate, + EndDate = null + } + } + }, + new() + { + Id = Guid.NewGuid(), + AdEmail = termedEmployeeQueryEmail, + PersonId = 456, + EmployeeId = termedEmployeeQueryEmployeeId, + BaseRole = "Developer New This Month - Yes", + FullName = "Jane Active", + HomeDepartment = "Branch-44", + Terminated = DateTime.Today.AddDays(-10), + Role = Role.BusinessLineManager, + Departments = new List() + { + new() + { + Department = new Department() + { + DnaOrgNbr = "52", + DayforceDepartmentName = "Test", + DayForceGlCode = "Test", + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code, + }, + DepartmentId = 3, + Role = Role.Admin, + StartDate = startDate, + EndDate = null + } + } + }, + new() + { + Id = Guid.NewGuid(), + AdEmail = "ghiQ@test.com", + PersonId = 789, + EmployeeId = "99z", + BaseRole = "QA Active 90 days - Yes", + FullName = "John Deer", + HomeDepartment = "Branch-44", + Role = Role.BusinessLineManager, + Departments = new List() + { + new() + { + Department = new Department() + { + DnaOrgNbr = "53", + DayforceDepartmentName = "Test", + DayForceGlCode = "Test", + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code, + }, + DepartmentId = 3, + Role = Role.Teller, + StartDate = startDate.Subtract(TimeSpan.FromDays(90)), + EndDate = range.EndTime + } + } + }, + new() + { + Id = Guid.NewGuid(), + AdEmail = "jklQ@test.com", + PersonId = 900, + EmployeeId = "CONTRACTOR", + FullName = "Emily Random", + BaseRole = "QA Not Active Yet? - No", + Role = Role.BusinessLineManager, + Departments = new List() + { + new() + { + Department = new Department() + { + DnaOrgNbr = "54", + DayforceDepartmentName = "Test", + DayForceGlCode = "Test", + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code, + }, + DepartmentId = 3, + Role = Role.BM, + StartDate = startDate.Add(TimeSpan.FromDays(90)), + EndDate = null + } + }, + HomeDepartment = "Branch-0" + }, + new() + { + Id = Guid.NewGuid(), + AdEmail = "JohnDWayne@test.com", + PersonId = 910, + EmployeeId = "314159", + FullName = "Wayne, John D", + BaseRole = "QA Exact - Yes", + HomeDepartment = "Branch-0", + Role = Role.BusinessLineManager, + Departments = new List() + { + new() + { + Department = new Department() + { + DnaOrgNbr = "55", + DayforceDepartmentName = "Test", + DayForceGlCode = "Test", + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code, + }, + DepartmentId = 3, + Role = Role.Developer, + StartDate = startDate, + EndDate = startDate.AddMonths(1).Subtract(TimeSpan.FromSeconds(1)) + } + } + } + }; + var userList = await _repository.SaveUserRangeAsync(users); + await _context.SaveChangesAsync(); + Assert.NotNull(userList); + Assert.AreEqual(6, userList.Count); + + var termedShouldBeNullEmployeeEmailResult = await _repository.FindActiveUserByAdEmailAsync(termedEmployeeQueryEmail); + var termedShouldBeNullEmployeeEmployeeIdResult = await _repository.FindActiveUserByEmployeeIdAsync(termedEmployeeQueryEmployeeId); + Assert.IsNull(termedShouldBeNullEmployeeEmailResult); + Assert.IsNull(termedShouldBeNullEmployeeEmployeeIdResult); + + var termedShouldNotBeNullEmployeeEmailResult = await _repository.FindUserByAdEmailAsync(termedEmployeeQueryEmail); + var termedShouldNotBeNullEmployeeEmployeeIdResult = await _repository.FindUserByEmployeeIdAsync(termedEmployeeQueryEmployeeId); + Assert.IsNotNull(termedShouldNotBeNullEmployeeEmailResult); + Assert.IsNotNull(termedShouldNotBeNullEmployeeEmployeeIdResult); + + Assert.AreEqual(termedShouldNotBeNullEmployeeEmailResult.Id, laterTermedEmployeeRecordGuid); + Assert.AreEqual(termedShouldNotBeNullEmployeeEmployeeIdResult.Id, laterTermedEmployeeRecordGuid); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Action/ActionCreditCardImportServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Action/ActionCreditCardImportServiceTest.cs new file mode 100644 index 0000000..7940b33 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Action/ActionCreditCardImportServiceTest.cs @@ -0,0 +1,382 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using CsvHelper; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApi.Services.Action; +using LCU.API.Internal.Operations.IncentiveApi.Models.Action; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.Library.Internal.FileOperationsModels.Models.NameSettings; +using LCU.Library.Internal.FileOperationsModels.Models; + +namespace Application.UnitTests.Services.Action +{ + public class ActionCreditCardImportServiceTest + { + private IActionCreditCardImportService _actionCreditCardImportService; + private IUserRepository _userRepository; + private IActivityService _activityService; + private IFileOperationsApiService _fileOperationsApiService; + private Mock _deadActivityService; + + [SetUp] + public void Setup() + { + _userRepository = Mock.Of(); + _fileOperationsApiService = Mock.Of(); + _activityService = Mock.Of(); + _deadActivityService = new Mock(); + _actionCreditCardImportService = new ActionCreditCardImportService(_userRepository, _fileOperationsApiService, _activityService,_deadActivityService.Object); + } + + private static StreamReader GetActionFile() + { + var path = @"Services/Action/Action_CreditCardPersonal.csv"; + var reader = new StreamReader(path); + return reader; + } + + [Test] + public async Task ProcessFileNames_OK() + { + var department = new Department + { + DepartmentName = "Dept1", + Id = 55, + State = DepartmentState.Active + }; + var user = new AppUser + { + AdEmail = "johndoe@test.com", + EmployeeId = "11771", + FullName = "John Doe", + Departments = new List() + }; + var assignment = new UserDepartment + { + Id = 99, + Department = department, + Employee = user, + Role = Role.BM, + IsHome = true, + StartDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1), + EndDate = null + }; + user.Departments.Add(assignment); + + var request = new ActionCreditCardRequest + { + DynamicFileName = "credit", + FilePath = "clean", + DynamicFileSettings = new DynamicNameSetting() { } + }; + + var files = new[] + { + "x" + }; + + var array = await File.ReadAllBytesAsync( @"Services/Action/Action_CreditCardPersonal.csv"); + + //Setup + Mock.Get(_fileOperationsApiService).Setup(x => x.BuildDynamicFileList(It.IsAny())).ReturnsAsync(files); + Mock.Get(_fileOperationsApiService).Setup(x => x.GetFileContent(It.IsAny())).ReturnsAsync(array.ToString()); + Mock.Get(_userRepository).Setup(s => s.FindUserByAdEmailAsync(It.IsAny())).ReturnsAsync(user); + Mock.Get(_activityService).Setup(x => x.SaveActivityRangeAsync(It.IsAny>())).Verifiable(); + + await _actionCreditCardImportService.ImportTransactions("personal", request); + + Mock.Get(_fileOperationsApiService).Verify(x => x.GetFileContent(It.IsAny()), Times.Once); + Mock.Get(_activityService).Verify(x => x.SaveActivityRangeAsync(It.IsAny>()), Times.Once); + } + + [Test] + public async Task ProcessCsv_OK() + { + var department = new Department + { + DepartmentName = "Dept1", + Id = 55, + State = DepartmentState.Active + }; + var user = new AppUser + { + AdEmail = "johndoe@test.com", + EmployeeId = "11771", + FullName = "John Doe", + Departments = new List() + }; + var assignment = new UserDepartment + { + Id = 99, + Department = department, + Employee = user, + Role = Role.BM, + IsHome = true, + StartDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1), + EndDate = null + }; + user.Departments.Add(assignment); + + Mock.Get(_userRepository).Setup(s => s.FindUserByAdEmailAsync(It.IsAny())).ReturnsAsync(user); + + var transactions = ImportTestTransactions(); + Assert.NotNull(transactions); + var actionCreditCardPersonalRows = transactions.ToList(); + Assert.IsNotEmpty(actionCreditCardPersonalRows); + Assert.AreEqual(15, actionCreditCardPersonalRows.Count); + + // Should lose the duplicate. + var results = await _actionCreditCardImportService.CreateActivity(actionCreditCardPersonalRows); + Assert.NotNull(results); + Assert.AreEqual(14, results.Count); + } + + private IEnumerable ImportTestTransactions() + { + var input = GetActionFile(); + var csvReader = new CsvReader(input, CultureInfo.InvariantCulture); + return csvReader.GetRecords().ToList(); + } + + [Test] + public async Task SavesDeadActivity_WhenNoUserFound() + { + Mock.Get(_userRepository).Setup(s => s.FindUserByAdEmailAsync(It.IsAny())).ReturnsAsync((AppUser)null); + + var transactions = ImportTestTransactions(); + Assert.NotNull(transactions); + var actionCreditCardPersonalRows = transactions.ToList(); + Assert.IsNotEmpty(actionCreditCardPersonalRows); + Assert.AreEqual(15, actionCreditCardPersonalRows.Count); + + var results = await _actionCreditCardImportService.CreateActivity(actionCreditCardPersonalRows); + Assert.NotNull(results); + Assert.IsEmpty(results); + + _deadActivityService.Verify(x => x.SaveDeadActivityRange(It.Is>( + da => + da.Count == 15 && + da.All(y => y.Reason == DeadActivityReason.MissingRequiredField) && + da.All(y => y.ReasonNote.ToLowerInvariant().Contains("action import, no user for"))))); + + //Our test CSV has email information for every row except 1 + _deadActivityService.Verify(x =>x.SaveDeadActivityRange(It.Is>( + da => + da.Count(deadActivity => !string.IsNullOrWhiteSpace(deadActivity.EmployeeEmail)) == 14))); + } + + [Test] + public async Task SavesDeadActivity_WhenFindingUserFails() + { + Mock.Get(_userRepository).Setup(s => s.FindUserByAdEmailAsync(It.IsAny())) + .Throws(new Exception("OOops")); + + var transactions = ImportTestTransactions(); + Assert.NotNull(transactions); + var actionCreditCardPersonalRows = transactions.ToList(); + Assert.IsNotEmpty(actionCreditCardPersonalRows); + Assert.AreEqual(15, actionCreditCardPersonalRows.Count); + + var results = await _actionCreditCardImportService.CreateActivity(actionCreditCardPersonalRows); + Assert.NotNull(results); + Assert.IsEmpty(results); + + _deadActivityService.Verify(x => x.SaveDeadActivityRange(It.Is>( + da => + da.Count == 15 && + da.All(y => y.Reason == DeadActivityReason.MissingRequiredField) && + da.All(y => y.ReasonNote.ToLowerInvariant().Contains("action import, no user for"))))); + + //Our test CSV has email information for every row except 1 + _deadActivityService.Verify(x =>x.SaveDeadActivityRange(It.Is>( + da => + da.Count(deadActivity => !string.IsNullOrWhiteSpace(deadActivity.EmployeeEmail)) == 14))); + } + + [Test] + public async Task SavesDeadActivity_WhenNoHomeDepartmentFound() + { + //Use one user for all lines for simplicity + var user = new AppUser + { + AdEmail = "johndoe@test.com", + EmployeeId = "11771", + FullName = "John Doe", + Departments = new List() + }; + + Mock.Get(_userRepository).Setup(s => s.FindUserByAdEmailAsync(It.IsAny())).ReturnsAsync(user); + + var transactions = ImportTestTransactions(); + Assert.NotNull(transactions); + var actionCreditCardPersonalRows = transactions.ToList(); + Assert.IsNotEmpty(actionCreditCardPersonalRows); + Assert.AreEqual(15, actionCreditCardPersonalRows.Count); + + var results = await _actionCreditCardImportService.CreateActivity(actionCreditCardPersonalRows); + Assert.NotNull(results); + Assert.IsEmpty(results); + + _deadActivityService.Verify(x => x.SaveDeadActivityRange(It.Is>( + da => + da.Count == 15 && + da.All(y => y.Reason == DeadActivityReason.MissingRequiredField) && + da.All(y => y.ReasonNote.ToLowerInvariant().Contains("reliably determinable home"))))); + + } + + [Test] + public async Task SavesDeadActivity_MissingEmployeeId_MissingDepartmentId() + { + var user = new AppUser + { + AdEmail = "johndoe@test.com", + EmployeeId = "11771", + FullName = "John Doe", + Departments = new List() + }; + + //There are 2 entries with this email, they will have a user but no home department + Mock.Get(_userRepository).Setup(s => s.FindUserByAdEmailAsync(It.Is(x => x.ToLowerInvariant() == "michaelnembhard@landmarkcu.com"))).ReturnsAsync(user); + + var transactions = ImportTestTransactions(); + Assert.NotNull(transactions); + var actionCreditCardPersonalRows = transactions.ToList(); + Assert.IsNotEmpty(actionCreditCardPersonalRows); + Assert.AreEqual(15, actionCreditCardPersonalRows.Count); + + var results = await _actionCreditCardImportService.CreateActivity(actionCreditCardPersonalRows); + Assert.NotNull(results); + Assert.IsEmpty(results); + + _deadActivityService.Verify(x => x.SaveDeadActivityRange(It.Is>( + da => + da.Count(y => + y.Reason == DeadActivityReason.MissingRequiredField && + y.ReasonNote.ToLowerInvariant().Contains("action import, no user for")) == 13))); + + _deadActivityService.Verify(x => x.SaveDeadActivityRange(It.Is>( + da => + da.Count(y => + y.Reason == DeadActivityReason.MissingRequiredField && + y.ReasonNote.ToLowerInvariant().Contains("reliably determinable home")) == 2))); + } + + [Test] + public async Task SomeActivitiesAreDeadJim_Ok() + { + var john = new AppUser + { + AdEmail = "johndoe@test.com", + EmployeeId = "John", + FullName = "John Doe", + Departments = new List() + }; + //There are 2 entries with this email, they will have a user but no home department + Mock.Get(_userRepository).Setup(s => s.FindUserByAdEmailAsync(It.Is(x => x.ToLowerInvariant() == "michaelnembhard@landmarkcu.com"))).ReturnsAsync(john); + + var department = new Department + { + DepartmentName = "Dept1", + Id = 55, + State = DepartmentState.Active + }; + var mary = new AppUser + { + AdEmail = "marydeer@test.com", + EmployeeId = "Mary", + FullName = "Mary Deer", + Departments = new List() + }; + var assignment = new UserDepartment + { + Id = 99, + Department = department, + Employee = mary, + Role = Role.BM, + IsHome = true, + StartDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1), + EndDate = null + }; + mary.Departments.Add(assignment); + //There are 2 entries with this email, they will not have a home department assigned + Mock.Get(_userRepository).Setup(s => s.FindUserByAdEmailAsync(It.Is(x => x.ToLowerInvariant() == "michaelnembhard@landmarkcu.com"))).ReturnsAsync(john); + //There are 2 entries with this email, they will have a home department assigned + Mock.Get(_userRepository).Setup(s => s.FindUserByAdEmailAsync(It.Is(x => x.ToLowerInvariant() == "lauriepaulos@landmarkcu.com"))).ReturnsAsync(mary); + + //Everyone else won't exist + var transactions = ImportTestTransactions(); + Assert.NotNull(transactions); + var actionCreditCardPersonalRows = transactions.ToList(); + Assert.IsNotEmpty(actionCreditCardPersonalRows); + Assert.AreEqual(15, actionCreditCardPersonalRows.Count); + + //Mary should have 2 successful results + var results = await _actionCreditCardImportService.CreateActivity(actionCreditCardPersonalRows); + Assert.NotNull(results); + Assert.AreEqual(2,results.Count); + Assert.True(results.All(x => x.EmployeeId == "Mary")); + + //John should have 2 entries in dead activity for not having a home department + _deadActivityService.Verify(x => x.SaveDeadActivityRange(It.Is>( + da => + da.Count(y => + y.EmployeeId == john.EmployeeId && + y.Reason == DeadActivityReason.MissingRequiredField && + y.ReasonNote.ToLowerInvariant().Contains("reliably determinable home")) == 2))); + + //Everyone else could not be identified + _deadActivityService.Verify(x => x.SaveDeadActivityRange(It.Is>( + da => + da.Count(y => + (y.EmployeeId != john.EmployeeId && y.EmployeeId != mary.EmployeeId) && + y.Reason == DeadActivityReason.MissingRequiredField && + y.ReasonNote.ToLowerInvariant().Contains("action import, no user for")) == 11))); + } + + [Test] + public void FailToSaveDeadActivity_DoesntThrow() + { + Mock.Get(_userRepository).Setup(s => s.FindUserByAdEmailAsync(It.IsAny())).ReturnsAsync((AppUser)null); + _deadActivityService.Setup(x => x.SaveDeadActivityRange(It.IsAny>())) + .ThrowsAsync(new Exception("Oops")); + + var transactions = ImportTestTransactions(); + Assert.NotNull(transactions); + var actionCreditCardPersonalRows = transactions.ToList(); + Assert.IsNotEmpty(actionCreditCardPersonalRows); + Assert.AreEqual(15, actionCreditCardPersonalRows.Count); + + List results = null; + Assert.DoesNotThrowAsync(async () => + { + var res = await _actionCreditCardImportService.CreateActivity(actionCreditCardPersonalRows); + results = res.ToList(); + }); + Assert.NotNull(results); + Assert.IsEmpty(results); + + _deadActivityService.Verify(x => x.SaveDeadActivityRange(It.Is>( + da => + da.Count == 15 && + da.All(y => y.Reason == DeadActivityReason.MissingRequiredField) && + da.All(y => y.ReasonNote.ToLowerInvariant().Contains("action import, no user for"))))); + + //Our test CSV has email information for every row except 1 + _deadActivityService.Verify(x =>x.SaveDeadActivityRange(It.Is>( + da => + da.Count(deadActivity => !string.IsNullOrWhiteSpace(deadActivity.EmployeeEmail)) == 14))); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Action/ActionVehicleImportServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Action/ActionVehicleImportServiceTest.cs new file mode 100644 index 0000000..2f4326d --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Action/ActionVehicleImportServiceTest.cs @@ -0,0 +1,374 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using CsvHelper; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApi.Services.Action; +using LCU.API.Internal.Operations.IncentiveApi.Models.Action; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.FileOperationsModels.Models; +using LCU.Library.Internal.FileOperationsModels.Models.NameSettings; + +namespace Application.UnitTests.Services.Action +{ + public class ActionVehicleImportServiceTest + { + private IActionVehicleImportService _service; + private IUserRepository _userRepository; + private IActivityService _activityService; + private IFileOperationsApiService _fileOperationsApiService; + private Mock _deadActivityService; + [SetUp] + public void Setup() + { + _userRepository = Mock.Of(); + _fileOperationsApiService = Mock.Of(); + _activityService = Mock.Of(); + _deadActivityService = new Mock(); + _service = new ActionVehicleImportService(_userRepository, _fileOperationsApiService, _activityService, _deadActivityService.Object); + } + + public StreamReader GetActionFile() + { + var path = @"Services/Action/Action_IndirectVehicleLoanSales.csv"; + var reader = new StreamReader(path); + return reader; + } + + [Test] + public async Task ProcessFileNames_OK() + { + var department = new Department() + { + DepartmentName = "Dept1", + Id = 55, + State = DepartmentState.Active + }; + var user = new AppUser() + { + AdEmail = "johndoe@test.com", + EmployeeId = "11771", + FullName = "John Doe", + Departments = new List() + }; + var assignment = new UserDepartment() + { + Id = 99, + Department = department, + Employee = user, + Role = Role.BM, + IsHome = true, + StartDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1), + EndDate = null + }; + user.Departments.Add(assignment); + + var request = new ActionVehicleRequest + { + DynamicFileName = "vehiclesales", + FilePath = "clean", + DynamicFileSettings = new DynamicNameSetting() { } + }; + + var files = new string[] + { + "x" + }; + + byte[] array = File.ReadAllBytes( @"Services/Action/Action_IndirectVehicleLoanSales.csv"); + + //Setup + Mock.Get(_fileOperationsApiService).Setup(x => x.BuildDynamicFileList(It.IsAny())).ReturnsAsync(files); + Mock.Get(_fileOperationsApiService).Setup(x => x.GetFileContent(It.IsAny())).ReturnsAsync(array.ToString()); + Mock.Get(_userRepository).Setup(s => s.FindUserByAdEmailAsync(It.IsAny())).ReturnsAsync(user); + Mock.Get(_activityService).Setup(x => x.SaveActivityRangeAsync(It.IsAny>())).Verifiable(); + + await _service.ImportTransactions(request); + + Mock.Get(_fileOperationsApiService).Verify(x => x.GetFileContent(It.IsAny()), Times.Once); + Mock.Get(_activityService).Verify(x => x.SaveActivityRangeAsync(It.IsAny>()), Times.Once); + } + + [Test] + public async Task ProcessCsv_OK() + { + var department = new Department() + { + DepartmentName = "Dept1", + Id = 55, + State = DepartmentState.Active + }; + var user = new AppUser() + { + AdEmail = "johndoe@test.com", + EmployeeId = "11771", + FullName = "John Doe", + Departments = new List() + }; + var assignment = new UserDepartment() + { + Id = 99, + Department = department, + Employee = user, + Role = Role.BM, + IsHome = true, + StartDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1), + EndDate = null + }; + user.Departments.Add(assignment); + + Mock.Get(_userRepository).Setup(s => s.FindUserByAdEmailAsync(It.IsAny())).ReturnsAsync(user); + + + var transactions = ImportTestTransactions(); + Assert.NotNull(transactions); + Assert.IsNotEmpty(transactions); + Assert.AreEqual(7, transactions.Count()); + + var results = await _service.CreateActivity(transactions); + Assert.NotNull(results); + Assert.AreEqual(7, results.Count()); + } + + public IEnumerable ImportTestTransactions() + { + var input = GetActionFile(); + var csvReader = new CsvReader(input, CultureInfo.InvariantCulture); + return csvReader.GetRecords().ToList(); + } + + [Test] + public async Task SavesDeadActivity_WhenNoUserFound() + { + Mock.Get(_userRepository).Setup(s => s.FindUserByAdEmailAsync(It.IsAny())).ReturnsAsync((AppUser)null); + + var transactions = ImportTestTransactions(); + Assert.NotNull(transactions); + Assert.IsNotEmpty(transactions); + Assert.AreEqual(7, transactions.Count()); + + var results = await _service.CreateActivity(transactions); + Assert.NotNull(results); + Assert.IsEmpty(results); + + _deadActivityService.Verify(x => x.SaveDeadActivityRange(It.Is>( + da => + da.Count() == 7 && + da.All(y => y.Reason == DeadActivityReason.MissingRequiredField) && + da.All(y => y.ReasonNote.ToLowerInvariant().Contains("action import, no user for"))))); + + //Our test CSV has email information for every row except 1 + _deadActivityService.Verify(x =>x.SaveDeadActivityRange(It.Is>( + da => + da.Count(x => !string.IsNullOrWhiteSpace(x.EmployeeEmail)) == 6))); + } + + [Test] + public async Task SavesDeadActivity_WhenFindingUserFails() + { + Mock.Get(_userRepository).Setup(s => s.FindUserByAdEmailAsync(It.IsAny())) + .Throws(new Exception("OOops")); + + var transactions = ImportTestTransactions(); + Assert.NotNull(transactions); + Assert.IsNotEmpty(transactions); + Assert.AreEqual(7, transactions.Count()); + + var results = await _service.CreateActivity(transactions); + Assert.NotNull(results); + Assert.IsEmpty(results); + + _deadActivityService.Verify(x => x.SaveDeadActivityRange(It.Is>( + da => + da.Count() == 7 && + da.All(y => y.Reason == DeadActivityReason.MissingRequiredField) && + da.All(y => y.ReasonNote.ToLowerInvariant().Contains("action import, no user for"))))); + + //Our test CSV has email information for every row except 1 + _deadActivityService.Verify(x =>x.SaveDeadActivityRange(It.Is>( + da => + da.Count(x => !string.IsNullOrWhiteSpace(x.EmployeeEmail)) == 6))); + } + + [Test] + public async Task SavesDeadActivity_WhenNoHomeDepartmentFound() + { + //Use one user for all lines for simplicity + var user = new AppUser() + { + AdEmail = "johndoe@test.com", + EmployeeId = "11771", + FullName = "John Doe", + Departments = new List() + }; + + Mock.Get(_userRepository).Setup(s => s.FindUserByAdEmailAsync(It.IsAny())).ReturnsAsync(user); + + var transactions = ImportTestTransactions(); + Assert.NotNull(transactions); + Assert.IsNotEmpty(transactions); + Assert.AreEqual(7, transactions.Count()); + + var results = await _service.CreateActivity(transactions); + Assert.NotNull(results); + Assert.IsEmpty(results); + + _deadActivityService.Verify(x => x.SaveDeadActivityRange(It.Is>( + da => + da.Count() == 7 && + da.All(y => y.Reason == DeadActivityReason.MissingRequiredField) && + da.All(y => y.ReasonNote.ToLowerInvariant().Contains("reliably determinable home"))))); } + + [Test] + public async Task SavesDeadActivity_BothKinds() + { + var user = new AppUser() + { + AdEmail = "johndoe@test.com", + EmployeeId = "11771", + FullName = "John Doe", + Departments = new List() + }; + + //There are 2 entries with this email, they will have a user but no home department + Mock.Get(_userRepository).Setup(s => s.FindUserByAdEmailAsync(It.Is(x => x.ToLowerInvariant() == "test2@landmarkcu.com"))).ReturnsAsync(user); + + var transactions = ImportTestTransactions(); + Assert.NotNull(transactions); + Assert.IsNotEmpty(transactions); + Assert.AreEqual(7, transactions.Count()); + + var results = await _service.CreateActivity(transactions); + Assert.NotNull(results); + Assert.IsEmpty(results); + + + _deadActivityService.Verify(x => x.SaveDeadActivityRange(It.Is>( + da => + da.Count(y => + y.Reason == DeadActivityReason.MissingRequiredField && + y.ReasonNote.ToLowerInvariant().Contains("action import, no user for")) == 5))); + + _deadActivityService.Verify(x => x.SaveDeadActivityRange(It.Is>( + da => + da.Count(y => + y.Reason == DeadActivityReason.MissingRequiredField && + y.ReasonNote.ToLowerInvariant().Contains("reliably determinable home")) == 2))); + + } + + [Test] + public async Task SomeActivitiesAreDeadJim_Ok() + { + var john = new AppUser() + { + AdEmail = "johndoe@test.com", + EmployeeId = "John", + FullName = "John Doe", + Departments = new List() + }; + //There are 2 entries with this email, they will have a user but no home department + Mock.Get(_userRepository).Setup(s => s.FindUserByAdEmailAsync(It.Is(x => x.ToLowerInvariant() == "michaelnembhard@landmarkcu.com"))).ReturnsAsync(john); + + var department = new Department() + { + DepartmentName = "Dept1", + Id = 55, + State = DepartmentState.Active + }; + var mary = new AppUser() + { + AdEmail = "marydeer@test.com", + EmployeeId = "Mary", + FullName = "Mary Deer", + Departments = new List() + }; + var assignment = new UserDepartment() + { + Id = 99, + Department = department, + Employee = mary, + Role = Role.BM, + IsHome = true, + StartDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1), + EndDate = null + }; + mary.Departments.Add(assignment); + //There are 2 entries with this email, they will not have a home department assigned + Mock.Get(_userRepository).Setup(s => s.FindUserByAdEmailAsync(It.Is(x => x.ToLowerInvariant() == "test@landmarkcu.com"))).ReturnsAsync(john); + //There are 2 entries with this email, they will have a home department assigned + Mock.Get(_userRepository).Setup(s => s.FindUserByAdEmailAsync(It.Is(x => x.ToLowerInvariant() == "test2@landmarkcu.com"))).ReturnsAsync(mary); + + //Everyone else won't exist + var transactions = ImportTestTransactions(); + Assert.NotNull(transactions); + Assert.IsNotEmpty(transactions); + Assert.AreEqual(7, transactions.Count()); + + //Mary should have 2 successful results + var results = await _service.CreateActivity(transactions); + Assert.NotNull(results); + Assert.AreEqual(2,results.Count); + Assert.True(results.All(x => x.EmployeeId == "Mary")); + + //John should have 2 entries in dead activity for not having a home department + _deadActivityService.Verify(x => x.SaveDeadActivityRange(It.Is>( + da => + da.Count(y => + y.EmployeeId == john.EmployeeId && + y.Reason == DeadActivityReason.MissingRequiredField && + y.ReasonNote.ToLowerInvariant().Contains("reliably determinable home")) == 2))); + + //Everyone else could not be identified + _deadActivityService.Verify(x => x.SaveDeadActivityRange(It.Is>( + da => + da.Count(y => + (y.EmployeeId != john.EmployeeId && y.EmployeeId != mary.EmployeeId) && + y.Reason == DeadActivityReason.MissingRequiredField && + y.ReasonNote.ToLowerInvariant().Contains("action import, no user for")) == 3))); + + } + + [Test] + public async Task FailToSaveDeadActivity_DoesntThrow() + { + Mock.Get(_userRepository).Setup(s => s.FindUserByAdEmailAsync(It.IsAny())).ReturnsAsync((AppUser)null); + _deadActivityService.Setup(x => x.SaveDeadActivityRange(It.IsAny>())) + .ThrowsAsync(new Exception("Oops")); + + var transactions = ImportTestTransactions(); + Assert.NotNull(transactions); + Assert.IsNotEmpty(transactions); + Assert.AreEqual(7, transactions.Count()); + + List results = null; + Assert.DoesNotThrowAsync(async () => + { + var res = await _service.CreateActivity(transactions); + results = res.ToList(); + }); + Assert.NotNull(results); + Assert.IsEmpty(results); + + _deadActivityService.Verify(x => x.SaveDeadActivityRange(It.Is>( + da => + da.Count() == 7 && + da.All(y => y.Reason == DeadActivityReason.MissingRequiredField) && + da.All(y => y.ReasonNote.ToLowerInvariant().Contains("action import, no user for"))))); + + //Our test CSV has email information for every row except 1 + _deadActivityService.Verify(x =>x.SaveDeadActivityRange(It.Is>( + da => + da.Count(x => !string.IsNullOrWhiteSpace(x.EmployeeEmail)) == 6))); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Action/Action_CreditCardPersonal.csv b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Action/Action_CreditCardPersonal.csv new file mode 100644 index 0000000..71443ce --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Action/Action_CreditCardPersonal.csv @@ -0,0 +1,16 @@ +BookedDate,MemberFirstName,MemberLastName,AppID,Principal,ProdDetailFirstName,ProdDetailLastName,ProdDetailEmail,ProdDetailEmployeeNum,IncentiveEarnedEmail,ProductShortName +11/4/2021 16:03,CHRISTOPHER,Doe,1721467,7800,Laurie,Doerson,lauriepaulos@landmarkcu.com,5306,,Rewards Credit Card +11/4/2021 15:52,Joseph,Doe,1721422,500,Michael,Doerson,MichaelNembhard@landmarkcu.com,546,MICHAELNEMBHARD@LANDMARKCU.COM,Starter Credit Card +11/4/2021 15:27,Dalia,Doe,1721404,10000,Michael,Doerson,MichaelNembhard@landmarkcu.com,546,MICHAELNEMBHARD@LANDMARKCU.COM,Qualifile CC +11/4/2021 15:50,Pablo,Doe,1721379,2000,Valerie,Doerson,valerierobles@landmarkcu.com,835,,Rewards Credit Card +11/4/2021 15:54,Antonio,Doe,1721245,500,Valerie,Doerson,valerierobles@landmarkcu.com,835,,Rewards Credit Card +11/4/2021 10:52,BARRY,Doe,1721090,5000,Alexandria,Doerson,AlexandriaMcGinn@landmarkcu.com,5313,,Rewards Credit Card +11/4/2021 9:31,SAMANTHA,Doe,1721022,500,Joseph,Doerson,josephruiz@landmarkcu.com,817,,Starter Credit Card +11/4/2021 10:08,EDWIN,Doe,1720844,500,Jeanfred,Doerson,JeanfredPerez@landmarkcu.com,2455,,Rewards Credit Card +11/4/2021 10:49,DANIEL,Doe,1720766,500,Laurie,Doerson,lauriepaulos@landmarkcu.com,5306,,Student Rewards CC +11/4/2021 9:38,Traeci,Doe,1720546,12000,Monica,Doerson,monicawatts@landmarkcu.com,5503,,Rewards Credit Card +11/4/2021 11:00,CELINA,Doe,1720088,4500,Shannon,Doerson,ShannonLewis@landmarkcu.com,3723,SHANNONLEWIS@LANDMARKCU.COM,Rewards Credit Card +11/4/2021 13:34,CHAD,Doe,1719634,5000,Alexandria,Doerson,AlexandriaMcGinn@landmarkcu.com,5313,,Cash Back CC +11/4/2021 9:11,ELVIRA,Duplicate,1714817,1000,Daisy,Doerson,Daisysalas@landmarkcu.com,822,,Rewards Credit Card +11/4/2021 9:11,Poppy,Doe,1714818,1000,Popeye,Doerson,,823,,Rewards Credit Card +11/4/2021 9:11,ELVIRA,Duplicate,1714817,1000,Daisy,,Daisysalas@landmarkcu.com,822,,Rewards Credit Card diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Action/Action_IndirectVehicleLoanSales.csv b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Action/Action_IndirectVehicleLoanSales.csv new file mode 100644 index 0000000..895c42d --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Action/Action_IndirectVehicleLoanSales.csv @@ -0,0 +1,10 @@ +EmailAddress,Member_Number,Account_Number,Borrower_Name,APP_START_DATE,OPEN_DATE,PBorrowerSSN_Ind,CBorrowerName_Ind,CBorrowerSSN_Ind,SBorrowerName_Ind,SBorrowerSSN_Ind,IndAppID,IndProduct,IndAmtFinanced,IndCreationDate,IndAccountNumber,PBorrowerName_Veh,PBorrowerSSN_Veh,CBorrowerName_Veh,CBorrowerSSN_Veh,SBorrowerName_Veh,SBorrowerSSN_Veh,VehAppID,VehProduct,VehAmtFinanced,DistributionChannel,DistributionChannelCode +test@landmarkcu.com,69714,144,kenneth doerson,8/31/2021,9/8/2021,222334444,,,,,1692568,Ind Auto Truck,"$32,890.70",9/3/2021,69714-144,KENNETH doerson,222334444,,,,,1691319,Vehicle Pre-Approval,"47,000.00",New Berlin,2 +test@landmarkcu.com,8091110,144,LOUIS DOE,9/1/2021,9/22/2021,333445555,JUNE DOE,111223333,,,1696816,Ind Prem Auto Truck,"$49,683.36",9/13/2021,8091110-144,LOUIS DOE,333445555,JUNE DOE,111223333,,,1691794,Vehicle,"45,147.36",Hartland,7 +test2@landmarkcu.com,8091110,144,LOUIS DOE,9/1/2021,9/22/2021,333445555,JUNE DOE,111223333,,,1696816,Ind Prem Auto Truck,"$49,683.36",9/13/2021,8091110-144,LOUIS DOE,333445555,JUNE DOE,111223333,,,1691794,Vehicle,"45,147.36",Hartland,7 +test2@landmarkcu.com,8091110,144,LOUIS DOE,9/1/2021,9/22/2021,333445555,JUNE DOE,111223333,,,1696816,Ind Prem Auto Truck,"$49,683.36",9/13/2021,8091110-144,LOUIS DOE,333445555,JUNE DOE,111223333,,,1691794,Vehicle,"45,147.36",Hartland,7 +test3@landmarkcu.com,8091110,144,LOUIS DOE,9/1/2021,9/22/2021,333445555,JUNE DOE,111223333,,,1696816,Ind Prem Auto Truck,"$49,683.36",9/13/2021,8091110-144,LOUIS DOE,333445555,JUNE DOE,111223333,,,1691794,Vehicle,"45,147.36",Hartland,7 +test3@landmarkcu.com,8091110,144,LOUIS DOE,9/1/2021,9/22/2021,333445555,JUNE DOE,111223333,,,1696816,Ind Prem Auto Truck,"$49,683.36",9/13/2021,8091110-144,LOUIS DOE,333445555,JUNE DOE,111223333,,,1691794,Vehicle,"45,147.36",Hartland,7 +,8091110,144,LOUIS DOE,9/1/2021,9/22/2021,333445555,JUNE DOE,111223333,,,1696816,Ind Prem Auto Truck,"$49,683.36",9/13/2021,8091110-144,LOUIS DOE,333445555,JUNE DOE,111223333,,,1691794,Vehicle,"45,147.36",Hartland,7 + + diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ActivityDetailServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ActivityDetailServiceTest.cs new file mode 100644 index 0000000..e5b89cb --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ActivityDetailServiceTest.cs @@ -0,0 +1,911 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Accounts; +using Microsoft.EntityFrameworkCore; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApi.Services.DuplicateDetection; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; + +namespace Application.UnitTests.Services +{ + public class ActivityDetailServiceTest + { + private IActivityDetailService _activityDetailService; + private IActivityDetailUpdateRepository _activityDetailUpdateRepository; + private Mock _mockDnaService; + + private ActivityService _activityService; + private IActivityRepository _activityRepository; + private IActivityChangeRepository _activityChangeRepository; + private IActivityTransferActionRepository _activityTransferActionRepository; + + private Mock _mockUserService; + private Mock _mockNotificationService; + private Mock _mockDuplicateDetector; + private Mock _mockAuthorizationService; + + [SetUp] + public void Setup() + { + var mockDateTime = new DateTimeService(); + var mockCurrentUserService = Mock.Of(); + + Mock.Get(mockCurrentUserService).Setup(s => s.GetCurrentUser()).Returns(new ApplicationUser + { + Email = "JohnTest@test.com", + }); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + var context = new ApplicationDbContext(options, mockCurrentUserService, mockDateTime); + _activityDetailUpdateRepository = new ActivityDetailUpdateRepository(context); + + _mockDnaService = new Mock(); + _activityDetailService = new ActivityDetailService(_activityDetailUpdateRepository, new UnitOfWork(context), _mockDnaService.Object); + + + _activityRepository = new ActivityRepository(context); + _activityChangeRepository = new ActivityChangeRepository(context); + _activityTransferActionRepository = new ActivityTransferActionRepository(context); + _mockUserService = new Mock(); + _mockNotificationService = new Mock(); + _mockDuplicateDetector = new Mock(); + _mockAuthorizationService = new Mock(); + + _activityService = + new ActivityService(_activityRepository, _activityChangeRepository, _activityTransferActionRepository, + _mockUserService.Object, _mockNotificationService.Object, _mockDuplicateDetector.Object, + _mockAuthorizationService.Object, new UnitOfWork(context)); + } + + [Test] + public async Task TryUpdateActivityAccountDetails_Basic_Flow_OK() + { + await _activityService.SaveActivityRangeAsync(new[] + { + new Activity + { + EmployeeId = "1", + SourceId = "5", + Accepted = DateTime.Now, + Detail = new ActivityDetail + { + AccountNumber = "345", + AccountStatus = "APPR", + IsGapInsuranceEligible = false + } + }, + new Activity + { + EmployeeId = "1", SourceId = "6", + Accepted = DateTime.Now, + Detail = new ActivityDetail + { + AccountNumber = "123", + AccountStatus = "APPR", + IsGapInsuranceEligible = true + } + }, + new Activity + { + EmployeeId = "1", SourceId = "7", + Accepted = DateTime.Now, + Detail = new ActivityDetail + { + AccountNumber = "345", + AccountStatus = "ORIG", + IsGapInsuranceEligible = false + } + }, + new Activity + { + EmployeeId = "1", SourceId = "8", + Accepted = DateTime.Now, + Detail = new ActivityDetail + { + AccountNumber = "10", + AccountStatus = "ACT", + IsGapInsuranceEligible = true + } + }, + new Activity + { + EmployeeId = "1", SourceId = "9", + Accepted = DateTime.Now, + Detail = new ActivityDetail + { + AccountNumber = "11", + AccountStatus = "ORIG", + IsGapInsuranceEligible = true + } + }, + new Activity + { + EmployeeId = "1", SourceId = "100", + Accepted = DateTime.Now, + Detail = new ActivityDetail + { + AccountNumber = "120", + AccountStatus = "ORIG", + IsGapInsuranceEligible = true + } + }, + }); + + var activities = await _activityService.GetActivitiesAsync(); + Assert.NotNull(activities); + Assert.AreEqual(6, activities.Count); + Assert.AreEqual(2, activities.Count(x => x.Detail.AccountStatus == "APPR")); + Assert.AreEqual(3, activities.Count(x => x.Detail.AccountStatus == "ORIG")); + Assert.AreEqual(1, activities.Count(x => x.Detail.AccountStatus == "ACT")); + Assert.AreEqual(4, activities.Count(x => x.Detail.IsGapInsuranceEligible == true)); + + var accountUpdates = new List + { + new ActivityAccountDetails + { + AccountNumber = 123, + CurrentAccountStatus = "CLS" + }, + new ActivityAccountDetails + { + AccountNumber = 345, + CurrentAccountStatus = "ACT" + }, + //Account Update Found, but account did was not in "AccountsToUpdate" + new ActivityAccountDetails + { + AccountNumber = 20000, + CurrentAccountStatus = "BOO" + }, + //Account Update Found, but account did was not in "AccountsToUpdate" + new ActivityAccountDetails + { + AccountNumber = 10, + CurrentAccountStatus = "CLS" + }, + //Same Account Status, no Change + new ActivityAccountDetails + { + AccountNumber = 120, + CurrentAccountStatus = "ORIG" + } + }; + + _mockDnaService.Setup(x => x.GetNewAccountDetailsForActivities(It.IsAny>())).ReturnsAsync(accountUpdates); + + var results = await _activityDetailService.TryUpdateActivityAccountDetails(activities.Where(x => x.Detail.AccountStatus != "ACT").ToList()); + Assert.NotNull(results); + Assert.AreEqual(3, results.Count); + Assert.False(results.Exists(x => x.Detail.AccountNumber == "120")); + + activities = await _activityService.GetActivitiesAsync(); + Assert.NotNull(activities); + Assert.AreEqual(6, activities.Count); + Assert.AreEqual(0, activities.Count(x => x.Detail.AccountStatus == "APPR")); + Assert.AreEqual(2, activities.Count(x => x.Detail.AccountStatus == "ORIG")); + Assert.AreEqual(3, activities.Count(x => x.Detail.AccountStatus == "ACT")); + Assert.AreEqual(1, activities.Count(x => x.Detail.AccountStatus == "CLS")); + Assert.AreEqual(4, activities.Count(x => x.Detail.IsGapInsuranceEligible == true)); + + Assert.AreEqual("ACT", activities.Single(x => x.SourceId == "5").Detail.AccountStatus); + Assert.AreEqual("CLS", activities.Single(x => x.SourceId == "6").Detail.AccountStatus); + Assert.AreEqual("ACT", activities.Single(x => x.SourceId == "7").Detail.AccountStatus); + Assert.AreEqual("ACT", activities.Single(x => x.SourceId == "8").Detail.AccountStatus); + Assert.AreEqual("ORIG", activities.Single(x => x.SourceId == "9").Detail.AccountStatus); + + Assert.AreEqual(false, activities.Single(x => x.SourceId == "5").Detail.IsGapInsuranceEligible); + Assert.AreEqual(true, activities.Single(x => x.SourceId == "6").Detail.IsGapInsuranceEligible); + Assert.AreEqual(false, activities.Single(x => x.SourceId == "7").Detail.IsGapInsuranceEligible); + Assert.AreEqual(true, activities.Single(x => x.SourceId == "8").Detail.IsGapInsuranceEligible); + Assert.AreEqual(true, activities.Single(x => x.SourceId == "9").Detail.IsGapInsuranceEligible); + } + + [Test] + public async Task TryUpdateActivityAccountDetails_NoUpdates_OK() + { + await _activityService.SaveActivityRangeAsync(new[] + { + new Activity + { + EmployeeId = "1", + SourceId = "5", + Accepted = DateTime.Now, + Detail = new ActivityDetail + { + AccountNumber = "345", + AccountStatus = "APPR" + } + }, + new Activity + { + EmployeeId = "1", + SourceId = "6", + Accepted = DateTime.Now, + Detail = new ActivityDetail + { + AccountNumber = "123", + AccountStatus = "APPR" + } + }, + new Activity + { + EmployeeId = "1", + SourceId = "7", + Accepted = DateTime.Now, + Detail = new ActivityDetail + { + AccountNumber = "345", + AccountStatus = "ORIG" + } + }, + new Activity + { + EmployeeId = "1", + SourceId = "8", + Accepted = DateTime.Now, + Detail = new ActivityDetail + { + AccountNumber = "10", + AccountStatus = "ACT" + } + }, + new Activity + { + EmployeeId = "1", + SourceId = "9", + Accepted = DateTime.Now, + Detail = new ActivityDetail + { + AccountNumber = "11", + AccountStatus = "ORIG" + } + }, + }); + + var activities = await _activityService.GetActivitiesAsync(); + + Assert.NotNull(activities); + Assert.AreEqual(5, activities.Count); + Assert.AreEqual(2, activities.Count(x => x.Detail.AccountStatus == "APPR")); + Assert.AreEqual(2, activities.Count(x => x.Detail.AccountStatus == "ORIG")); + Assert.AreEqual(1, activities.Count(x => x.Detail.AccountStatus == "ACT")); + + var accountUpdates = new List + { + new ActivityAccountDetails + { + AccountNumber = 199, + CurrentAccountStatus = "CLS" + }, + new ActivityAccountDetails + { + AccountNumber = 299, + CurrentAccountStatus = "ACT" + }, + //Account Update Found, but account did was not in "AccountsToUpdate" + new ActivityAccountDetails + { + AccountNumber = 399, + CurrentAccountStatus = "BOO" + }, + //Account Update Found, but account did was not in "AccountsToUpdate" + new ActivityAccountDetails + { + AccountNumber = 499, + CurrentAccountStatus = "CLS" + } + }; + + _mockDnaService.Setup(x => x.GetNewAccountDetailsForActivities(It.IsAny>())).ReturnsAsync(accountUpdates); + + var results = await _activityDetailService.TryUpdateActivityAccountDetails(activities.Where(x => x.Detail.AccountStatus != "ACT").ToList()); + + Assert.IsNotNull(results); + Assert.IsEmpty(results); + + activities = await _activityService.GetActivitiesAsync(); + Assert.NotNull(activities); + Assert.AreEqual(5, activities.Count); + Assert.AreEqual(2, activities.Count(x => x.Detail.AccountStatus == "APPR")); + Assert.AreEqual(2, activities.Count(x => x.Detail.AccountStatus == "ORIG")); + Assert.AreEqual(1, activities.Count(x => x.Detail.AccountStatus == "ACT")); + } + + [Test] + public async Task TryUpdateActivityAccountDetails_NoAccountDetails_OK() + { + _mockDnaService.Setup(x => x.GetNewAccountDetailsForActivities(It.IsAny>())).ReturnsAsync(new List()); + + var results = await _activityDetailService.TryUpdateActivityAccountDetails(new List + { + new Activity + { + EmployeeId = "1", + SourceId = "5", + Accepted = DateTime.Now, + Detail = new ActivityDetail + { + AccountNumber = "345", + AccountStatus = "APPR" + } + }, + new Activity + { + EmployeeId = "1", + SourceId = "6", + Accepted = DateTime.Now, + Detail = new ActivityDetail + { + AccountNumber = "123", + AccountStatus = "APPR" + } + }, + new Activity + { + EmployeeId = "1", + SourceId = "7", + Accepted = DateTime.Now, + Detail = new ActivityDetail + { + AccountNumber = "345", + AccountStatus = "ORIG" + } + }, + new Activity + { + EmployeeId = "1", SourceId = "8", + Accepted = DateTime.Now, + Detail = new ActivityDetail + { + AccountNumber = "10", + AccountStatus = "ACT" + } + }, + new Activity + { + EmployeeId = "1", + SourceId = "9", + Accepted = DateTime.Now, + Detail = new ActivityDetail + { + AccountNumber = "11", + AccountStatus = "ORIG" + } + } + }); + + Assert.IsNotNull(results); + Assert.IsEmpty(results); + } + + [Test] + public async Task TryUpdateActivityAccountDetails_NoActivities_OK() + { + var accountUpdates = new List + { + new ActivityAccountDetails + { + AccountNumber = 199, + CurrentAccountStatus = "CLS" + }, + new ActivityAccountDetails + { + AccountNumber = 299, + CurrentAccountStatus = "ACT" + }, + //Account Update Found, but account did was not in "AccountsToUpdate" + new ActivityAccountDetails + { + AccountNumber = 399, + CurrentAccountStatus = "BOO" + }, + //Account Update Found, but account did was not in "AccountsToUpdate" + new ActivityAccountDetails + { + AccountNumber = 499, + CurrentAccountStatus = "CLS" + } + }; + + _mockDnaService.Setup(x => x.GetNewAccountDetailsForActivities(It.IsAny>())).ReturnsAsync(accountUpdates); + + var results = await _activityDetailService.TryUpdateActivityAccountDetails(new List()); + Assert.IsNotNull(results); + Assert.IsEmpty(results); + } + + [Test] + public async Task TryUpdateLicReferralStationDetail_Basic_Flow_OK() + { + var daysBeforeStationChange = 10; + var activityAcceptedDate = DateTime.Now.AddDays(daysBeforeStationChange); + await _activityService.SaveActivityRangeAsync(new[] + { + new Activity + { + EmployeeId = "1", + SourceId = "5", + Accepted = activityAcceptedDate, + Detail = new ActivityDetail + { + AccountNumber = "345", + AccountStatus = "APPR", + StationName = "LIC- Submitted" + } + }, + new Activity + { + EmployeeId = "1", + SourceId = "6", + Accepted = activityAcceptedDate, + Detail = new ActivityDetail + { + AccountNumber = "123", + AccountStatus = "APPR", + StationName = "LIC- Submitted" + } + }, + new Activity + { + EmployeeId = "1", + SourceId = "7", + Accepted = activityAcceptedDate, + Detail = new ActivityDetail + { + AccountNumber = "345", + AccountStatus = "ORIG", + StationName = "LIC- Submitted" + } + }, + new Activity + { + EmployeeId = "1", + SourceId = "8", + Accepted = activityAcceptedDate, + Detail = new ActivityDetail + { + AccountNumber = "10", + AccountStatus = "ACT", + StationName = "LIC- In Progress" + } + }, + new Activity + { + EmployeeId = "1", SourceId = "9", + Accepted = activityAcceptedDate, + Detail = new ActivityDetail + { + AccountNumber = "11", + StationName = "LIC- SomethingElse" + } + } + }); + + var activities = await _activityService.GetActivitiesAsync(); + Assert.NotNull(activities); + Assert.AreEqual(5, activities.Count); + + var inStationDate = DateTime.Now; + + var reQueriedActivityManagerIncentiveReferrals = new List + { + new NewActivityManagerReferral + { + ActivityId = 5, + ReferralStation = "LIC Referral- Not Qualified", + StationDateTime = inStationDate + }, + new NewActivityManagerReferral + { + ActivityId = 6, + ReferralStation = "LIC Referral-Qualified", + StationDateTime = inStationDate + }, + //No change, should not be added to results + new NewActivityManagerReferral + { + ActivityId = 7, + ReferralStation = "LIC- Submitted", + StationDateTime = activityAcceptedDate + }, + new NewActivityManagerReferral + { + ActivityId = 8, + ReferralStation = "LIC Referral- Not Qualified", + StationDateTime = inStationDate + }, + //Account Update Found, but account did was not in "AccountsToUpdate" + new NewActivityManagerReferral + { + ActivityId = 100, + ReferralStation = "LIC- Station", + StationDateTime = inStationDate + } + }; + + _mockDnaService.Setup(x => x.QueryActivityManagerForActivities(It.IsAny>())).ReturnsAsync(reQueriedActivityManagerIncentiveReferrals); + + var results = await _activityDetailService.TryUpdateLicReferralStationDetail(activities.ToList()); + Assert.NotNull(results); + Assert.AreEqual(3, results.Count); + Assert.False(results.Exists(x => x.SourceId == "7")); + Assert.False(results.Exists(x => x.SourceId == "100")); + + activities = await _activityService.GetActivitiesAsync(); + Assert.NotNull(activities); + Assert.AreEqual(5, activities.Count); + Assert.AreEqual(2, activities.Count(x => x.Detail.StationName == "LIC Referral- Not Qualified")); + Assert.AreEqual(1, activities.Count(x => x.Detail.StationName == "LIC- Submitted")); + Assert.AreEqual(1, activities.Count(x => x.Detail.StationName == "LIC Referral-Qualified")); + Assert.AreEqual(1, activities.Count(x => x.Detail.StationName == "LIC- SomethingElse")); + + Assert.AreEqual("LIC Referral- Not Qualified", activities.Single(x => x.SourceId == "5").Detail.StationName); + Assert.AreEqual(inStationDate.Date, results.Single(x => x.SourceId == "5").Accepted.Date); + Assert.AreEqual("LIC Referral-Qualified", activities.Single(x => x.SourceId == "6").Detail.StationName); + Assert.AreEqual(inStationDate.Date, results.Single(x => x.SourceId == "6").Accepted.Date); + Assert.AreEqual("LIC- Submitted", activities.Single(x => x.SourceId == "7").Detail.StationName); + Assert.AreEqual("LIC Referral- Not Qualified", activities.Single(x => x.SourceId == "8").Detail.StationName); + Assert.AreEqual(inStationDate.Date, results.Single(x => x.SourceId == "8").Accepted.Date); + Assert.AreEqual("LIC- SomethingElse", activities.Single(x => x.SourceId == "9").Detail.StationName); + } + + [Test] + public async Task TryUpdateLicReferralStationDetail_NoUpdates_OK() + { + await _activityService.SaveActivityRangeAsync(new[] + { + new Activity + { + EmployeeId = "1", + SourceId = "5", + Accepted = DateTime.Now, + Detail = new ActivityDetail + { + AccountNumber = "345", + AccountStatus = "APPR", + StationName = "LIC- Submitted" + } + }, + new Activity + { + EmployeeId = "1", + SourceId = "6", + Accepted = DateTime.Now, + Detail = new ActivityDetail + { + AccountNumber = "123", + AccountStatus = "APPR", + StationName = "LIC- Submitted" + } + }, + new Activity + { + EmployeeId = "1", + SourceId = "7", + Accepted = DateTime.Now, + Detail = new ActivityDetail + { + AccountNumber = "345", + AccountStatus = "ORIG", + StationName = "LIC- Submitted" + } + }, + new Activity + { + EmployeeId = "1", + SourceId = "8", + Accepted = DateTime.Now, + Detail = new ActivityDetail + { + AccountNumber = "10", + AccountStatus = "ACT", + StationName = "LIC- In Progress" + } + }, + new Activity + { + EmployeeId = "1", + SourceId = "9", + Accepted = DateTime.Now, + Detail = new ActivityDetail + { + AccountNumber = "11", + StationName = "LIC- SomethingElse" + } + }, + }); + + var activities = await _activityService.GetActivitiesAsync(); + Assert.NotNull(activities); + Assert.AreEqual(5, activities.Count); + Assert.AreEqual(5, activities.Count); + Assert.AreEqual(3, activities.Count(x => x.Detail.StationName == "LIC- Submitted")); + Assert.AreEqual(1, activities.Count(x => x.Detail.StationName == "LIC- In Progress")); + Assert.AreEqual(1, activities.Count(x => x.Detail.StationName == "LIC- SomethingElse")); + + var reQueriedActivityManagerIncentiveReferrals = new List + { + new NewActivityManagerReferral + { + ActivityId = 5000, + ReferralStation = "LIC- Not Qualified" + }, + new NewActivityManagerReferral + { + ActivityId = 6000, + ReferralStation = "LIC-Qualified" + } + }; + + _mockDnaService.Setup(x => x.QueryActivityManagerForActivities(It.IsAny>())).ReturnsAsync(reQueriedActivityManagerIncentiveReferrals); + + var results = await _activityDetailService.TryUpdateLicReferralStationDetail(activities.ToList()); + + Assert.NotNull(results); + Assert.IsEmpty(results); + + activities = await _activityService.GetActivitiesAsync(); + Assert.NotNull(activities); + Assert.AreEqual(5, activities.Count); + Assert.AreEqual(3, activities.Count(x => x.Detail.StationName == "LIC- Submitted")); + Assert.AreEqual(1, activities.Count(x => x.Detail.StationName == "LIC- In Progress")); + Assert.AreEqual(1, activities.Count(x => x.Detail.StationName == "LIC- SomethingElse")); + } + + [Test] + public async Task TryUpdateLicReferralStationDetail_NoAccountDetails_OK() + { + _mockDnaService.Setup(x => x.QueryActivityManagerForActivities(It.IsAny>())).ReturnsAsync(new List()); + + var results = await _activityDetailService.TryUpdateLicReferralStationDetail(new List + { + new Activity + { + EmployeeId = "1", + SourceId = "5", + Accepted = DateTime.Now, + Detail = new ActivityDetail + { + AccountNumber = "345", + AccountStatus = "APPR", + StationName = "LIC- Submitted" + } + }, + new Activity + { + EmployeeId = "1", + SourceId = "6", + Accepted = DateTime.Now, + Detail = new ActivityDetail + { + AccountNumber = "123", + AccountStatus = "APPR", + StationName = "LIC- Submitted" + } + }, + new Activity + { + EmployeeId = "1", + SourceId = "7", + Accepted = DateTime.Now, + Detail = new ActivityDetail + { + AccountNumber = "345", + AccountStatus = "ORIG", + StationName = "LIC- Submitted" + } + }, + new Activity + { + EmployeeId = "1", + SourceId = "8", + Accepted = DateTime.Now, + Detail = new ActivityDetail + { + AccountNumber = "10", + AccountStatus = "ACT", + StationName = "LIC- In Progress" + } + }, + new Activity + { + EmployeeId = "1", + SourceId = "9", + Accepted = DateTime.Now, + Detail = new ActivityDetail + { + AccountNumber = "11", + StationName = "LIC- SomethingElse" + } + }, + }); + Assert.IsNotNull(results); + Assert.IsEmpty(results); + } + + [Test] + public async Task TryUpdateLicReferralStationDetail_NoActivities_OK() + { + var reQueriedActivityManagerIncentiveReferrals = new List + { + new NewActivityManagerReferral + { + ActivityId = 5, + ReferralStation = "LIC- Not Qualified" + }, + new NewActivityManagerReferral + { + ActivityId = 6, + ReferralStation = "LIC-Qualified" + }, + + new NewActivityManagerReferral + { + ActivityId = 7, + ReferralStation = "LIC- Submitted" + }, + new NewActivityManagerReferral + { + ActivityId = 8, + ReferralStation = "LIC- Not Qualified" + }, + //Account Update Found, but account did was not in "AccountsToUpdate" + new NewActivityManagerReferral + { + ActivityId = 100, + ReferralStation = "LIC- Station" + } + }; + + _mockDnaService.Setup(x => x.QueryActivityManagerForActivities(It.IsAny>())).ReturnsAsync(reQueriedActivityManagerIncentiveReferrals); + + var results = await _activityDetailService.TryUpdateLicReferralStationDetail(new List()); + + Assert.IsNotNull(results); + Assert.IsEmpty(results); + } + + [Test] + public async Task TryUpdateDpEligibleLoanDetail_UpdatesActivity_OK() + { + var existingActivities = new List + { + new Activity + { + Id = 1, + DepartmentId = 55, + EmployeeId = "11774", + Product = Product.Vehicle, + Type = ActivityType.Sale, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceId = "1", + State = ActivityState.Accepted, + Detail = new ActivityDetail() + { + Id = 23786, + AccountBalance = 12000, + AccountNumber = "867530986", + AccountStatus = "ACT", + DebtProtectionAddDate = null, + DebtProtectionRemoveDate = null, + IsDebtProtectionEligible = true, + IsGapInsuranceEligible = true, + MemberName = "ORLANDO TESTCUSTOMER10" + } + } + }; + + var accountUpdates = new List + { + new ActivityAccountDetails + { + AccountNumber = 867530989, + CurrentAccountStatus = "ACT", + DebtProtectionEffectiveDate = DateTime.Parse("12/3/2021"), + DebtProtectionInactiveDate = DateTime.Parse("1/18/2022") + }, + new ActivityAccountDetails + { + AccountNumber = 867530986, + CurrentAccountStatus = "ACT", + DebtProtectionEffectiveDate = DateTime.Parse("1/18/2022"), + DebtProtectionInactiveDate = DateTime.Parse("1/18/2022") + }, + new ActivityAccountDetails + { + AccountNumber = 867530988, + CurrentAccountStatus = "ACT", + DebtProtectionEffectiveDate = DateTime.Parse("1/18/2022"), + DebtProtectionInactiveDate = null + } + }; + + _mockDnaService.Setup(x => x.GetNewAccountDetailsForActivities(It.IsAny>())).ReturnsAsync(accountUpdates); + var results = await _activityDetailService.TryUpdateDpEligibleLoanDetail(existingActivities); + + Assert.AreEqual(DateTime.Parse("1/18/2022").Date.AddHours(12), existingActivities.FirstOrDefault()?.Detail.DebtProtectionAddDate); + Assert.AreEqual(DateTime.Parse("1/18/2022").Date.AddHours(12), existingActivities.FirstOrDefault()?.Detail.DebtProtectionRemoveDate); + Assert.AreEqual(true, existingActivities.FirstOrDefault()?.Detail.IsDebtProtectionEligible); + Assert.AreEqual(true, existingActivities.FirstOrDefault()?.Detail.IsGapInsuranceEligible); + Assert.IsNotNull(results); + Assert.IsNotEmpty(results); + } + + [Test] + public async Task TryUpdateDpEligibleLoanDetail_ReturnsEmpty_OK() + { + var existingActivities = new List + { + new Activity + { + Id = 1, + DepartmentId = 55, + EmployeeId = "11774", + Type = ActivityType.Sale, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceId = "1", + State = ActivityState.Accepted, + Detail = new ActivityDetail() + { + Id = 23786, + AccountBalance = 12000, + AccountNumber = "867530986", + AccountStatus = "ACT", + DebtProtectionAddDate = DateTime.Parse("1/18/2022").Date.AddHours(12), + DebtProtectionRemoveDate = null, + IsDebtProtectionEligible = true, + IsGapInsuranceEligible = true, + MemberName = "ORLANDO TESTCUSTOMER10" + } + } + }; + + var accountUpdates = new List + { + new ActivityAccountDetails + { + AccountNumber = 867530986, + CurrentAccountStatus = "ACT", + DebtProtectionEffectiveDate = DateTime.Parse("12/3/2021"), + DebtProtectionInactiveDate = DateTime.Parse("1/18/2022") + }, + new ActivityAccountDetails + { + AccountNumber = 867530986, + CurrentAccountStatus = "ACT", + DebtProtectionEffectiveDate = DateTime.Parse("1/18/2022"), + DebtProtectionInactiveDate = DateTime.Parse("1/18/2022") + }, + new ActivityAccountDetails + { + AccountNumber = 867530986, + CurrentAccountStatus = "ACT", + DebtProtectionEffectiveDate = DateTime.Parse("1/18/2022"), + DebtProtectionInactiveDate = null + } + }; + + _mockDnaService.Setup(x => x.GetNewAccountDetailsForActivities(It.IsAny>())).ReturnsAsync(accountUpdates); + + var results = await _activityDetailService.TryUpdateDpEligibleLoanDetail(existingActivities); + + Assert.IsNotNull(results); + Assert.IsEmpty(results); + Assert.AreEqual(true, existingActivities.FirstOrDefault()?.Detail.IsDebtProtectionEligible); + Assert.AreEqual(true, existingActivities.FirstOrDefault()?.Detail.IsGapInsuranceEligible); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ActivityServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ActivityServiceTest.cs new file mode 100644 index 0000000..93e8a6d --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ActivityServiceTest.cs @@ -0,0 +1,2951 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApiLib.Extensions; +using Microsoft.EntityFrameworkCore; +using Moq; +using NUnit.Framework; +using NewLoanSaleActivity = LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities.NewLoanSaleActivity; +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; +using LCU.API.Internal.Operations.IncentiveApi.Services.DuplicateDetection; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace Application.UnitTests.Services +{ + public class ActivityServiceTest + { + private ActivityService _activityService; + private IActivityRepository _activityRepository; + private IActivityChangeRepository _activityChangeRepository; + private IActivityTransferActionRepository _activityTransferActionRepository; + + private ActivityService _mockedActivityService; + private Mock _mockActivityRepository; + private Mock _mockUnitOfWork; + private Mock _mockActivityChangeRepository; + private Mock _mockActivityTransferActionRepository; + private Mock _mockUserService; + private Mock _mockNotificationService; + private Mock _mockDuplicateDetector; + private Mock _mockAuthorizationService; + + [SetUp] + public void Setup() + { + var mockDateTime = new DateTimeService(); + var mockCurrentUserService = Mock.Of(); + + Mock.Get(mockCurrentUserService).Setup(s => s.GetCurrentUser()).Returns(new ApplicationUser() + { + Email = "JohnTest@test.com", + }); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .EnableSensitiveDataLogging(true) + .Options; + var context = new ApplicationDbContext(options, mockCurrentUserService, mockDateTime); + _activityRepository = new ActivityRepository(context); + _activityChangeRepository = new ActivityChangeRepository(context); + _activityTransferActionRepository = new ActivityTransferActionRepository(context); + _mockUserService = new Mock(); + _mockNotificationService = new Mock(); + _mockDuplicateDetector = new Mock(); + _mockAuthorizationService = new Mock(); + + _activityService = + new ActivityService(_activityRepository, _activityChangeRepository, _activityTransferActionRepository, + _mockUserService.Object, _mockNotificationService.Object, _mockDuplicateDetector.Object, + _mockAuthorizationService.Object, new UnitOfWork(context)); + + //Create Fully Mocked Activity Service for Conditional Testing + _mockActivityRepository = new Mock(); + _mockActivityChangeRepository = new Mock(); + _mockActivityTransferActionRepository = new Mock(); + _mockUnitOfWork = new Mock(); + _mockedActivityService = new ActivityService(_mockActivityRepository.Object, + _mockActivityChangeRepository.Object, _mockActivityTransferActionRepository.Object, + _mockUserService.Object, _mockNotificationService.Object, _mockDuplicateDetector.Object, + _mockAuthorizationService.Object, _mockUnitOfWork.Object); + } + + [Test] + public async Task Basic_Flow() + { + _mockUserService.Setup(mock => mock.GetActiveUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync(new AppUser + { + Id = Guid.NewGuid(), + AdEmail = "NewGuidUser@landmarkcu.com", + EmployeeId = "C0000", + Departments = + { + new UserDepartment + { + DepartmentId = 1 + } + } + }); + + var saved = await _activityService.GetActivitiesAsync(); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + _mockDuplicateDetector.Setup(x => + x.TryFindDuplicates(It.IsAny())) + .ReturnsAsync(new List()); + + var activity = new Activity() + { + Id = 1, + SourceId = "1", + DepartmentId = 10, + EmployeeId = "113322" + }; + + var result = await _activityService.SaveActivityAsync(activity); + Assert.NotNull(result); + + saved = await _activityService.GetActivitiesAsync(); + Assert.NotNull(saved); + Assert.IsNotEmpty(saved); + + _mockDuplicateDetector.Verify(x => + x.TryFindDuplicates(It.IsAny()), Times.Once); + } + + [Test] + public async Task BasicSaveWithGapLogging_Flow() + { + _mockUserService.Setup(mock => mock.GetActiveUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync(new AppUser + { + Id = Guid.NewGuid(), + AdEmail = "NewGuidUser@landmarkcu.com", + EmployeeId = "C0000", + Departments = + { + new UserDepartment + { + DepartmentId = 1 + } + } + }); + + var saved = await _activityService.GetActivitiesAsync(); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + _mockDuplicateDetector.Setup(x => + x.TryFindDuplicates(It.IsAny())) + .ReturnsAsync(new List()); + + var activity = new Activity() + { + Id = 1, + SourceId = "1", + SourceSystem = SystemType.Dna, + DepartmentId = 10, + EmployeeId = "113322", + Product = Product.Vehicle, + Detail = new() + { + MemberName = "No company", + AccountBalance = 100000, + IsGapInsuranceEligible = false, + } + }; + + var result = await _activityService.SaveActivityAsync(activity); + Assert.NotNull(result); + + saved = await _activityService.GetActivitiesAsync(); + Assert.NotNull(saved); + Assert.IsNotEmpty(saved); + + _mockDuplicateDetector.Verify(x => + x.TryFindDuplicates(It.IsAny()), Times.Once); + } + + [Test] + public async Task BasicSaveWithNoGapLogging_Flow() + { + _mockUserService.Setup(mock => mock.GetActiveUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync(new AppUser + { + Id = Guid.NewGuid(), + AdEmail = "NewGuidUser@landmarkcu.com", + EmployeeId = "C0000", + Departments = + { + new UserDepartment + { + DepartmentId = 1 + } + } + }); + + var saved = await _activityService.GetActivitiesAsync(); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + _mockDuplicateDetector.Setup(x => + x.TryFindDuplicates(It.IsAny())) + .ReturnsAsync(new List()); + + var activity = new Activity() + { + Id = 1, + SourceId = "1", + SourceSystem = SystemType.Dna, + DepartmentId = 10, + EmployeeId = "113322", + Product = Product.Vehicle, + Detail = new() + { + MemberName = "company LLC", + AccountBalance = 100000, + IsGapInsuranceEligible = false, + } + }; + var result = await _activityService.SaveActivityAsync(activity); + Assert.NotNull(result); + + saved = await _activityService.GetActivitiesAsync(); + Assert.NotNull(saved); + Assert.IsNotEmpty(saved); + + _mockDuplicateDetector.Verify(x => + x.TryFindDuplicates(It.IsAny()), Times.Once); + } + + [Test] + public void LogGapInformationFalseWithLLC() + { + var activity = new Activity() + { + Id = 1, + SourceId = "1", + SourceSystem = SystemType.Dna, + DepartmentId = 10, + EmployeeId = "113322", + Product = Product.Vehicle, + Detail = new() + { + MemberName = " llc", + AccountBalance = 100000, + IsGapInsuranceEligible = false, + } + }; + var result = ActivityService.LogGapInformation(activity); + Assert.False(result); + } + + [Test] + public void LogGapInformationTrueWithoutLLC() + { + var activity = new Activity() + { + Id = 1, + SourceId = "1", + SourceSystem = SystemType.Dna, + DepartmentId = 10, + EmployeeId = "113322", + Product = Product.Vehicle, + Detail = new() + { + MemberName = "Normal Member", + AccountBalance = 100000, + IsGapInsuranceEligible = false, + } + }; + var result = ActivityService.LogGapInformation(activity); + Assert.True(result); + } + + [Test] + public void LogGapInformationFalseWithInc() + { + var activity = new Activity() + { + Id = 1, + SourceId = "1", + SourceSystem = SystemType.Dna, + DepartmentId = 10, + EmployeeId = "113322", + Product = Product.Vehicle, + Detail = new() + { + MemberName = " Inc", + AccountBalance = 100000, + IsGapInsuranceEligible = false, + } + }; + var result = ActivityService.LogGapInformation(activity); + Assert.False(result); + } + + [Test] + public void LogGapInformationFalseWithoutSourceDna() + { + var activity = new Activity() + { + Id = 1, + SourceId = "1", + SourceSystem = SystemType.Allied, + DepartmentId = 10, + EmployeeId = "113322", + Product = Product.Vehicle, + Detail = new() + { + MemberName = "Normal", + AccountBalance = 100000, + IsGapInsuranceEligible = false, + } + }; + var result = ActivityService.LogGapInformation(activity); + Assert.False(result); + } + + [Test] + public void LogGapInformationFalseWithoutBalanceAmount() + { + var activity = new Activity() + { + Id = 1, + SourceId = "1", + SourceSystem = SystemType.Allied, + DepartmentId = 10, + EmployeeId = "113322", + Product = Product.Vehicle, + Detail = new() + { + MemberName = "Normal", + AccountBalance = 100001, + IsGapInsuranceEligible = false, + } + }; + var result = ActivityService.LogGapInformation(activity); + Assert.False(result); + } + + [Test] + public async Task Save_RequiresValues_OK() + { + var saved = await _activityService.GetActivitiesAsync(); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + var activity = new Activity() { }; + + Assert.ThrowsAsync(async () => await _activityService.SaveActivityAsync(activity)); + + activity = new Activity() + { + EmployeeId = "Test", + DepartmentId = -1, + }; + + Assert.ThrowsAsync(async () => await _activityService.SaveActivityAsync(activity)); + + activity = new Activity() + { + DepartmentId = 5, + EmployeeId = null, + }; + + Assert.ThrowsAsync(async () => await _activityService.SaveActivityAsync(activity)); + } + + [Test] + public async Task DuplicateActivity_BasicFlow() + { + _mockUserService.Setup(mock => mock.GetActiveUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync(new AppUser + { + Id = Guid.NewGuid(), + AdEmail = "NewGuidUser@landmarkcu.com", + EmployeeId = "C0000", + Departments = + { + new UserDepartment + { + DepartmentId = 1 + } + } + }); + + _mockDuplicateDetector.Setup(x => + x.TryFindDuplicates(It.IsAny())) + .ReturnsAsync(new List()); + + var activity1 = new Activity() + { + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Referral, + Product = Product.CheckCard, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceId = "2", + State = ActivityState.PendingReferral, + Detail = new ActivityDetail() + { + MemberName = "John Doe", + SubjectPersonNbr = "12345", + SubjectOrgNbr = "123" + } + }; + await _activityService.SaveActivityAsync(activity1); + _mockDuplicateDetector.Verify(x => + x.TryFindDuplicates(It.IsAny()), Times.Once); + + var found = await _activityService.GetActivitiesAsync(); + Assert.NotNull(found); + Assert.IsNotEmpty(found); + Assert.AreEqual(1, found.Count()); + Assert.AreEqual(0, found.Count(x => x.State == ActivityState.Rejected)); + var changes = await _activityChangeRepository.GetActivityChangesByActivityId(found.First().Id); + Assert.NotNull(changes); + Assert.IsEmpty(changes); + + _mockDuplicateDetector.Reset(); + _mockDuplicateDetector.Setup(x => + x.TryFindDuplicates(It.IsAny())) + .ReturnsAsync(new List() { activity1 }); + + var activity2 = new Activity() + { + DepartmentId = 55, + EmployeeId = "1", + Type = ActivityType.Referral, + Product = Product.CheckCard, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceId = "1", + State = ActivityState.PendingReferral, + Detail = new ActivityDetail() + { + MemberName = "John Doe", + SubjectPersonNbr = "12345", + SubjectOrgNbr = "123" + } + }; + await _activityService.SaveActivityAsync(activity2); + + found = await _activityService.GetActivitiesAsync(); + Assert.NotNull(found); + Assert.IsNotEmpty(found); + Assert.AreEqual(2, found.Count()); + Assert.AreEqual(1, found.Count(x => x.State == ActivityState.Rejected)); + var rejected = found.First(x => x.State == ActivityState.Rejected); + changes = await _activityChangeRepository.GetActivityChangesByActivityId(rejected.Id); + Assert.NotNull(changes); + Assert.IsNotEmpty(changes); + Assert.AreEqual(changes.First().ActivityId, rejected.Id); + Assert.AreEqual(changes.First().State, ActivityState.Rejected); + + _mockDuplicateDetector.Verify(x => + x.TryFindDuplicates(It.IsAny()), Times.Once); + } + + [Test] + public async Task Basic_State_Change_Flow() + { + _mockUserService.Setup(mock => mock.GetActiveUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync(new AppUser + { + Id = Guid.NewGuid(), + AdEmail = "NewGuidUser@landmarkcu.com", + EmployeeId = "C0000", + Departments = + { + new UserDepartment + { + DepartmentId = 1 + } + } + }); + + _mockDuplicateDetector.Setup(x => + x.TryFindDuplicates(It.IsAny())) + .ReturnsAsync(new List()); + var activityAccepted = DateTime.Today.AddDays(-2); + + var activity = new Activity() + { + DepartmentId = 55, + EmployeeId = "11771", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = activityAccepted, + SourceId = "2", + State = ActivityState.Created + }; + var activityId = await _activityService.SaveActivityAsync(activity); + + var stateChange1 = new ActivityStateChange() + { + ActivityId = activityId, + Created = DateTime.Today, + CreatedBy = "DNA", + CreatedByType = UserType.System, + State = ActivityState.Rejected + }; + + var updatedActivity = await _activityService.ApplyStateChange(stateChange1); + Assert.NotNull(updatedActivity); + Assert.AreEqual(ActivityState.Rejected, updatedActivity.State); + Assert.AreEqual(0, updatedActivity.MatchingActivityId); + // It was rejected, so the accepted date should change... + Assert.AreNotEqual(activityAccepted, updatedActivity.Accepted); + + var stateChange2 = new ActivityStateChange() + { + ActivityId = activityId, + Created = DateTime.Today, + CreatedBy = "DNA", + CreatedByType = UserType.System, + State = ActivityState.ManualReview + }; + activity.Accepted = activityAccepted; + + updatedActivity = await _activityService.ApplyStateChange(stateChange2); + Assert.NotNull(updatedActivity); + Assert.AreEqual(ActivityState.ManualReview, updatedActivity.State); + // don't change the date, it wasn't accepted or rejected. + Assert.AreEqual(activityAccepted, updatedActivity.Accepted); + + var result = await _activityChangeRepository.GetActivityChangesByActivityId(activityId); + Assert.NotNull(result); + Assert.AreEqual(2, result.Count); + + Assert.AreNotEqual(activity.Created, activity.LastUpdated); + + updatedActivity = await _activityRepository.GetActivityById(activityId); + Assert.AreEqual(ActivityState.ManualReview, updatedActivity.State); + + var stateChange3 = new ActivityStateChange() + { + ActivityId = activityId, + Created = DateTime.Today, + CreatedBy = "DNA", + CreatedByType = UserType.System, + State = ActivityState.Accepted, + AcceptedDate = DateTime.Parse("2/8/2022") + }; + activity.Accepted = activityAccepted; + + updatedActivity = await _activityService.ApplyStateChange(stateChange3); + Assert.NotNull(updatedActivity); + Assert.AreEqual(ActivityState.Accepted, updatedActivity.State); + + // date is changed and set to +12 of StateChange AcceptedDate + Assert.AreEqual(DateTime.Parse("2/8/2022").Date.AddHours(12), updatedActivity.Accepted); + + var result2 = await _activityChangeRepository.GetActivityChangesByActivityId(activityId); + Assert.NotNull(result2); + Assert.AreEqual(3, result2.Count); + + Assert.AreNotEqual(activity.Created, activity.LastUpdated); + + updatedActivity = await _activityRepository.GetActivityById(activityId); + Assert.AreEqual(ActivityState.Accepted, updatedActivity.State); + } + + [Test] + public async Task Exception_State_Change() + { + _mockUserService.Setup(mock => mock.GetActiveUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync(new AppUser + { + Id = Guid.NewGuid(), + AdEmail = "NewGuidUser@landmarkcu.com", + EmployeeId = "C0000", + Departments = + { + new UserDepartment + { + DepartmentId = 1 + } + } + }); + + _mockDuplicateDetector.Setup(x => + x.TryFindDuplicates(It.IsAny())) + .ReturnsAsync(new List()); + var activityAccepted = DateTime.Today.AddDays(-2); + + var activity = new Activity() + { + DepartmentId = 55, + EmployeeId = "11771", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = activityAccepted, + SourceId = "2", + State = ActivityState.Accepted + }; + var activityId = await _activityService.SaveActivityAsync(activity); + + var stateChange1 = new ActivityStateChange() + { + ActivityId = 9999999, + Created = DateTime.Today, + CreatedBy = "DNA", + CreatedByType = UserType.System, + State = ActivityState.Rejected + }; + + Assert.ThrowsAsync(async () => { await _activityService.ApplyStateChange(stateChange1); }); + + var stateChange2 = new ActivityStateChange() + { + ActivityId = activityId, + Created = DateTime.Today, + CreatedBy = "DNA", + CreatedByType = UserType.System, + State = ActivityState.Rejected, + ActivityMatchId = activityId + }; + + Assert.ThrowsAsync(async () => { await _activityService.ApplyStateChange(stateChange2); }); + } + + [Test] + public async Task Basic_State_Change_Duplicate_Reject() + { + _mockUserService.Setup(mock => mock.GetActiveUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync(new AppUser + { + Id = Guid.NewGuid(), + AdEmail = "NewGuidUser@landmarkcu.com", + EmployeeId = "C0000", + Departments = + { + new UserDepartment + { + DepartmentId = 1 + } + } + }); + + _mockDuplicateDetector.Setup(x => + x.TryFindDuplicates(It.IsAny())) + .ReturnsAsync(new List()); + var activityAccepted = DateTime.Today.AddDays(-2); + + var activity = new Activity() + { + DepartmentId = 55, + EmployeeId = "11771", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = activityAccepted, + SourceId = "1", + State = ActivityState.Accepted + }; + var activityId = await _activityService.SaveActivityAsync(activity); + + var duplicate = new Activity() + { + DepartmentId = 55, + EmployeeId = "11771", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = activityAccepted, + SourceId = "2", + State = ActivityState.Accepted + }; + var duplicateId = await _activityService.SaveActivityAsync(duplicate); + activity = await _activityService.GetActivity(activityId); + duplicate = await _activityService.GetActivity(duplicateId); + + Assert.AreEqual(ActivityState.Accepted, activity.State); + Assert.AreEqual(0, activity.MatchingActivityId); + Assert.AreEqual(ActivityState.Accepted, duplicate.State); + Assert.AreEqual(0, duplicate.MatchingActivityId); + + var stateChange1 = new ActivityStateChange() + { + ActivityId = duplicateId, + Created = DateTime.Today, + CreatedBy = "DNA", + CreatedByType = UserType.System, + State = ActivityState.Rejected, + ActivityMatchId = activityId + }; + + var updatedActivity = await _activityService.ApplyStateChange(stateChange1); + activity = await _activityService.GetActivity(activityId); + duplicate = await _activityService.GetActivity(duplicateId); + Assert.NotNull(activity); + Assert.NotNull(duplicate); + Assert.AreEqual(ActivityState.Rejected, duplicate.State); + Assert.AreEqual(activityId, duplicate.MatchingActivityId); + Assert.AreEqual(0, activity.MatchingActivityId); + // It was rejected, so the accepted date should change... + Assert.AreNotEqual(activityAccepted, duplicate.Accepted); + } + + [Test] + public async Task Basic_State_Change_Manual_Review() + { + _mockUserService.Setup(mock => mock.GetActiveUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync(new AppUser + { + Id = Guid.NewGuid(), + AdEmail = "NewGuidUser@landmarkcu.com", + EmployeeId = "C0000", + Departments = + { + new UserDepartment + { + DepartmentId = 1 + } + } + }); + + _mockDuplicateDetector.Setup(x => + x.TryFindDuplicates(It.IsAny())) + .ReturnsAsync(new List()); + var activityAccepted = DateTime.Today.AddDays(-2).AddHours(12); + + var activity = new Activity() + { + DepartmentId = 55, + EmployeeId = "11771", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = activityAccepted, + SourceId = "2", + State = ActivityState.Accepted + }; + var activityId = await _activityService.SaveActivityAsync(activity); + activity = await _activityService.GetActivity(activityId); + + Assert.AreEqual(ActivityState.Accepted, activity.State); + Assert.AreEqual(0, activity.MatchingActivityId); + + var stateChange1 = new ActivityStateChange() + { + ActivityId = activityId, + Created = DateTime.Today, + CreatedBy = "DNA", + CreatedByType = UserType.System, + State = ActivityState.ManualReview + }; + + var updatedActivity = await _activityService.ApplyStateChange(stateChange1); + activity = await _activityService.GetActivity(activityId); + Assert.NotNull(activity); + Assert.AreEqual(ActivityState.ManualReview, activity.State); + Assert.AreEqual(0, activity.MatchingActivityId); + // accepted date should not change... + Assert.AreEqual(activityAccepted, activity.Accepted); + } + + [Test] + public async Task ApplyStateChange_Exception_No_Existing_Activity() + { + _mockUserService.Setup(mock => mock.GetActiveUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync(new AppUser + { + Id = Guid.NewGuid(), + AdEmail = "NewGuidUser@landmarkcu.com", + EmployeeId = "C0000", + Departments = + { + new UserDepartment + { + DepartmentId = 1 + } + } + }); + + _mockDuplicateDetector.Setup(x => + x.TryFindDuplicates(It.IsAny())) + .ReturnsAsync(new List()); + var activity = new Activity() + { + DepartmentId = 55, + EmployeeId = "11771", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + SourceId = "2", + State = ActivityState.Created + }; + var activityId = await _activityService.SaveActivityAsync(activity); + + var stateChange1 = new ActivityStateChange() + { + ActivityId = 1155151511, + Created = DateTime.Today, + CreatedBy = "DNA", + CreatedByType = UserType.System, + State = ActivityState.Rejected + }; + + Assert.ThrowsAsync(async () => { await _activityService.ApplyStateChange(stateChange1); }); + + var result = await _activityChangeRepository.GetActivityChangesByActivityId(1155151511); + Assert.NotNull(result); + Assert.AreEqual(0, result.Count); + } + + [Test] + public async Task ApplyStateChange_No_Change() + { + _mockUserService.Setup(mock => mock.GetActiveUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync(new AppUser + { + Id = Guid.NewGuid(), + AdEmail = "NewGuidUser@landmarkcu.com", + EmployeeId = "C0000", + Departments = + { + new UserDepartment + { + DepartmentId = 1 + } + } + }); + + _mockDuplicateDetector.Setup(x => + x.TryFindDuplicates(It.IsAny())) + .ReturnsAsync(new List()); + var activity = new Activity() + { + DepartmentId = 55, + EmployeeId = "11771", + Type = ActivityType.Sale, + Product = Product.CheckCard, + LastUpdated = DateTime.Parse("1999/12/31"), + Accepted = DateTime.Now, + SourceId = "1", + State = ActivityState.Created + }; + var activityId = await _activityService.SaveActivityAsync(activity); + + var stateChange1 = new ActivityStateChange() + { + ActivityId = activityId, + Created = DateTime.Today, + CreatedBy = "DNA", + CreatedByType = UserType.System, + State = ActivityState.Created + }; + + System.Threading.Thread.Sleep(100); + + //Assert that no change has been made to the existing activity for duplicate state + activity = await _activityService.ApplyStateChange(stateChange1); + Assert.AreNotEqual(activity.Created, activity.LastUpdated); + + var result = await _activityChangeRepository.GetActivityChangesByActivityId(activityId); + Assert.NotNull(result); + Assert.AreEqual(1, result.Count); + } + + [Test] + public async Task GetActivityForStateUpdate_Exception_No_Existing_Activity() + { + _mockUserService.Setup(mock => mock.GetActiveUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync(new AppUser + { + Id = Guid.NewGuid(), + AdEmail = "NewGuidUser@landmarkcu.com", + EmployeeId = "C0000", + Departments = + { + new UserDepartment + { + DepartmentId = 1 + } + } + }); + + _mockUserService.Setup(mock => mock.GetActiveUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync(new AppUser + { + Id = Guid.NewGuid(), + AdEmail = "NewGuidUser@landmarkcu.com", + EmployeeId = "C0000", + Departments = + { + new UserDepartment + { + DepartmentId = 1 + } + } + }); + + var existingActivity = new Activity + { + Id = 1, + DepartmentId = 55, + EmployeeId = "11771", + Type = ActivityType.Sale, + Product = Product.GAPInsurance, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + State = ActivityState.Accepted, + SourceId = "1", + Detail = new ActivityDetail() + { + AccountNumber = "867530986", + MemberName = "ORLANDO TESTCUSTOMER10" + } + }; + var activityId = await _activityService.SaveActivityAsync(existingActivity); + + var updateActivity = new Activity + { + DepartmentId = existingActivity.DepartmentId, + EmployeeId = existingActivity.EmployeeId, + Type = existingActivity.Type, + Product = existingActivity.Product, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceId = existingActivity.SourceId, + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + State = existingActivity.State, + Detail = existingActivity.Detail + }; + + Assert.DoesNotThrowAsync(async () => { await _activityService.GetActivityForStateUpdate(existingActivity); }); + + Assert.DoesNotThrowAsync(async () => { await _activityService.GetActivityForStateUpdate(updateActivity); }); + } + + [Test] + public async Task GetActivityForStateUpdate_Ok() + { + _mockUserService.Setup(mock => mock.GetActiveUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync(new AppUser + { + Id = Guid.NewGuid(), + AdEmail = "NewGuidUser@landmarkcu.com", + EmployeeId = "C0000", + Departments = + { + new UserDepartment + { + DepartmentId = 1 + } + } + }); + + var existingActivity = new Activity + { + Id = 1, + DepartmentId = 55, + EmployeeId = "11771", + Type = ActivityType.Sale, + Product = Product.GAPInsurance, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceId = "2", + State = ActivityState.Accepted, + Detail = new ActivityDetail() + { + AccountNumber = "867530986", + MemberName = "ORLANDO TESTCUSTOMER10" + } + }; + var activityId = await _activityService.SaveActivityAsync(existingActivity); + + var updateActivity = new Activity + { + DepartmentId = existingActivity.DepartmentId, + EmployeeId = existingActivity.EmployeeId, + Type = existingActivity.Type, + Product = existingActivity.Product, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceId = existingActivity.SourceId, + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + State = ActivityState.Rejected, + Detail = existingActivity.Detail + }; + + System.Threading.Thread.Sleep(100); + + var returnedActivity = await _activityService.GetActivityForStateUpdate(updateActivity); + Assert.AreNotEqual(existingActivity, updateActivity); + Assert.AreEqual(existingActivity, returnedActivity); + } + + [Test] + public async Task ListByFilterAsync_JustPage_Ok() + { + _mockActivityRepository.Setup(x => + x.GetPagedFilteredActivities(It.IsAny())) + .ReturnsAsync(new PagedResult() { Items = new List(), Total = 1000 }); + + _mockActivityRepository.Setup(x => x.GetActivitiesAsync(It.IsAny(), It.IsAny())) + .Verifiable(); + + var result = await _mockedActivityService.ListByFilterAsync(new ActivityFilter() { Page = 1, Limit = 100 }); + + Assert.NotNull(result); + Assert.AreEqual(1000, result.Total); + //When states null, calls GetActivityAsync + _mockActivityRepository.Verify( + x => x.GetPagedFilteredActivities(It.IsAny()), + Times.Once); + _mockActivityRepository.Verify(x => x.GetActivitiesAsync(It.IsAny(), It.IsAny()), Times.Never); + } + + [Test] + public async Task ListByFilterAsync_NoResults_Ok() + { + _mockActivityRepository.Setup(x => + x.GetPagedFilteredActivities(It.IsAny())) + .ReturnsAsync(new PagedResult() { Items = new List(), Total = 0 }); + + _mockActivityRepository.Setup(x => x.GetActivitiesAsync(It.IsAny(), It.IsAny())) + .Verifiable(); + + var result = await _mockedActivityService.ListByFilterAsync(new ActivityFilter() { Page = 1, Limit = 100 }); + + Assert.NotNull(result); + Assert.AreEqual(0, result.Total); + + _mockActivityRepository.Verify( + x => x.GetPagedFilteredActivities(It.IsAny()), + Times.Once); + _mockActivityRepository.Verify(x => x.GetActivitiesAsync(It.IsAny(), It.IsAny()), Times.Never); + } + + [Test] + public async Task GetDepartmentActivityForMonthRangeAsync_BasicFlow() + { + _mockUserService.Setup(mock => mock.GetActiveUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync(new AppUser + { + Id = Guid.NewGuid(), + AdEmail = "NewGuidUser@landmarkcu.com", + EmployeeId = "C0000", + Departments = + { + new UserDepartment + { + DepartmentId = 1 + } + } + }); + + _mockDuplicateDetector.Setup(x => + x.TryFindDuplicates(It.IsAny())) + .ReturnsAsync(new List()); + var activity1 = new Activity() + { + DepartmentId = 55, + EmployeeId = "11771", + Type = ActivityType.Sale, + Product = Product.CheckCard, + SourceId = "1", + Accepted = DateTime.Today + }; + await _activityService.SaveActivityAsync(activity1); + var activity2 = new Activity() + { + DepartmentId = 56, + EmployeeId = "11771", + Type = ActivityType.Sale, + Product = Product.CheckCard, + SourceId = "2", + Accepted = DateTime.Today + }; + await _activityService.SaveActivityAsync(activity2); + var activity3 = new Activity() + { + DepartmentId = 55, + EmployeeId = "11772", + Type = ActivityType.Sale, + Product = Product.CheckCard, + SourceId = "3", + Accepted = DateTime.Today.AddMonths(-1) + }; + await _activityService.SaveActivityAsync(activity3); + + var user = new AppUser() + { + Role = Role.Admin, + AdEmail = "tester@test.com", + BaseRole = "Tester", + EmployeeId = "11771", + FullName = "Test McTester", + HomeDepartment = "HOME", + PersonId = 11771, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 55, + Department = new Department() + { + Id = 55, + State = DepartmentState.Active, + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code + } + } + } + }; + + var range = new MonthRange + ( + DateTime.Today.Year, + DateTime.Today.Month, + DateTime.Today.Year, + DateTime.Today.Month + ); + + var found = await _activityService.GetDepartmentActivityForMonthRangeAsync(user.Departments, range); + Assert.NotNull(found); + + Assert.IsNotEmpty(found.First().Progress.ProductActivity); + Assert.IsNotEmpty(found.First().ActivityAudit); + Assert.AreEqual(1, found.First().ActivityAudit.Count()); + } + + [Test] + public async Task GetDepartmentActivityForMonthRangeAsync_NoAssignmentInMonthRange_ReturnsEmpty() + { + var userDepartments = new List + { + new UserDepartment() + { + DepartmentId = 55, + Department = new Department() + { + Id = 55, + State = DepartmentState.Active, + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code + }, + StartDate = DateTime.Today, + EndDate = DateTime.Today + } + }; + + var range = new MonthRange( + 2021, + 11, + 2021, + 12 + ); + + var found = await _activityService.GetDepartmentActivityForMonthRangeAsync(userDepartments, range); + Assert.IsEmpty(found); + } + + [Test] + public async Task GetActivity_ReturnsActivity() + { + var activityFromRepo = new Activity + { + Id = 3, + DepartmentId = 32, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + Detail = new ActivityDetail(), + ActivityPayouts = new List() + }; + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(3)) + .ReturnsAsync(activityFromRepo); + + var result = await _mockedActivityService.GetActivity(3); + + Assert.NotNull(result); + Assert.AreEqual(3, result.Id); + } + + [Test] + public async Task MatchOrExpireReferrals_Match_OnPersonId() + { + _mockUserService.Setup(mock => mock.GetActiveUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync(new AppUser + { + Id = Guid.NewGuid(), + AdEmail = "NewGuidUser@landmarkcu.com", + EmployeeId = "C0000", + Departments = + { + new UserDepartment + { + DepartmentId = 1 + } + } + }); + + var activityOne = new Activity + { + Id = 67260, + DepartmentId = 53, + EmployeeId = "9585", + Type = ActivityType.Sale, + Product = Product.NewChecking, + Accepted = DateTime.Today, + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceId = "255557196", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + IsDebtProtectionEligible = false, + SubjectPersonNbr = "80542" + } + }; + var activityTwo = new Activity + { + Id = 67247, + DepartmentId = 53, + EmployeeId = "12133", + Type = ActivityType.Referral, + Product = Product.NewChecking, + Accepted = DateTime.Today, + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceId = "4977", + State = ActivityState.PendingReferral, + Detail = new ActivityDetail + { + IsDebtProtectionEligible = false, + SubjectPersonNbr = "80542" + } + }; + + await _activityService.SaveActivityAsync(activityOne); + await _activityService.SaveActivityAsync(activityTwo); + await _activityService.MatchOrExpireReferrals(); + + var activityOneStateChanges = await _activityService.GetActivityStateChanges(activityOne.Id); + var activityTwoStateChanges = await _activityService.GetActivityStateChanges(activityTwo.Id); + + Assert.NotNull(activityOneStateChanges); + Assert.NotNull(activityTwoStateChanges); + + Assert.IsEmpty(activityOneStateChanges); + Assert.IsNotEmpty(activityTwoStateChanges); + + Assert.AreEqual(activityOne.Id, activityTwoStateChanges.FirstOrDefault().ActivityMatchId); + _mockUserService.Verify(mock => mock.GetUserByEmployeeIdAsync(It.IsAny()), Times.Never); + } + + [Test] + public async Task MatchOrExpireReferrals_NoMatch_NoExpire() + { + var referral1 = new Activity + { + Id = 1, + DepartmentId = 32, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + Detail = new ActivityDetail(), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + ActivityPayouts = new List() + }; + var referral2 = new Activity + { + Id = 2, + DepartmentId = 32, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + Detail = new ActivityDetail(), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + ActivityPayouts = new List() + }; + var basicList = new List() + { + referral1, referral2 + }; + _mockActivityRepository.Setup(mock => mock.FindUnmatchedReferrals()) + .ReturnsAsync(basicList); + _mockActivityRepository.Setup(mock => mock.FindUnmatchedSaleForReferral(referral1)) + .ReturnsAsync((Activity)null); + _mockActivityRepository.Setup(mock => mock.FindUnmatchedSaleForReferral(referral2)) + .ReturnsAsync((Activity)null); + + _mockActivityRepository.Setup(mock => mock.GetActivityById(1)) + .ReturnsAsync(referral1); + _mockActivityRepository.Setup(mock => mock.GetActivityById(2)) + .ReturnsAsync(referral2); + + _mockActivityChangeRepository.Setup(mock => mock.SaveActivityStateChange(It.IsAny())) + .Verifiable(); + _mockUnitOfWork.Setup(mock => mock.CompleteAsync()).Verifiable(); + + var results = await _mockedActivityService.MatchOrExpireReferrals(); + + Assert.NotNull(results); + Assert.IsEmpty(results); + _mockActivityRepository.Verify(mock => mock.FindUnmatchedReferrals(), Times.Once); + _mockActivityRepository.Verify(mock => mock.FindUnmatchedSaleForReferral(referral1), Times.Once); + _mockActivityRepository.Verify(mock => mock.FindUnmatchedSaleForReferral(referral2), Times.Once); + + _mockActivityRepository.Verify(mock => mock.GetActivityById(1), Times.Never); + _mockActivityRepository.Verify(mock => mock.GetActivityById(2), Times.Never); + + _mockActivityChangeRepository.Verify(mock => mock.SaveActivityStateChange(It.IsAny()), + Times.Never); + _mockUnitOfWork.Verify(mock => mock.CompleteAsync(), Times.Never); + _mockUserService.Verify(mock => mock.GetUserByEmployeeIdAsync(It.IsAny()), Times.Never); + } + + [Test] + public async Task MatchOrExpireReferrals_Match_NoExpire() + { + var referral1 = new Activity + { + Id = 1, + DepartmentId = 32, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + Detail = new ActivityDetail(), + ActivityPayouts = new List() + }; + var referral2 = new Activity + { + Id = 2, + DepartmentId = 32, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + Detail = new ActivityDetail(), + ActivityPayouts = new List() + }; + var sale1 = new Activity + { + Id = 3, + DepartmentId = 32, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + SourceCreated = DateTime.Today, + Detail = new ActivityDetail(), + ActivityPayouts = new List() + }; + var basicList = new List() + { + referral1, referral2 + }; + _mockActivityRepository.Setup(mock => mock.FindUnmatchedReferrals()) + .ReturnsAsync(basicList); + _mockActivityRepository.Setup(mock => mock.FindUnmatchedSaleForReferral(referral1)) + .ReturnsAsync((Activity)sale1); + _mockActivityRepository.Setup(mock => mock.FindUnmatchedSaleForReferral(referral2)) + .ReturnsAsync((Activity)null); + + _mockActivityRepository.Setup(mock => mock.GetActivityById(1)) + .ReturnsAsync(referral1); + _mockActivityRepository.Setup(mock => mock.GetActivityById(2)) + .ReturnsAsync(referral2); + _mockActivityRepository.Setup(mock => mock.GetActivityById(3)) + .ReturnsAsync(sale1); + + _mockActivityChangeRepository.Setup(mock => mock.SaveActivityStateChange(It.IsAny())) + .Verifiable(); + _mockUnitOfWork.Setup(mock => mock.CompleteAsync()).Verifiable(); + + var results = await _mockedActivityService.MatchOrExpireReferrals(); + + Assert.NotNull(results); + Assert.IsNotEmpty(results); + Assert.AreEqual(ActivityState.Accepted, results.First().State); + _mockActivityRepository.Verify(mock => mock.FindUnmatchedReferrals(), Times.Once); + _mockActivityRepository.Verify(mock => mock.FindUnmatchedSaleForReferral(referral1), Times.Once); + _mockActivityRepository.Verify(mock => mock.FindUnmatchedSaleForReferral(referral2), Times.Once); + + _mockActivityRepository.Verify(mock => mock.GetActivityById(1), Times.Once); + _mockActivityRepository.Verify(mock => mock.GetActivityById(2), Times.Never); + // They matched! so they should be linked together. + Assert.AreEqual(sale1.Id, referral1.MatchingActivityId); + Assert.AreEqual(referral1.Id, sale1.MatchingActivityId); + + _mockActivityChangeRepository.Verify(mock => mock.SaveActivityStateChange(It.IsAny()), + Times.Once); + _mockUnitOfWork.Verify(mock => mock.CompleteAsync(), Times.Once); + _mockUserService.Verify(mock => mock.GetUserByEmployeeIdAsync(It.IsAny()), Times.Never); + } + + [Test] + public async Task MatchOrExpireReferrals_NoMatch_Expire() + { + var referral1 = new Activity + { + Id = 1, + DepartmentId = 32, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(61)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(61)), + Detail = new ActivityDetail(), + ActivityPayouts = new List() + }; + var referral2 = new Activity + { + Id = 2, + DepartmentId = 32, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + Detail = new ActivityDetail(), + ActivityPayouts = new List() + }; + var referral3 = new Activity + { + Id = 3, + DepartmentId = 32, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.FirstMortgageReferral, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(121)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(121)), + Detail = new ActivityDetail(), + ActivityPayouts = new List() + }; + + var basicList = new List() + { + referral1, referral2, referral3 + }; + _mockActivityRepository.Setup(mock => mock.FindUnmatchedReferrals()) + .ReturnsAsync(basicList); + _mockActivityRepository.Setup(mock => mock.FindUnmatchedSaleForReferral(referral1)) + .ReturnsAsync((Activity)null); + _mockActivityRepository.Setup(mock => mock.FindUnmatchedSaleForReferral(referral2)) + .ReturnsAsync((Activity)null); + _mockActivityRepository.Setup(mock => mock.FindUnmatchedSaleForReferral(referral3)) + .ReturnsAsync((Activity)null); + + _mockActivityRepository.Setup(mock => mock.GetActivityById(1)) + .ReturnsAsync(referral1); + _mockActivityRepository.Setup(mock => mock.GetActivityById(2)) + .ReturnsAsync(referral2); + _mockActivityRepository.Setup(mock => mock.GetActivityById(3)) + .ReturnsAsync(referral3); + + _mockActivityChangeRepository.Setup(mock => mock.SaveActivityStateChange(It.IsAny())) + .Verifiable(); + _mockUnitOfWork.Setup(mock => mock.CompleteAsync()).Verifiable(); + + var results = await _mockedActivityService.MatchOrExpireReferrals(); + + Assert.NotNull(results); + Assert.IsNotEmpty(results); + Assert.AreEqual(ActivityState.Rejected, results.Single(r => r.ActivityId == 1).State); + Assert.AreEqual(ActivityState.Rejected, results.Single(r => r.ActivityId == 3).State); + _mockActivityRepository.Verify(mock => mock.FindUnmatchedReferrals(), Times.Once); + _mockActivityRepository.Verify(mock => mock.FindUnmatchedSaleForReferral(referral1), Times.Once); + _mockActivityRepository.Verify(mock => mock.FindUnmatchedSaleForReferral(referral2), Times.Once); + _mockActivityRepository.Verify(mock => mock.FindUnmatchedSaleForReferral(referral3), Times.Once); + + _mockActivityRepository.Verify(mock => mock.GetActivityById(1), Times.Once); + _mockActivityRepository.Verify(mock => mock.GetActivityById(2), Times.Never); + _mockActivityRepository.Verify(mock => mock.GetActivityById(3), Times.Once); + + _mockActivityChangeRepository.Verify(mock => mock.SaveActivityStateChange(It.IsAny()), + Times.Exactly(2)); + _mockUnitOfWork.Verify(mock => mock.CompleteAsync(), Times.Exactly(2)); + } + + [Test] + public async Task MatchOrExpireReferrals_SameEmployeeOnMatchedActivities_RejectReferral() + { + var referral = new Activity + { + Id = 1, + DepartmentId = 32, + EmployeeId = "1", + Type = ActivityType.Referral, + Product = Product.NewChecking, // Only NewChecking referrals are rejected in this scenario + Accepted = DateTime.Today, + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + Detail = new ActivityDetail(), + ActivityPayouts = new List() + }; + var sale = new Activity + { + Id = 3, + DepartmentId = 32, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.NewChecking, + Accepted = DateTime.Today, + SourceCreated = DateTime.Today, + Detail = new ActivityDetail(), + ActivityPayouts = new List() + }; + var creditedEmployee = new AppUser + { + Id = Guid.NewGuid(), + AdEmail = "NewGuidUser@landmarkcu.com", + EmployeeId = "1", + Departments = + { + new UserDepartment + { + DepartmentId = 1 + } + } + }; + + _mockActivityRepository.Setup(mock => mock.FindUnmatchedReferrals()) + .ReturnsAsync(new List() { referral }); + _mockActivityRepository.Setup(mock => mock.FindUnmatchedSaleForReferral(referral)) + .ReturnsAsync((Activity)sale); + _mockActivityRepository.Setup(mock => mock.GetActivityById(referral.Id)) + .ReturnsAsync(referral); + _mockActivityRepository.Setup(mock => mock.GetActivityById(sale.Id)) + .ReturnsAsync(sale); + _mockUserService.Setup(mock => mock.GetUserByEmployeeIdAsync(sale.EmployeeId)) + .ReturnsAsync(creditedEmployee); + + _mockActivityChangeRepository.Setup(mock => mock.SaveActivityStateChange(It.IsAny())) + .Verifiable(); + _mockUnitOfWork.Setup(mock => mock.CompleteAsync()).Verifiable(); + + var results = await _mockedActivityService.MatchOrExpireReferrals(); + + Assert.NotNull(results); + Assert.IsNotEmpty(results); + //There are two state changes, one for accepted and the other for rejected + Assert.IsTrue(results.Count == 2); + Assert.AreEqual(ActivityState.Accepted, results.ElementAt(0).State); + Assert.AreEqual(ActivityState.Rejected, results.ElementAt(1).State); + _mockActivityRepository.Verify(mock => mock.FindUnmatchedReferrals(), Times.Once); + _mockActivityRepository.Verify(mock => mock.FindUnmatchedSaleForReferral(referral), Times.Once); + + _mockActivityRepository.Verify(mock => mock.GetActivityById(1), Times.Exactly(2)); + // They matched! so they should be linked together. + Assert.AreEqual(sale.Id, referral.MatchingActivityId); + Assert.AreEqual(referral.Id, sale.MatchingActivityId); + + _mockActivityChangeRepository.Verify(mock => mock.SaveActivityStateChange(It.IsAny()), + Times.Exactly(2)); + _mockUnitOfWork.Verify(mock => mock.CompleteAsync(), Times.Exactly(2)); + _mockUserService.Verify(mock => mock.GetUserByEmployeeIdAsync(sale.EmployeeId), Times.Once()); + } + + [Test] + public void GetActivityAuthorizedCommands_ActivityIsNull_ThrowsNotFoundException() + { + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(1)) + .ReturnsAsync((Activity)null); + + var exception = Assert.ThrowsAsync(async () => + { + await _mockedActivityService.GetActivityAuthorizedCommands(new AppUser(), 1); + }); + + Assert.NotNull(exception); + Assert.IsInstanceOf(exception); + Assert.AreEqual("No activity with Id 1 could be found", exception.Message); + } + + [Test] + public async Task GetActivityAuthorizedCommands_PendingTransferActivity_ReturnsAuthCommands() + { + var inThePast = DateTime.Now.AddDays(-90); + var targetDepartmentId = 2; + var user = new AppUser + { + Departments = new List() + { + new UserDepartment() + { + DepartmentId = targetDepartmentId, + StartDate = inThePast, + EndDate = null, + Role = Role.BOM + } + } + }; + + var activity = new Activity() + { + Id = 1, + State = ActivityState.PendingTransfer, + DepartmentId = 1 + }; + + var activityTransferAction = new ActivityTransferAction() { NewActivityDepartmentId = targetDepartmentId }; + + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(1)) + .ReturnsAsync(activity); + + _mockAuthorizationService.Setup(mock => mock.ValidateHierarchicalDepartmentPermission(user, Role.DepartmentManagerRoles, activity.DepartmentId)) + .ReturnsAsync(false); + + _mockAuthorizationService.Setup(mock => mock.AppUserCanMakeStateChange(user, activity, It.IsAny(), It.IsAny())) + .ReturnsAsync(true); + + _mockActivityTransferActionRepository.Setup(mock => mock.GetRequestedTransferActionByActivityId(activity.Id)) + .ReturnsAsync(activity.State == ActivityState.PendingTransfer ? activityTransferAction : null); + + var authorizedCommands = await _mockedActivityService.GetActivityAuthorizedCommands(user, activity.Id); + + Assert.IsTrue(authorizedCommands.AllowedChanges.Contains(ActivityState.Transferred)); + Assert.IsTrue(authorizedCommands.AllowedChanges.Contains(ActivityState.Accepted)); + Assert.IsTrue(authorizedCommands.CanApproveTransferFrom); + Assert.IsTrue(authorizedCommands.CanApproveTransferTo); + } + + [Test] + public void GetActivity_NotFound() + { + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(3)) + .ReturnsAsync((Activity)null); + + var exception = Assert.ThrowsAsync(async () => { await _mockedActivityService.GetActivity(3); }); + + Assert.NotNull(exception); + Assert.IsInstanceOf(exception); + Assert.AreEqual("No activity with Id 3 could be found", exception.Message); + } + + [Test] + public void ValidateCanApplyActivityStateChange_NotFound() + { + var user = new AppUser(); + var stateChange = new ActivityStateChange + { + ActivityId = 11, + Note = "Change", + State = ActivityState.Accepted + }; + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(It.IsAny())) + .ReturnsAsync((Activity)null); + + var exception = Assert.ThrowsAsync(async () => + { + await _mockedActivityService.ValidateCanApplyActivityStateChange(user, stateChange); + }); + + Assert.NotNull(exception); + Assert.IsInstanceOf(exception); + Assert.AreEqual("No activity with Id 11 could be found", exception.Message); + } + + [Test] + public void ValidateCanApplyActivityStateChange_HasPayouts() + { + var user = new AppUser(); + var stateChange = new ActivityStateChange + { + ActivityId = 11, + Note = "Change", + State = ActivityState.Accepted + }; + var activityWithPayouts = new Activity + { + ActivityPayouts = new List { new ActivityPayoutLineItem() } + }; + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(It.IsAny())) + .ReturnsAsync(activityWithPayouts); + + var exception = Assert.ThrowsAsync(async () => + { + await _mockedActivityService.ValidateCanApplyActivityStateChange(user, stateChange); + }); + + Assert.NotNull(exception); + Assert.IsInstanceOf(exception); + Assert.AreEqual("Cannot change the state of an activity that has a payout.", exception.Message); + } + + [Test] + public void ValidateCanApplyActivityStateChange_AcceptedDateToFarInThePast() + { + var user = new AppUser(); + var stateChange = new ActivityStateChange + { + ActivityId = 11, + AcceptedDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1).AddMonths(-1).AddDays(-1), + Note = "Change", + State = ActivityState.Accepted + }; + var activity = new Activity(); + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(It.IsAny())).ReturnsAsync(activity); + + var exception = Assert.ThrowsAsync(async () => + { + await _mockedActivityService.ValidateCanApplyActivityStateChange(user, stateChange); + }); + + Assert.NotNull(exception); + Assert.IsInstanceOf(exception); + Assert.AreEqual("Cannot change the accepted date of an activity to a date that's already paid out, or is greater than 1 month from now.", + exception.Message); + } + + [Test] + public void ValidateCanApplyActivityStateChange_AcceptedDateToFarInTheFuture() + { + var user = new AppUser(); + var stateChange = new ActivityStateChange + { + ActivityId = 11, + AcceptedDate = DateTime.Now.AddMonths(1).AddDays(1), + Note = "Change", + State = ActivityState.Accepted + }; + var activity = new Activity(); + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(It.IsAny())).ReturnsAsync(activity); + + var exception = Assert.ThrowsAsync(async () => + { + await _mockedActivityService.ValidateCanApplyActivityStateChange(user, stateChange); + }); + + Assert.NotNull(exception); + Assert.IsInstanceOf(exception); + Assert.AreEqual("Cannot change the accepted date of an activity to a date that's already paid out, or is greater than 1 month from now.", + exception.Message); + } + + [Test] + public void ValidateCanApplyActivityStateChange_NotAuthorized() + { + var user = new AppUser + { + Role = Role.UNKNOWN_ROLE, + AdditionalPermissions = new List() + }; + var stateChange = new ActivityStateChange + { + ActivityId = 11, + Note = "Change", + State = ActivityState.Accepted + }; + var activityFromRepo = new Activity + { + State = ActivityState.ManualReview, + ActivityPayouts = new List() + }; + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(It.IsAny())) + .ReturnsAsync(activityFromRepo); + + Assert.AreEqual(StateChangeRequirement.AdminOrPermission, + activityFromRepo.RequiredToChangeState(stateChange.State)); + + var exception = Assert.ThrowsAsync(async () => + { + await _mockedActivityService.ValidateCanApplyActivityStateChange(user, stateChange); + }); + + Assert.NotNull(exception); + Assert.IsInstanceOf(exception); + Assert.AreEqual("User does not have the permission to change the activity state.", + exception.Message); + } + + [Test] + public async Task ValidateCanApplyActivityStateChange_ApproveSalesAsync() + { + var userDepartment = new UserDepartment() + { + DepartmentId = 55, + Department = new Department() + { + Id = 55, + State = DepartmentState.Active, + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code + }, + StartDate = DateTime.Today, + Role = Role.Admin + }; + var user = new AppUser + { + AdditionalPermissions = new List(), + Departments = new List + { + userDepartment + } + }; + var stateChange = new ActivityStateChange + { + ActivityId = 11, + Note = "Change", + State = ActivityState.Accepted, + AcceptedDate = DateTime.Now.AddDays(-1) + }; + var activityFromRepo = new Activity + { + ActivityPayouts = new List(), + Type = ActivityType.Sale, + State = ActivityState.ManualReview + }; + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(It.IsAny())) + .ReturnsAsync(activityFromRepo); + _mockAuthorizationService.Setup(mock => + mock.ValidateHierarchicalDepartmentPermission(user, It.IsAny>(), It.IsAny())) + .ReturnsAsync(true); + + // Type = Sale, State = ManualReview + activityFromRepo.Type = ActivityType.Sale; + activityFromRepo.State = ActivityState.ManualReview; + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(It.IsAny())) + .ReturnsAsync(activityFromRepo); + Assert.AreEqual(StateChangeRequirement.AdminOrPermission, + activityFromRepo.RequiredToChangeState(stateChange.State)); + await _mockedActivityService.ValidateCanApplyActivityStateChange(user, stateChange); + + // Type = Sale, State = PendingReverral + activityFromRepo.Type = ActivityType.Sale; + activityFromRepo.State = ActivityState.PendingReferral; + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(It.IsAny())) + .ReturnsAsync(activityFromRepo); + await _mockedActivityService.ValidateCanApplyActivityStateChange(user, stateChange); + + // Type = Referral, State = ManualReview + activityFromRepo.Type = ActivityType.Referral; + activityFromRepo.State = ActivityState.ManualReview; + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(It.IsAny())) + .ReturnsAsync(activityFromRepo); + await _mockedActivityService.ValidateCanApplyActivityStateChange(user, stateChange); + + // Type = Referral, State = PendingReferral + activityFromRepo.Type = ActivityType.Referral; + activityFromRepo.State = ActivityState.PendingReferral; + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(It.IsAny())) + .ReturnsAsync(activityFromRepo); + await _mockedActivityService.ValidateCanApplyActivityStateChange(user, stateChange); + } + + [Test] + public async Task ValidateCanApplyActivityStateChange_ApproveReferralsAsync() + { + var userDepartment = new UserDepartment() + { + DepartmentId = 55, + Department = new Department() + { + Id = 55, + State = DepartmentState.Active, + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code + }, + StartDate = DateTime.Parse("2021-11-02"), + Role = Role.DepartmentManagerRoles.First() + }; + var user = new AppUser + { + AdditionalPermissions = new List(), + Departments = new List + { + userDepartment + } + }; + var stateChange = new ActivityStateChange + { + ActivityId = 11, + Note = "Change", + State = ActivityState.Accepted + }; + var activityFromRepo = new Activity + { + ActivityPayouts = new List(), + Type = ActivityType.Referral, + State = ActivityState.ManualReview + }; + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(It.IsAny())) + .ReturnsAsync(activityFromRepo); + _mockAuthorizationService.Setup(mock => + mock.ValidateHierarchicalDepartmentPermission(user, It.IsAny>(), It.IsAny())) + .ReturnsAsync(true); + + // Type = Referral, State = ManualReview + activityFromRepo.Type = ActivityType.Referral; + activityFromRepo.State = ActivityState.ManualReview; + Assert.AreEqual(StateChangeRequirement.AdminOrPermission, + activityFromRepo.RequiredToChangeState(stateChange.State)); + + Assert.ThrowsAsync(async () => { await _mockedActivityService.ValidateCanApplyActivityStateChange(user, stateChange); }); + + // Type = Referral, Accepted => Manual Review + activityFromRepo.Type = ActivityType.Referral; + activityFromRepo.State = ActivityState.Accepted; + userDepartment.Role = Role.Admin; + stateChange.State = ActivityState.ManualReview; + Assert.AreEqual(StateChangeRequirement.AdminOrPermission, activityFromRepo.RequiredToChangeState(stateChange.State)); + } + + [Test] + public async Task RequestTransfer_Ok() + { + var request = new ActivityTransferRequest + { + ActivityStateBeforeTransfer = ActivityState.Accepted, + RequestedActivityId = 1, + NewDepartmentId = 2, + NewEmployeeId = "C0000" + }; + var activtyFromRepo = new Activity + { + Id = 1, + State = ActivityState.Accepted, + ActivityPayouts = new List(), + DepartmentId = 1, + Accepted = DateTime.Today + }; + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(It.IsAny())) + .ReturnsAsync(activtyFromRepo); + _mockActivityRepository.Setup(mock => mock.GetActivityById(It.IsAny())) + .ReturnsAsync(activtyFromRepo); + + var user1 = new List { new AppUser { AdEmail = "user1@landmarkcu.com" } }; + var user2 = new List { new AppUser { AdEmail = "user2@landmarkcu.com" } }; + _mockUserService.Setup(mock => mock.FindDepartmentManagers(1)) + .ReturnsAsync(user1); + _mockUserService.Setup(mock => mock.FindDepartmentManagers(2)) + .ReturnsAsync(user2); + var user3 = new AppUser { Id = Guid.NewGuid(), AdEmail = "newguiduser@landmarkcu.com" }; + _mockUserService.Setup(mock => mock.GetActiveUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync(user3); + _mockActivityTransferActionRepository + .Setup(mock => mock.SaveTransferAsync(It.IsAny())) + .Callback(transfer => transfer.RequestedActivity = activtyFromRepo); + + var result = await _mockedActivityService.RequestTransfer(request); + + Assert.NotNull(result); + Assert.AreEqual(ActivityState.Accepted, result.ActivityStateBeforeTransfer); + Assert.AreEqual(1, result.RequestedActivityId); + Assert.AreEqual(ActivityTransferState.Requested, result.State); + Assert.AreEqual(2, result.NewActivityDepartmentId); + Assert.AreEqual("C0000", result.NewActivityEmployeeId); + Assert.AreEqual(activtyFromRepo.State, ActivityState.PendingTransfer); + Assert.AreEqual(DateTime.Today, activtyFromRepo.Accepted); + + // Verify transfer action and state change is saved + _mockActivityTransferActionRepository.Verify(mock => + mock.SaveTransferAsync(It.Is(action => action.Id == 0)), + Times.Once); + _mockActivityChangeRepository.Verify(mock => + mock.SaveActivityStateChange(It.Is(change => + change.ActivityId == 1 && + change.Note == "" && + change.State == ActivityState.PendingTransfer + ))); + + _mockUnitOfWork.Verify(mock => mock.CompleteAsync(), Times.Once); + + // Verify notification sent + _mockNotificationService.Verify(mock => + mock.SendTransferRequestInitiatedNotification( + It.IsAny(), + It.Is>(recipients => + recipients.Count == 2 && + recipients.Contains("user1@landmarkcu.com") && + recipients.Contains("user2@landmarkcu.com")) + ), Times.Once); + } + + [Test] + public async Task RequestTransfer_DuplicateEmailAddresses() + { + var request = new ActivityTransferRequest + { + ActivityStateBeforeTransfer = ActivityState.Accepted, + RequestedActivityId = 1, + NewDepartmentId = 2, + NewEmployeeId = "C0000" + }; + var activtyFromRepo = new Activity + { + Id = 1, + State = ActivityState.Accepted, + ActivityPayouts = new List(), + DepartmentId = 1 + }; + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(It.IsAny())) + .ReturnsAsync(activtyFromRepo); + _mockActivityRepository.Setup(mock => mock.GetActivityById(It.IsAny())) + .ReturnsAsync(activtyFromRepo); + + var user1 = new List { new AppUser { AdEmail = "single@landmarkcu.com" } }; + var user2 = new List { new AppUser { AdEmail = "single@landmarkcu.com" } }; + var user3 = new AppUser { Id = Guid.NewGuid(), AdEmail = "newguiduser@landmarkcu.com" }; + _mockUserService.Setup(mock => mock.FindDepartmentManagers(1)) + .ReturnsAsync(user1); + _mockUserService.Setup(mock => mock.FindDepartmentManagers(2)) + .ReturnsAsync(user2); + _mockUserService.Setup(mock => mock.GetActiveUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync(user3); + _mockActivityTransferActionRepository + .Setup(mock => mock.SaveTransferAsync(It.IsAny())) + .Callback(transfer => transfer.RequestedActivity = activtyFromRepo); + + var result = await _mockedActivityService.RequestTransfer(request); + + Assert.NotNull(result); + + _mockNotificationService.Verify(mock => + mock.SendTransferRequestInitiatedNotification( + It.IsAny(), + It.Is>(recipients => + recipients.Count == 1 && + recipients.Contains("single@landmarkcu.com")) + ), Times.Once); + } + + [Test] + public void RequestTransfer_NotFoundException() + { + var request = new ActivityTransferRequest + { + ActivityStateBeforeTransfer = ActivityState.Accepted, + RequestedActivityId = 1, + NewDepartmentId = 2, + NewEmployeeId = "C0000" + }; + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(It.IsAny())) + .ReturnsAsync((Activity)null); + + var exception = Assert.ThrowsAsync(async () => { await _mockedActivityService.RequestTransfer(request); }); + + Assert.NotNull(exception); + Assert.IsInstanceOf(exception); + Assert.AreEqual("No activity with Id 1 could be found", exception.Message); + + _mockUnitOfWork.Verify(mock => mock.CompleteAsync(), Times.Never); + _mockNotificationService.Verify(mock => + mock.SendTransferRequestInitiatedNotification(It.IsAny(), + It.IsAny>()), + Times.Never); + } + + [Test] + public void RequestTransfer_HasPayout_BadActivityTransferRequestException() + { + var request = new ActivityTransferRequest + { + ActivityStateBeforeTransfer = ActivityState.Accepted, + RequestedActivityId = 1, + NewDepartmentId = 2, + NewEmployeeId = "C0000" + }; + var activtyFromRepo = new Activity + { + Id = 1, + State = ActivityState.Accepted, + ActivityPayouts = new List + { + new ActivityPayoutLineItem() + } + }; + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(It.IsAny())) + .ReturnsAsync(activtyFromRepo); + + var exception = Assert.ThrowsAsync(async () => { await _mockedActivityService.RequestTransfer(request); }); + + Assert.NotNull(exception); + Assert.IsInstanceOf(exception); + Assert.AreEqual("Cannot request a transfer of an activity that has a payout.", exception.Message); + + _mockUnitOfWork.Verify(mock => mock.CompleteAsync(), Times.Never); + _mockNotificationService.Verify(mock => + mock.SendTransferRequestInitiatedNotification(It.IsAny(), + It.IsAny>()), + Times.Never); + } + + [Test] + public void RequestTransfer_IneligibleActivityState_BadActivityTransferRequestException() + { + var request = new ActivityTransferRequest + { + ActivityStateBeforeTransfer = ActivityState.Accepted, + RequestedActivityId = 1, + NewDepartmentId = 2, + NewEmployeeId = "C0000" + }; + var activtyFromRepo = new Activity + { + Id = 1, + State = ActivityState.Created, + ActivityPayouts = new List() + }; + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(It.IsAny())) + .ReturnsAsync(activtyFromRepo); + + var exception = Assert.ThrowsAsync(async () => { await _mockedActivityService.RequestTransfer(request); }); + + Assert.NotNull(exception); + Assert.IsInstanceOf(exception); + Assert.AreEqual("Cannot request a transfer that isn't pending or approved.", exception.Message); + + _mockUnitOfWork.Verify(mock => mock.CompleteAsync(), Times.Never); + _mockNotificationService.Verify(mock => + mock.SendTransferRequestInitiatedNotification(It.IsAny(), + It.IsAny>()), + Times.Never); + } + + [Test] + public async Task RequestTransferToOwnDepartment_Ok() + { + var employee = new AppUser + { + Id = Guid.NewGuid(), + AdEmail = "NewGuidUser@landmarkcu.com", + EmployeeId = "C0000", + Departments = + { + new UserDepartment + { + DepartmentId = 1 + } + } + }; + + var request = new ActivityTransferRequest + { + ActivityStateBeforeTransfer = ActivityState.Accepted, + RequestedActivityId = 1, + NewDepartmentId = 1, + NewEmployeeId = employee.EmployeeId + }; + + var activityFromRepo = new Activity + { + Id = 1, + State = ActivityState.Accepted, + ActivityPayouts = new List(), + DepartmentId = 2, + EmployeeId = employee.EmployeeId, + Detail = new ActivityDetail() + }; + + _mockUserService.Setup(mock => mock.GetActiveUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync(employee); + + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(1)).ReturnsAsync(activityFromRepo); + _mockActivityRepository.Setup(mock => mock.GetActivityById(It.IsAny())).ReturnsAsync(activityFromRepo); + _mockActivityRepository.Setup(mock => mock.GetActivityById(It.IsAny())).ReturnsAsync(activityFromRepo); + + _mockActivityTransferActionRepository.Setup(mock => mock.SaveTransferAsync(It.Is(x => + x.Id == 0 && + x.ActivityStateBeforeTransfer == ActivityState.Accepted && + x.RequestedActivityId == 1 && + x.State == ActivityTransferState.Accepted && + x.NewActivityDepartmentId == 1 && + x.NewActivityEmployeeId == "C0000" + ))) + .ReturnsAsync(1); + + var result = await _mockedActivityService.TransferToOwnDepartment(request, employee); + + Assert.NotNull(result); + Assert.AreEqual(ActivityState.Accepted, result.ActivityStateBeforeTransfer); + Assert.AreEqual(1, result.RequestedActivityId); + Assert.AreEqual(1, result.NewActivityDepartmentId); + Assert.AreEqual("C0000", result.NewActivityEmployeeId); + Assert.AreEqual(ActivityTransferState.Accepted, result.State); + + // Verify transfer action and state change is saved + _mockActivityTransferActionRepository.Verify(); + _mockActivityChangeRepository.Verify(mock => mock.SaveActivityStateChange(It.IsAny()), Times.Exactly(2)); + _mockUnitOfWork.Verify(mock => mock.CompleteAsync(), Times.Exactly(3)); + } + + [Test] + public void RequestTransferToOwnDepartment_ActivityNotFound_NotFoundExceptionThrown() + { + var request = new ActivityTransferRequest + { + ActivityStateBeforeTransfer = ActivityState.Accepted, + RequestedActivityId = 1, + NewDepartmentId = 1, + NewEmployeeId = "C0000" + }; + + var employee = new AppUser + { + EmployeeId = "C0000", + Departments = + { + new UserDepartment + { + DepartmentId = 1 + } + } + }; + + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(1)).ReturnsAsync((Activity)null); + + Assert.ThrowsAsync(() => _mockedActivityService.TransferToOwnDepartment(request, employee)); + + // Verify transfer action and state change is saved + _mockActivityTransferActionRepository.Verify(); + _mockActivityChangeRepository.Verify(mock => mock.SaveActivityStateChange(It.IsAny()), Times.Never); + _mockUnitOfWork.Verify(mock => mock.CompleteAsync(), Times.Never); + } + + [Test] + public void RequestTransferToOwnDepartment_DoesNotBelongToCaller_AuthorizationExceptionThrown() + { + var request = new ActivityTransferRequest + { + ActivityStateBeforeTransfer = ActivityState.Accepted, + RequestedActivityId = 1, + NewDepartmentId = 1, + NewEmployeeId = "C0000" + }; + + var employee = new AppUser + { + EmployeeId = "C0000", + Departments = + { + new UserDepartment + { + DepartmentId = 1 + } + } + }; + + var activityFromRepo = new Activity + { + Id = 1, + State = ActivityState.Accepted, + ActivityPayouts = new List(), + DepartmentId = 2, + EmployeeId = "C0001", + Detail = new ActivityDetail() + }; + + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(1)).ReturnsAsync(activityFromRepo); + + Assert.ThrowsAsync(() => _mockedActivityService.TransferToOwnDepartment(request, employee)); + + // Verify transfer action and state change is saved + _mockActivityTransferActionRepository.Verify(); + _mockActivityChangeRepository.Verify(mock => mock.SaveActivityStateChange(It.IsAny()), Times.Never); + _mockUnitOfWork.Verify(mock => mock.CompleteAsync(), Times.Never); + } + + [Test] + public void RequestTransferToOwnDepartment_EmployeeDoesNotHaveDepartment_BadActivityTransferActionExceptionThrown() + { + var request = new ActivityTransferRequest + { + ActivityStateBeforeTransfer = ActivityState.Accepted, + RequestedActivityId = 1, + NewDepartmentId = 1, + NewEmployeeId = "C0000" + }; + + var employee = new AppUser + { + EmployeeId = "C0000", + Departments = + { + new UserDepartment + { + DepartmentId = 2 + } + } + }; + + var activityFromRepo = new Activity + { + Id = 1, + State = ActivityState.Accepted, + ActivityPayouts = new List(), + DepartmentId = 2, + EmployeeId = "C0000", + Detail = new ActivityDetail() + }; + + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(1)).ReturnsAsync(activityFromRepo); + + Assert.ThrowsAsync(() => _mockedActivityService.TransferToOwnDepartment(request, employee)); + + // Verify transfer action and state change is saved + _mockActivityTransferActionRepository.Verify(); + _mockActivityChangeRepository.Verify(mock => mock.SaveActivityStateChange(It.IsAny()), Times.Never); + _mockUnitOfWork.Verify(mock => mock.CompleteAsync(), Times.Never); + } + + [Test] + public void RequestTransferToOwnDepartment_HasPayouts_BadActivityTransferActionExceptionThrown() + { + var request = new ActivityTransferRequest + { + ActivityStateBeforeTransfer = ActivityState.Accepted, + RequestedActivityId = 1, + NewDepartmentId = 1, + NewEmployeeId = "C0000" + }; + + var employee = new AppUser + { + EmployeeId = "C0000", + Departments = + { + new UserDepartment + { + DepartmentId = 1 + } + } + }; + + var activityFromRepo = new Activity + { + Id = 1, + State = ActivityState.Accepted, + ActivityPayouts = new List + { + new ActivityPayoutLineItem + { + ActivityId = 1 + } + }, + DepartmentId = 2, + EmployeeId = "C0000", + Detail = new ActivityDetail() + }; + + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(1)).ReturnsAsync(activityFromRepo); + + Assert.ThrowsAsync(() => _mockedActivityService.TransferToOwnDepartment(request, employee)); + + // Verify transfer action and state change is saved + _mockActivityTransferActionRepository.Verify(); + _mockActivityChangeRepository.Verify(mock => mock.SaveActivityStateChange(It.IsAny()), Times.Never); + _mockUnitOfWork.Verify(mock => mock.CompleteAsync(), Times.Never); + } + + [Test] + public void RequestTransferToOwnDepartment_NotAcceptedState_BadActivityTransferActionExceptionThrown() + { + var request = new ActivityTransferRequest + { + ActivityStateBeforeTransfer = ActivityState.Accepted, + RequestedActivityId = 1, + NewDepartmentId = 1, + NewEmployeeId = "C0000" + }; + + var employee = new AppUser + { + EmployeeId = "C0000", + Departments = + { + new UserDepartment + { + DepartmentId = 1 + } + } + }; + + var activityFromRepo = new Activity + { + Id = 1, + State = ActivityState.PendingReferral, + ActivityPayouts = new List(), + DepartmentId = 2, + EmployeeId = "C0000", + Detail = new ActivityDetail() + }; + + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(1)).ReturnsAsync(activityFromRepo); + + Assert.ThrowsAsync(() => _mockedActivityService.TransferToOwnDepartment(request, employee)); + + // Verify transfer action and state change is saved + _mockActivityTransferActionRepository.Verify(); + _mockActivityChangeRepository.Verify(mock => mock.SaveActivityStateChange(It.IsAny()), Times.Never); + _mockUnitOfWork.Verify(mock => mock.CompleteAsync(), Times.Never); + } + + [Test] + public async Task ApproveTransfer_Reject() + { + var approver = new AppUser { AdEmail = "manager@landmarkcu.com", EmployeeId = "M0000" }; + var approval = new ActivityTransferApprovalRequest + { + Id = 1, + ApprovalState = ActivityTransferState.Rejected + }; + var transferActionFromRepo = new ActivityTransferAction + { + State = ActivityTransferState.Requested, + RequestedActivity = new Activity { DepartmentId = 2 }, + NewActivityDepartmentId = 3, + ActivityStateBeforeTransfer = ActivityState.ManualReview, + RequestedActivityId = 4 + }; + var activityFromRepo = new Activity + { + State = ActivityState.PendingTransfer + }; + + _mockActivityTransferActionRepository.Setup(mock => mock.GetTransferActionById(1)) + .ReturnsAsync(transferActionFromRepo); + _mockActivityRepository.Setup(mock => mock.GetActivityById(4)) + .ReturnsAsync(activityFromRepo); + + _mockAuthorizationService + .Setup(mock => + mock.AppUserCanMakeStateChange(approver, It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(true); + + var result = await _mockedActivityService.ApproveTransfer(approver, approval); + + Assert.NotNull(result); + Assert.AreEqual(ActivityTransferState.Rejected, result.State); + + _mockActivityChangeRepository.Verify(mock => mock.SaveActivityStateChange( + It.Is(x => + x.ActivityId == 4 && + x.Note.Equals("Activity transfer has been rejected.") && + x.State == ActivityState.ManualReview) + ), Times.Once); + _mockUnitOfWork.Verify(mock => mock.CompleteAsync(), Times.Once); + } + + [Test] + public async Task ApproveTransfer_Approve_ReturnsUpdatedTransferAction() + { + var approver = new AppUser + { + AdEmail = "manager@landmarkcu.com", + EmployeeId = "M0000", + FullName = "Manager, Approving" + }; + var approval = new ActivityTransferApprovalRequest + { + Id = 1, + ApprovalState = ActivityTransferState.Accepted + }; + var transferActionFromRepo = new ActivityTransferAction + { + Id = 6, + State = ActivityTransferState.Requested, + RequestedActivity = new Activity { DepartmentId = 2 }, + NewActivityDepartmentId = 3, + NewActivityEmployeeId = "N0000", + ActivityStateBeforeTransfer = ActivityState.ManualReview, + RequestedActivityId = 4 + }; + var activityFromRepo = new Activity + { + State = ActivityState.PendingTransfer + }; + + _mockActivityTransferActionRepository.Setup(mock => mock.GetTransferActionById(1)) + .ReturnsAsync(transferActionFromRepo); + _mockActivityRepository.Setup(mock => mock.GetActivityById(4)) + .ReturnsAsync(activityFromRepo); + + var mockActivityToTransfer = new Activity + { + Accepted = new DateTime(2021, 9, 22), + DepartmentId = 2, + Detail = new ActivityDetail + { + AccountBalance = 100.00, + AccountNewBalance = 99.00, + AccountNumber = "888888", + AccountStatus = "Open", + AgreementNumber = "999", + BranchOrgName = "Org Name", + BranchOrgNbr = "Org Number", + DnaPersonNbr = "DNA number", + MemberName = "Member Name", + StationName = "Station Name", + SubjectOrgNbr = "Subject Org Number", + SubjectPersonNbr = "Subject Person Number" + }, + EmployeeEmail = "employee@landmarkcu.com", + EmployeeId = "E0000", + Id = 4, + MatchingActivityId = 0, + Product = Product.CheckCard, + SourceCreated = new DateTime(2021, 9, 22), + SourceId = "Source ID", + SourceSystem = SystemType.Dna, + State = ActivityState.PendingTransfer, + Type = ActivityType.Sale + }; + + var mockActivityAfterTransfer = new Activity + { + Accepted = new DateTime(2021, 9, 22), + DepartmentId = 3, + Detail = new ActivityDetail + { + AccountBalance = 100.00, + AccountNewBalance = 99.00, + AccountNumber = "888888", + AccountStatus = "Open", + AgreementNumber = "999", + BranchOrgName = "Org Name", + BranchOrgNbr = "Org Number", + DnaPersonNbr = "DNA number", + MemberName = "Member Name", + StationName = "Station Name", + SubjectOrgNbr = "Subject Org Number", + SubjectPersonNbr = "Subject Person Number" + }, + EmployeeEmail = "employee@landmarkcu.com", + EmployeeId = "E0000", + Id = 50, + MatchingActivityId = 0, + Product = Product.CheckCard, + SourceCreated = new DateTime(2021, 9, 22), + SourceId = "Source ID", + SourceSystem = SystemType.Dna, + State = ActivityState.PendingTransfer, + Type = ActivityType.Sale + }; + var newUser = new AppUser + { + AdEmail = "newEmployee@landmarkcu.com" + }; + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(4)) + .ReturnsAsync(mockActivityToTransfer); + _mockUserService.Setup(mock => mock.GetUserByEmployeeIdAsync("N0000")) + .ReturnsAsync(newUser); + _mockActivityRepository.Setup(mock => mock.GetActivityById(4)) + .ReturnsAsync(mockActivityToTransfer); + _mockActivityRepository.Setup(mock => mock.GetActivityById(50)) + .ReturnsAsync(mockActivityAfterTransfer); + // simulate repo assigning a ID to newly save activity + _mockUnitOfWork.Setup(mock => mock.CompleteAsync()) + .Callback(() => transferActionFromRepo.NewActivity.Id = 50); + _mockAuthorizationService + .Setup(mock => + mock.AppUserCanMakeStateChange(approver, It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(true); + + var result = await _mockedActivityService.ApproveTransfer(approver, approval); + + // Assert transfer action is updated + Assert.AreEqual(6, result.Id); + Assert.AreEqual("Manager, Approving", result.NewDepartmentApprover); + Assert.AreEqual(DateTime.Today.Date, result.NewDepartmentApproved.Value.Date); + Assert.AreEqual("Manager, Approving", result.RequestedDepartmentApprover); + Assert.AreEqual(DateTime.Today.Date, result.RequestedDepartmentApproved.Value.Date); + Assert.AreEqual(ActivityTransferState.Accepted, result.State); + + // Assert new department, employee, and state before transfer are all updated on the new activity + Assert.AreEqual(50, result.NewActivity.Id); + Assert.AreEqual(3, result.NewActivity.DepartmentId); + Assert.AreEqual("newEmployee@landmarkcu.com", result.NewActivity.EmployeeEmail); + Assert.AreEqual("N0000", result.NewActivity.EmployeeId); + Assert.AreEqual(ActivityState.ManualReview, result.NewActivity.State); + } + + [Test] + public async Task ApproveTransfer_Approve_AddsActivityStateChanges() + { + var approver = new AppUser + { + AdEmail = "manager@landmarkcu.com", + EmployeeId = "M0000", + FullName = "Manager, Approving" + }; + var approval = new ActivityTransferApprovalRequest + { + Id = 1, + ApprovalState = ActivityTransferState.Accepted + }; + var transferActionFromRepo = new ActivityTransferAction + { + Id = 6, + State = ActivityTransferState.Requested, + RequestedActivity = new Activity { DepartmentId = 2 }, + NewActivityDepartmentId = 3, + NewActivityEmployeeId = "N0000", + ActivityStateBeforeTransfer = ActivityState.ManualReview, + RequestedActivityId = 4 + }; + var activityFromRepo = new Activity + { + Id = 50, + State = ActivityState.PendingTransfer + }; + + _mockActivityTransferActionRepository.Setup(mock => mock.GetTransferActionById(1)) + .ReturnsAsync(transferActionFromRepo); + _mockActivityRepository.Setup(mock => mock.GetActivityById(4)) + .ReturnsAsync(activityFromRepo); + _mockAuthorizationService + .Setup(mock => + mock.AppUserCanMakeStateChange(approver, It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(true); + + var mockActivityToTransfer = new Activity + { + Accepted = new DateTime(2021, 9, 22), + DepartmentId = 2, + Detail = new ActivityDetail(), + EmployeeEmail = "employee@landmarkcu.com", + EmployeeId = "E0000", + Id = 4, + MatchingActivityId = 0, + State = ActivityState.PendingTransfer, + }; + var newUser = new AppUser + { + AdEmail = "newEmployee@landmarkcu.com" + }; + _mockActivityRepository.Setup(mock => mock.GetActivityIncludingPayouts(4)) + .ReturnsAsync(mockActivityToTransfer); + _mockUserService.Setup(mock => mock.GetUserByEmployeeIdAsync("N0000")) + .ReturnsAsync(newUser); + _mockActivityRepository.Setup(mock => mock.GetActivityById(50)) + .ReturnsAsync(mockActivityToTransfer); + // simulate repo assigning a ID to newly save activity + _mockUnitOfWork.Setup(mock => mock.CompleteAsync()) + .Callback(() => transferActionFromRepo.NewActivity.Id = 50); + + var result = await _mockedActivityService.ApproveTransfer(approver, approval); + + _mockActivityChangeRepository.Verify(mock => mock.SaveActivityStateChange( + It.Is(x => + x.ActivityId == 50 && + x.Note.Equals("Activity has been transferred from Activity ID: 4") && + x.State == ActivityState.ManualReview) + ), Times.Once); + _mockActivityChangeRepository.Verify(mock => mock.SaveActivityStateChange( + It.Is(x => + x.ActivityId == 4 && + x.Note.Equals("Activity has been transferred to Activity ID: 50") && + x.State == ActivityState.Transferred) + ), Times.Once); + _mockUnitOfWork.Verify(mock => mock.CompleteAsync(), Times.Exactly(3)); + } + + [Test] + public void ApproveTransfer_Manager_NotFoundException() + { + var approval = new ActivityTransferApprovalRequest + { + Id = 1, + ApprovalState = ActivityTransferState.Accepted + }; + + _mockActivityTransferActionRepository.Setup(mock => mock.GetTransferActionById(3)) + .ReturnsAsync((ActivityTransferAction)null); + + var exception = Assert.ThrowsAsync(async () => + await _mockedActivityService.ApproveTransfer(new AppUser(), approval)); + + Assert.NotNull(exception); + Assert.IsInstanceOf(exception); + Assert.AreEqual("No activity transfer action with Id 1 could be found", exception.Message); + } + + [Test] + public void ApproveTransfer_Manager_BadActivityTransferActionException() + { + var approval = new ActivityTransferApprovalRequest + { + Id = 1, + ApprovalState = ActivityTransferState.Accepted + }; + var transferActionFromRepo = new ActivityTransferAction + { + State = ActivityTransferState.Accepted + }; + + _mockActivityTransferActionRepository.Setup(mock => mock.GetTransferActionById(1)) + .ReturnsAsync(transferActionFromRepo); + + var exception = Assert.ThrowsAsync(async () => + await _mockedActivityService.ApproveTransfer(new AppUser(), approval)); + + Assert.NotNull(exception); + Assert.IsInstanceOf(exception); + Assert.AreEqual("Approval has already been decided for this activity", exception.Message); + + transferActionFromRepo.State = ActivityTransferState.Rejected; + var exception2 = Assert.ThrowsAsync(async () => + await _mockedActivityService.ApproveTransfer(new AppUser(), approval)); + + Assert.NotNull(exception2); + Assert.IsInstanceOf(exception2); + Assert.AreEqual("Approval has already been decided for this activity", exception.Message); + } + + [Test] + public void ApproveTransfer_Manager_NotAuthorizedException() + { + var nonManagerUser = new AppUser { AdEmail = "teller@landmarkcu.com", EmployeeId = "C0000" }; + var managerUser = new AppUser { AdEmail = "manager@landmarkcu.com", EmployeeId = "M0000" }; + var approval = new ActivityTransferApprovalRequest + { + Id = 1, + ApprovalState = ActivityTransferState.Accepted + }; + var transferActionFromRepo = new ActivityTransferAction + { + State = ActivityTransferState.Requested, + RequestedActivity = new Activity { DepartmentId = 2 }, + NewActivityDepartmentId = 3 + }; + + _mockActivityTransferActionRepository.Setup(mock => mock.GetTransferActionById(1)) + .ReturnsAsync(transferActionFromRepo); + _mockUserService.Setup(mock => mock.FindDepartmentManagers(It.IsAny())) + .ReturnsAsync(new List { managerUser }); + + var exception = Assert.ThrowsAsync(async () => + await _mockedActivityService.ApproveTransfer(nonManagerUser, approval)); + + Assert.NotNull(exception); + Assert.IsInstanceOf(exception); + } + + [Test] + public async Task GetRequestedTransfer_ReturnsAction() + { + var actionFromRepo = new ActivityTransferAction + { + Id = 1, + NewActivityEmployeeId = "C0000", + NewActivityDepartmentId = 88 + }; + _mockActivityTransferActionRepository + .Setup(mock => mock.GetRequestedTransferActionByActivityId(It.IsAny())) + .ReturnsAsync(actionFromRepo); + + var result = await _mockedActivityService.GetRequestedTransfer(1); + + Assert.AreEqual(1, result.Id); + Assert.AreEqual("C0000", result.NewActivityEmployeeId); + Assert.AreEqual(88, result.NewActivityDepartmentId); + } + + [Test] + public async Task RejectPendingTransfers_Ok() + { + var range = new MonthRange + ( + DateTime.Today.Year, + DateTime.Today.Month, + DateTime.Today.Year, + DateTime.Today.Month + ); + + var activityFromRepo = new Activity + { + Id = 1, + State = ActivityState.PendingTransfer, + Accepted = DateTime.Today.Date + }; + var transferActionFromRepo = new ActivityTransferAction + { + State = ActivityTransferState.Requested, + RequestedActivity = activityFromRepo, + NewActivityDepartmentId = 3, + ActivityStateBeforeTransfer = ActivityState.Accepted, + RequestedActivityId = 1, + }; + var transfers = new List(); + transfers.Add(transferActionFromRepo); + + _mockActivityRepository.Setup(mock => mock.GetActivityById(1)) + .ReturnsAsync(activityFromRepo); + _mockActivityTransferActionRepository + .Setup(mock => mock.GetActiveTransfersForStateAsync(ActivityState.Accepted)) + .ReturnsAsync(transfers); + _mockActivityChangeRepository.Setup(mock => mock.SaveActivityStateChange( + It.Is(x => + x.ActivityId == 1 && + x.Note.Equals("Activity transfer has been rejected.") && + x.State == ActivityState.Accepted) + )).Verifiable(); + + await _mockedActivityService.CancelPendingTransfersBetween(range); + + _mockActivityChangeRepository.Verify(mock => mock.SaveActivityStateChange( + It.Is(x => + x.ActivityId == 1 && + x.Note.Equals("Activity transfer has been rejected.") && + x.State == ActivityState.Accepted) + ), Times.Once); + _mockUnitOfWork.Verify(mock => mock.CompleteAsync(), Times.Once); + } + + [TestCase("07-01-2021", 1)] + [TestCase("07-31-2021", 1)] + [TestCase("08-01-2021", 1)] + [TestCase("08-31-2021", 1)] + [TestCase("09-01-2021", 0)] + public async Task RejectPendingTransfers_Ok_MonthBorders(string dateString, int timesCalled) + { + var range = new MonthRange(2021, 7, 2021, 8); + + var activityFromRepo = new Activity + { + Id = 1, + State = ActivityState.PendingTransfer, + Accepted = DateTime.Parse(dateString) + }; + var transferActionFromRepo = new ActivityTransferAction + { + State = ActivityTransferState.Requested, + RequestedActivity = activityFromRepo, + NewActivityDepartmentId = 3, + ActivityStateBeforeTransfer = ActivityState.Accepted, + RequestedActivityId = 1, + }; + var transfers = new List(); + transfers.Add(transferActionFromRepo); + + _mockActivityRepository.Setup(mock => mock.GetActivityById(1)) + .ReturnsAsync(activityFromRepo); + _mockActivityTransferActionRepository + .Setup(mock => mock.GetActiveTransfersForStateAsync(ActivityState.Accepted)) + .ReturnsAsync(transfers); + _mockActivityChangeRepository.Setup(mock => mock.SaveActivityStateChange( + It.Is(x => + x.ActivityId == 1 && + x.Note.Equals("Activity transfer has been rejected.") && + x.State == ActivityState.Accepted) + )).Verifiable(); + + await _mockedActivityService.CancelPendingTransfersBetween(range); + + _mockActivityChangeRepository.Verify(mock => mock.SaveActivityStateChange( + It.Is(x => + x.ActivityId == 1 && + x.Note.Equals("Activity transfer has been rejected.") && + x.State == ActivityState.Accepted) + ), Times.Exactly(timesCalled)); + _mockUnitOfWork.Verify(mock => mock.CompleteAsync(), Times.Exactly(timesCalled)); + } + + [Test] + public async Task FilterExternalActivitiesByExistingSourceId_OK() + { + _mockActivityRepository.Setup(x => x.GetActivitiesBySourceId(It.IsAny>())).ReturnsAsync( + new List() + { + new Activity() + { + SourceId = "1", + Type = ActivityType.Sale, + Product = Product.Mortgage + }, + new Activity() + { + SourceId = "2", + Type = ActivityType.Sale, + Product = Product.Mortgage + }, + new Activity() + { + SourceId = "3", + Type = ActivityType.Sale, + Product = Product.Mortgage + }, + new Activity() + { + SourceId = "4", + Type = ActivityType.Sale, + Product = Product.Vehicle + }, + new Activity() + { + SourceId = "5", + Type = ActivityType.Sale, + Product = Product.Vehicle + }, + new Activity() + { + SourceId = "6", + Type = ActivityType.Referral, + Product = Product.Vehicle + } + }); + var results = await _mockedActivityService.FilterExternalActivitiesByExistingSourceId( + new List() + { + new NewLoanSaleActivity() + { + ActivityNbr = 1, + MinorAccountType = "FE01", + }, + new NewLoanSaleActivity() + { + ActivityNbr = 5, + MinorAccountType = "FE01", + }, + new NewLoanSaleActivity() + { + ActivityNbr = 5, + MinorAccountType = "DV01" + }, + new NewLoanSaleActivity() + { + ActivityNbr = 6, + MinorAccountType = "DV01" + }, + new NewLoanSaleActivity() + { + ActivityNbr = 7, + MinorAccountType = "DV01" + } + }); + + Assert.NotNull(results); + Assert.AreEqual(3, results.Count); + Assert.False(results.Any(x => x.GetSourceId() == "1" && x.GetProductType() == Product.Mortgage)); + Assert.True(results.Any(x => x.GetSourceId() == "5" && x.GetProductType() == Product.Mortgage)); + Assert.False(results.Any(x => x.GetSourceId() == "5" && x.GetProductType() == Product.Vehicle)); + Assert.True(results.Any(x => x.GetSourceId() == "6" && x.GetProductType() == Product.Vehicle)); + Assert.True(results.Any(x => x.GetSourceId() == "7" && x.GetProductType() == Product.Vehicle)); + } + + [Test] + public async Task NoEmployeeFoundWhenSavingNewActivityThrowsNotFoundException() + { + _mockUserService.Setup(mock => mock.GetActiveUserByEmployeeIdAsync(It.IsAny())).ReturnsAsync((AppUser)null); + _mockUserService.Setup(mock => mock.GetUserByEmployeeIdAsync(It.IsAny())).ReturnsAsync((AppUser)null); + + var activity = new Activity() + { + Id = 1, + SourceId = "1", + DepartmentId = 10, + EmployeeId = "113322" + }; + + //Assert + Assert.ThrowsAsync(async () => await _activityService.SaveActivityAsync(activity)); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Allied/AlliedTransactionImportServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Allied/AlliedTransactionImportServiceTest.cs new file mode 100644 index 0000000..a00e4cb --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Allied/AlliedTransactionImportServiceTest.cs @@ -0,0 +1,945 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using CsvHelper; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApi.Services.Allied; +using LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.API.Internal.Operations.IncentiveApi.Models.Allied; +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.Library.Internal.FileOperationsModels.Models.NameSettings; +using LCU.Library.Internal.FileOperationsModels.Models; + +namespace Application.UnitTests.Services.Allied +{ + public class AlliedTransactionImportServiceTest + { + private IAlliedGapMbpTransactionImportService _service; + private IUserRepository _userRepository; + private IActivityService _activityService; + private IActivityDecisionService _activityDecisionService; + private IActivityDetailService _activityDetailService; + private DebtProtectionAccessoryService _debtProtectionAccessoryService; + private IDnaService _dnaService; + private IFileOperationsApiService _fileOperationsApiService; + private Mock _deadActivityService; + private IGapInsuranceActivityService _gapInsuranceActivityService; + private Mock _activityRepository; + private Mock _unitOfWork; + private int _expectedRecordCount; + private string _testDataFilePath; + + [SetUp] + public void Setup() + { + var strategies = new List + { + new DebtProtectionEligibleLoanDecisionStrategy(), + new GapDecisionStrategy(), + new ManualDecisionStrategy(), + new MbpDecisionStrategy(), + new MortgageSaleDecisionStrategy(), + new NewCheckingDecisionStrategy(), + new ReferralDecisionStrategy(), + new VehicleDecisionStrategy() + }; + _userRepository = Mock.Of(); + _fileOperationsApiService = Mock.Of(); + _activityService = Mock.Of(); + _activityDetailService = Mock.Of(); + _debtProtectionAccessoryService = new DebtProtectionAccessoryService(_activityService, _activityDetailService); + _dnaService = Mock.Of(); + _activityDecisionService = new ActivityDecisionService(strategies, _activityService, _activityDetailService, _debtProtectionAccessoryService, _dnaService); + _deadActivityService = new Mock(); + _activityRepository = new Mock(); + _unitOfWork = new Mock(); + _gapInsuranceActivityService = new GapInsuranceActivityService(_activityDetailService, _activityRepository.Object, _activityService, _unitOfWork.Object); + _service = new AlliedGapMbpTransactionImportService( + _userRepository, + _gapInsuranceActivityService, + _fileOperationsApiService, + _activityService, + _activityDecisionService, + _activityDetailService, + _deadActivityService.Object + ); + _expectedRecordCount = 12; + _testDataFilePath = @"Services/Allied/SalesWithCancellationsByEmployee.csv"; + } + + public StreamReader GetAlliedFile() + { + var path = _testDataFilePath; + var reader = new StreamReader(path); + return reader; + } + + [Test] + public async Task ProcessFileNames_OK() + { + var department = new Department() + { + DepartmentName = "Dept1", + Id = 55, + State = DepartmentState.Active + }; + var user = new AppUser() + { + AdEmail = "johndoe@test.com", + EmployeeId = "11771", + FullName = "John Doe", + Departments = new List() + }; + var assignment = new UserDepartment() + { + Id = 99, + Department = department, + Employee = user, + Role = Role.BM, + IsHome = true, + StartDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1), + EndDate = null + }; + user.Departments.Add(assignment); + + var request = new AlliedGapMbpRequest + { + DynamicFileName = "alliedgapmbp", + FilePath = "clean", + DynamicFileSettings = new DynamicNameSetting() { } + }; + + var files = new string[] + { + "x" + }; + + byte[] array = File.ReadAllBytes(_testDataFilePath); + + //Setup + Mock.Get(_fileOperationsApiService).Setup(x => x.BuildDynamicFileList(It.IsAny())).ReturnsAsync(files); + Mock.Get(_fileOperationsApiService).Setup(x => x.GetFileContent(It.IsAny())).ReturnsAsync(array.ToString()); + Mock.Get(_userRepository).Setup(s => s.SearchUsersAsync(It.IsAny())).ReturnsAsync(new List() { user }); + + var results = await _service.ImportTransactions(request); + var activities = await _service.CreateActivity(It.IsAny>()); + Mock.Get(_fileOperationsApiService).Verify(x => x.GetFileContent(It.IsAny()), Times.Once); + Assert.AreEqual("alliedgapmbp", request.DynamicFileName); + } + + [Test] + public async Task ProcessCsv_OK() + { + SetupTestUsers(); + SetupTestActivities(); + + var transactions = ImportTestTransactions(); + Assert.NotNull(transactions); + var alliedSaleRows = transactions.ToList(); + Assert.IsNotEmpty(alliedSaleRows); + Assert.AreEqual(_expectedRecordCount, alliedSaleRows.Count()); + + var results = await _service.CreateActivity(alliedSaleRows); + Assert.NotNull(results); + Assert.AreEqual(_expectedRecordCount, results.Count); + Assert.AreEqual(_expectedRecordCount, results.Count(x => x.SourceSystem == SystemType.Allied)); + } + + public IEnumerable ImportTestTransactions() + { + var input = GetAlliedFile(); + var csvReader = new CsvReader(input, CultureInfo.InvariantCulture); + return csvReader.GetRecords().ToList(); + } + + public void SetupTestUsers() + { + var department = new Department() + { + DepartmentName = "Dept1", + Id = 55, + State = DepartmentState.Active + }; + + var testUsers = new List + { + new() + { + AdEmail = "AbrahamTestAssociate1@test.com", + EmployeeId = "11771", + FullName = "Abraham TestAssociate1", + Id = Guid.NewGuid(), + Departments = new List + { + new() + { + Id = 99, + Department = department, + Employee = null, + Role = Role.BM, + IsHome = true, + StartDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1), + EndDate = null + } + } + }, + new() + { + AdEmail = "FranchelieTestAssociate2@test.com", + EmployeeId = "11772", + FullName = "Franchelie TestAssociate2", + Id = Guid.NewGuid(), + Departments = new List + { + new() + { + Id = 100, + Department = department, + Employee = null, + Role = Role.BM, + IsHome = true, + StartDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1), + EndDate = null + } + } + }, + new() + { + AdEmail = "StefaniTestAssociate3@test.com", + EmployeeId = "11773", + FullName = "Stefani TestAssociate3", + Id = Guid.NewGuid(), + Departments = new List + { + new() + { + Id = 101, + Department = department, + Employee = null, + Role = Role.BM, + IsHome = true, + StartDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1), + EndDate = null + } + } + }, + new() + { + AdEmail = "AmarisaTestAssociate4@test.com", + EmployeeId = "11774", + FullName = "Amarisa TestAssociate4", + Id = Guid.NewGuid(), + Departments = new List + { + new() + { + Id = 102, + Department = department, + Employee = null, + Role = Role.BM, + IsHome = true, + StartDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1), + EndDate = null + } + } + }, + new() + { + AdEmail = "AmaliaTestAssociate5@test.com", + EmployeeId = "11774", + FullName = "Amalia TestAssociate5", + Id = Guid.NewGuid(), + Departments = new List + { + new() + { + Id = 103, + Department = department, + Employee = null, + Role = Role.BM, + IsHome = true, + StartDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1), + EndDate = null + } + } + } + }; + + foreach (var user in testUsers) + { + user.Departments.First().Employee = user; + Mock.Get(_userRepository) + .Setup(r => r.SearchUsersAsync(It.Is(x => x.Query == user.FullName))) + .ReturnsAsync(new List { testUsers.First(x => x.FullName == user.FullName) }); + } + } + + public void SetupTestActivities() + { + var existingActivities = new List + { + new() + { + Id = 1, + DepartmentId = 55, + EmployeeId = "11774", + Type = ActivityType.Sale, + Product = Product.GAPInsurance, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceSystem = SystemType.Allied, + State = ActivityState.Accepted, + Detail = new ActivityDetail() + { + AccountNumber = "867530986", + MemberName = "ORLANDO TESTCUSTOMER10" + } + }, + new() + { + Id = 2, + DepartmentId = 55, + EmployeeId = "11774", + Type = ActivityType.Sale, + Product = Product.GAPInsurance, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceSystem = SystemType.Allied, + State = ActivityState.Accepted, + Detail = new ActivityDetail() + { + AccountNumber = "8675309861", + MemberName = "ORLANDO TESTCUSTOMER11" + } + }, + new() + { + Id = 3, + DepartmentId = 55, + EmployeeId = "11774", + Type = ActivityType.Sale, + Product = Product.GAPInsurance, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceSystem = SystemType.Allied, + State = ActivityState.Accepted, + Detail = new ActivityDetail() + { + AccountNumber = "8675309862", + MemberName = "ORLANDO TESTCUSTOMER12" + } + } + }; + + foreach (var queryActivity in existingActivities) + { + Mock.Get(_activityService) + .Setup(r => r + .GetActivityForStateUpdate(It.Is(x => x.EmployeeId == queryActivity.EmployeeId + && x.Product == queryActivity.Product + && x.Detail.AccountNumber == queryActivity.Detail.AccountNumber + ))) + .ReturnsAsync(existingActivities.First(x => x.EmployeeId == queryActivity.EmployeeId + && x.Product == queryActivity.Product + && x.Detail.AccountNumber == queryActivity.Detail.AccountNumber + )); + + Mock.Get(_activityService) + .Setup(r => r + .GetActivityForDetailUpdate(It.Is(x => x.EmployeeId == queryActivity.EmployeeId + && x.Product == queryActivity.Product + && x.Detail.AccountNumber == queryActivity.Detail.AccountNumber + ))) + .ReturnsAsync(existingActivities.First(x => x.EmployeeId == queryActivity.EmployeeId + && x.Product == queryActivity.Product + && x.Detail.AccountNumber == queryActivity.Detail.AccountNumber + )); + + Mock.Get(_activityService) + .Setup(r => r + .GetActivity(It.IsAny())) + .ReturnsAsync(existingActivities.First(x => x.Id == queryActivity.Id + )); + + Mock.Get(_activityService) + .Setup(r => r + .CreateAndRetrieveActivity(It.Is(x => + x.AppUserId == queryActivity.AppUserId + && x.EmployeeId == queryActivity.EmployeeId + && x.Product == queryActivity.Product + && x.Detail.AccountNumber == queryActivity.Detail.AccountNumber + && x.Detail.SubAccountNumber == queryActivity.Detail.SubAccountNumber + ))) + .ReturnsAsync(existingActivities.First(x => + x.AppUserId == queryActivity.AppUserId + && x.EmployeeId == queryActivity.EmployeeId + && x.Product == queryActivity.Product + && x.Detail.AccountNumber == queryActivity.Detail.AccountNumber + && x.Detail.SubAccountNumber == queryActivity.Detail.SubAccountNumber + )); + } + + Mock.Get(_activityService) + .Setup(r => r + .CreateAndRetrieveActivity(It.IsAny())) + .ReturnsAsync(new Activity() + { + AppUserId = Guid.NewGuid(), + DepartmentId = 55, + EmployeeId = "11774", + Type = ActivityType.Sale, + Product = Product.GAPInsurance, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceSystem = SystemType.Allied, + State = ActivityState.Accepted, + Detail = new ActivityDetail() + { + AccountNumber = "8675309862", + MemberName = "ORLANDO TESTCUSTOMER12" + } + }); + } + + [Test] + public async Task SavesDeadActivity_WhenNoUserFound() + { + Mock.Get(_userRepository).Setup(s => s.FindUserByAdEmailAsync(It.IsAny())).ReturnsAsync((AppUser)null); + + var transactions = ImportTestTransactions(); + Assert.NotNull(transactions); + Assert.IsNotEmpty(transactions); + Assert.AreEqual(_expectedRecordCount, transactions.Count()); + + var results = await _service.CreateActivity(transactions); + Assert.NotNull(results); + Assert.IsEmpty(results); + + _deadActivityService.Verify(x => x.SaveDeadActivityRange(It.Is>( + da => + da.Count() == _expectedRecordCount && + da.All(y => y.Reason == DeadActivityReason.MissingRequiredField) && + da.All(y => y.ReasonNote.ToLowerInvariant().Contains("allied import, no user for"))))); + } + + [Test] + public async Task SavesDeadActivity_WhenFindingUserFails() + { + Mock.Get(_userRepository) + .Setup(s => s.FindUserByAdEmailAsync(It.IsAny())) + .Throws(new Exception("OOops")); + + var transactions = ImportTestTransactions(); + Assert.NotNull(transactions); + Assert.IsNotEmpty(transactions); + Assert.AreEqual(_expectedRecordCount, transactions.Count()); + + var results = await _service.CreateActivity(transactions); + Assert.NotNull(results); + Assert.IsEmpty(results); + + _deadActivityService.Verify(x => x.SaveDeadActivityRange(It.Is>( + da => + da.Count() == _expectedRecordCount && + da.All(y => y.Reason == DeadActivityReason.MissingRequiredField) && + da.All(y => y.ReasonNote.ToLowerInvariant().Contains("allied import, no user for"))))); + } + + [Test] + public async Task SavesDeadActivity_WhenTooManyUserFound() + { + Mock.Get(_userRepository) + .Setup(s => s.SearchUsersAsync(It.IsAny())) + .ReturnsAsync(new List() + { new() { EmployeeId = "One" }, new() { EmployeeId = "Two" } }); + + var transactions = ImportTestTransactions(); + Assert.NotNull(transactions); + Assert.IsNotEmpty(transactions); + Assert.AreEqual(_expectedRecordCount, transactions.Count()); + + var results = await _service.CreateActivity(transactions); + Assert.NotNull(results); + Assert.IsEmpty(results); + + _deadActivityService.Verify(x => x.SaveDeadActivityRange(It.Is>( + da => + da.Count() == _expectedRecordCount && + da.All(y => y.Reason == DeadActivityReason.MissingRequiredField) && + da.All(y => y.ReasonNote.ToLowerInvariant().Contains("allied import, no user for"))))); + } + + [Test] + public async Task SavesDeadActivity_WhenNoHomeDepartmentFound() + { + //Use one user for all lines for simplicity + var user = new AppUser() + { + AdEmail = "johndoe@test.com", + EmployeeId = "11771", + FullName = "John Doe", + Departments = new List() + }; + + Mock.Get(_userRepository).Setup(s => s.SearchUsersAsync(It.IsAny())).ReturnsAsync(new List { user }); + + var transactions = ImportTestTransactions(); + Assert.NotNull(transactions); + Assert.IsNotEmpty(transactions); + Assert.AreEqual(_expectedRecordCount, transactions.Count()); + + var results = await _service.CreateActivity(transactions); + Assert.NotNull(results); + Assert.IsEmpty(results); + + _deadActivityService.Verify(x => x.SaveDeadActivityRange(It.Is>( + da => + da.Count() == _expectedRecordCount && + da.All(y => y.Reason == DeadActivityReason.MissingRequiredField) && + da.All(y => y.ReasonNote.ToLowerInvariant().Contains("reliably determinable home"))))); + } + + [Test] + public async Task SavesDeadActivity_BothKinds() + { + var noHomeDepartmentUser = new AppUser() + { + AdEmail = "StefaniTestAssociate3@test.com", + EmployeeId = "11773", + FullName = "Stefani TestAssociate3", + Departments = new List() + }; + + Mock.Get(_userRepository) + .Setup(s => s.SearchUsersAsync(It.Is(x => x.Query == noHomeDepartmentUser.FullName))) + .ReturnsAsync(new List { noHomeDepartmentUser }); + + var transactions = ImportTestTransactions(); + var expectedHomelessDeadRecordCount = transactions.Count(x => x.Quoter == noHomeDepartmentUser.FullName); + + Assert.NotNull(transactions); + Assert.IsNotEmpty(transactions); + Assert.AreEqual(_expectedRecordCount, transactions.Count()); + + var results = await _service.CreateActivity(transactions); + Assert.NotNull(results); + Assert.IsEmpty(results); + + _deadActivityService.Verify(x => x.SaveDeadActivityRange(It.Is>( + da => + da.Count(y => + y.Reason == DeadActivityReason.MissingRequiredField && + y.ReasonNote.ToLowerInvariant().Contains("allied import, no user for")) == + _expectedRecordCount - expectedHomelessDeadRecordCount))); + + _deadActivityService.Verify(x => x.SaveDeadActivityRange(It.Is>( + da => + da.Count(y => + y.Reason == DeadActivityReason.MissingRequiredField && + y.ReasonNote.ToLowerInvariant().Contains("reliably determinable home")) == expectedHomelessDeadRecordCount))); + } + + [Test] + public async Task FailToSaveDeadActivity_DoesntThrow() + { + Mock.Get(_userRepository).Setup(s => s.FindUserByAdEmailAsync(It.IsAny())).ReturnsAsync((AppUser)null); + _deadActivityService.Setup(x => x.SaveDeadActivityRange(It.IsAny>())) + .ThrowsAsync(new Exception("Oops")); + + var transactions = ImportTestTransactions(); + Assert.NotNull(transactions); + Assert.IsNotEmpty(transactions); + Assert.AreEqual(_expectedRecordCount, transactions.Count()); + + List results = null; + Assert.DoesNotThrowAsync(async () => + { + var res = await _service.CreateActivity(transactions); + results = res.ToList(); + }); + Assert.NotNull(results); + Assert.IsEmpty(results); + + _deadActivityService.Verify(x => x.SaveDeadActivityRange(It.Is>( + da => + da.Count() == _expectedRecordCount && + da.All(y => y.Reason == DeadActivityReason.MissingRequiredField) && + da.All(y => y.ReasonNote.ToLowerInvariant().Contains("allied import, no user for"))))); + } + + [Test] + public async Task FilterAndOrderUpdates_DefaultWithNullExisting() + { + var sourceCreatedNewer = DateTime.Today.Subtract(TimeSpan.FromDays(3)); + var sourceCreatedOlder = DateTime.Today.Subtract(TimeSpan.FromDays(7)); + + var activityProcessingOrder = new ActivityProcessingOrder + { + ExistingActivity = null, + ActivityUpdateItems = new List() + { + new() + { + DepartmentId = 55, + EmployeeId = "11774", + Type = ActivityType.Sale, + Product = Product.MechanicalBreakdownProtection, + Accepted = sourceCreatedOlder, + SourceCreated = sourceCreatedOlder, + SourceId = "1", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "867530986", + AccountStatus = "Cancelled", + MemberName = "ORLANDO TESTCUSTOMER10", + SubAccountNumber = "8675309-AAG" + } + }, + new() + { + DepartmentId = 55, + EmployeeId = "11774", + Type = ActivityType.Sale, + Product = Product.MechanicalBreakdownProtection, + Accepted = sourceCreatedOlder, + SourceCreated = sourceCreatedOlder, + SourceId = "1", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "867530986", + AccountStatus = "Flagged", + MemberName = "ORLANDO TESTCUSTOMER10", + SubAccountNumber = "8675309-AAG" + } + }, + new() + { + DepartmentId = 55, + EmployeeId = "11774", + Type = ActivityType.Sale, + Product = Product.MechanicalBreakdownProtection, + Accepted = sourceCreatedNewer, + SourceCreated = sourceCreatedNewer, + SourceId = "1", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "867530986", + AccountStatus = "Sold", + MemberName = "ORLANDO TESTCUSTOMER10", + SubAccountNumber = "8675309-AAG" + } + }, + new() + { + DepartmentId = 55, + EmployeeId = "11774", + Type = ActivityType.Sale, + Product = Product.MechanicalBreakdownProtection, + Accepted = sourceCreatedOlder, + SourceCreated = sourceCreatedOlder, + SourceId = "1", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "867530986", + AccountStatus = "Reinstated", + MemberName = "ORLANDO TESTCUSTOMER10", + SubAccountNumber = "8675309-AAG" + } + }, + new() + { + DepartmentId = 55, + EmployeeId = "11774", + Type = ActivityType.Sale, + Product = Product.MechanicalBreakdownProtection, + Accepted = sourceCreatedOlder, + SourceCreated = sourceCreatedOlder, + SourceId = "1", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "867530986", + AccountStatus = "Sold", + MemberName = "ORLANDO TESTCUSTOMER10", + SubAccountNumber = "8675309-AAG" + } + }, + new Activity() + { + DepartmentId = 55, + EmployeeId = "11774", + Type = ActivityType.Sale, + Product = Product.MechanicalBreakdownProtection, + Accepted = sourceCreatedOlder, + SourceCreated = sourceCreatedOlder, + SourceId = "1", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "867530986", + AccountStatus = "Cancelled/Re-Quoted", + MemberName = "ORLANDO TESTCUSTOMER10", + SubAccountNumber = "8675309-AAG" + } + } + } + }; + + activityProcessingOrder = _service.FilterAndOrderUpdates(activityProcessingOrder); + + Assert.AreEqual(6, activityProcessingOrder.ActivityUpdateItems.Count); + + Assert.AreEqual(0, activityProcessingOrder.ActivityUpdateItems.FindIndex(x => x.Detail.AccountStatus == "Flagged")); + Assert.AreEqual(1, activityProcessingOrder.ActivityUpdateItems.FindIndex(x => x.Detail.AccountStatus == "Sold" && x.SourceCreated == sourceCreatedOlder)); + Assert.AreEqual(2, activityProcessingOrder.ActivityUpdateItems.FindIndex(x => x.Detail.AccountStatus == "Cancelled/Re-Quoted")); + Assert.AreEqual(3, activityProcessingOrder.ActivityUpdateItems.FindIndex(x => x.Detail.AccountStatus == "Cancelled")); + Assert.AreEqual(4, activityProcessingOrder.ActivityUpdateItems.FindIndex(x => x.Detail.AccountStatus == "Reinstated")); + Assert.AreEqual(5, activityProcessingOrder.ActivityUpdateItems.FindIndex(x => x.SourceCreated == sourceCreatedNewer)); + } + + [Test] + public async Task FilterAndOrderUpdates_DefaultWithNonNullExisting() + { + var sourceCreatedNewer = DateTime.Today.Subtract(TimeSpan.FromDays(3)); + var sourceCreatedOlder = DateTime.Today.Subtract(TimeSpan.FromDays(7)); + + var activityProcessingOrder = new ActivityProcessingOrder + { + ExistingActivity = new Activity + { + Id = 1, + DepartmentId = 55, + EmployeeId = "11774", + Type = ActivityType.Sale, + Product = Product.MechanicalBreakdownProtection, + Accepted = sourceCreatedOlder, + SourceCreated = sourceCreatedOlder, + SourceId = "1", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "867530986", + AccountStatus = "Cancelled/Re-Quoted", + MemberName = "ORLANDO TESTCUSTOMER10", + SubAccountNumber = "8675309-AAG" + } + }, + ActivityUpdateItems = new List() + { + new() + { + DepartmentId = 55, + EmployeeId = "11774", + Type = ActivityType.Sale, + Product = Product.MechanicalBreakdownProtection, + Accepted = sourceCreatedOlder, + SourceCreated = sourceCreatedOlder, + SourceId = "1", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "867530986", + AccountStatus = "Cancelled", + MemberName = "ORLANDO TESTCUSTOMER10", + SubAccountNumber = "8675309-AAG" + } + }, + new() + { + DepartmentId = 55, + EmployeeId = "11774", + Type = ActivityType.Sale, + Product = Product.MechanicalBreakdownProtection, + Accepted = sourceCreatedOlder, + SourceCreated = sourceCreatedOlder, + SourceId = "1", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "867530986", + AccountStatus = "Flagged", + MemberName = "ORLANDO TESTCUSTOMER10", + SubAccountNumber = "8675309-AAG" + } + }, + new() + { + DepartmentId = 55, + EmployeeId = "11774", + Type = ActivityType.Sale, + Product = Product.MechanicalBreakdownProtection, + Accepted = sourceCreatedNewer, + SourceCreated = sourceCreatedNewer, + SourceId = "1", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "867530986", + AccountStatus = "Sold", + MemberName = "ORLANDO TESTCUSTOMER10", + SubAccountNumber = "8675309-AAG" + } + }, + new() + { + DepartmentId = 55, + EmployeeId = "11774", + Type = ActivityType.Sale, + Product = Product.MechanicalBreakdownProtection, + Accepted = sourceCreatedOlder, + SourceCreated = sourceCreatedOlder, + SourceId = "1", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "867530986", + AccountStatus = "Reinstated", + MemberName = "ORLANDO TESTCUSTOMER10", + SubAccountNumber = "8675309-AAG" + } + }, + new() + { + DepartmentId = 55, + EmployeeId = "11774", + Type = ActivityType.Sale, + Product = Product.MechanicalBreakdownProtection, + Accepted = sourceCreatedOlder, + SourceCreated = sourceCreatedOlder, + SourceId = "1", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "867530986", + AccountStatus = "Sold", + MemberName = "ORLANDO TESTCUSTOMER10", + SubAccountNumber = "8675309-AAG" + } + }, + new Activity() + { + DepartmentId = 55, + EmployeeId = "11774", + Type = ActivityType.Sale, + Product = Product.MechanicalBreakdownProtection, + Accepted = sourceCreatedOlder, + SourceCreated = sourceCreatedOlder, + SourceId = "1", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "867530986", + AccountStatus = "Cancelled/Re-Quoted", + MemberName = "ORLANDO TESTCUSTOMER10", + SubAccountNumber = "8675309-AAG" + } + } + } + }; + + activityProcessingOrder = _service.FilterAndOrderUpdates(activityProcessingOrder); + + Assert.AreEqual(4, activityProcessingOrder.ActivityUpdateItems.Count); + + // These items should return an index of -1 since they should be removed in order to not run already run updates against the existing item + Assert.AreEqual(-1, activityProcessingOrder.ActivityUpdateItems.FindIndex(x => x.Detail.AccountStatus == "Flagged")); + Assert.AreEqual(-1, activityProcessingOrder.ActivityUpdateItems.FindIndex(x => x.Detail.AccountStatus == "Sold" && x.SourceCreated == sourceCreatedOlder)); + Assert.AreEqual(0, activityProcessingOrder.ActivityUpdateItems.FindIndex(x => x.Detail.AccountStatus == "Cancelled/Re-Quoted")); + Assert.AreEqual(1, activityProcessingOrder.ActivityUpdateItems.FindIndex(x => x.Detail.AccountStatus == "Cancelled")); + Assert.AreEqual(2, activityProcessingOrder.ActivityUpdateItems.FindIndex(x => x.Detail.AccountStatus == "Reinstated")); + Assert.AreEqual(3, activityProcessingOrder.ActivityUpdateItems.FindIndex(x => x.SourceCreated == sourceCreatedNewer)); + } + + [TestCase(ActivityState.PendingTransfer, 0, true)] // Activity exists, is pending transfer, and not updated + [TestCase(ActivityState.Rejected, 0, true)] // Activity exists, is pending transfer, and not updated + [TestCase(ActivityState.Transferred, 0, true)] // Activity exists, is pending transfer, and not updated + [TestCase(ActivityState.Accepted, 1, false)] // Activity does not exist and is created + [TestCase(ActivityState.Accepted, 0, true)] // Activity exists and is not created + [Test] + public async Task CreateActivity_ExistingActivityInVariousStates_ReturnsExpectedResultsCount(ActivityState activityState, int expectedResultCount, bool returnsActivity) + { + SetupTestUsers(); + var transactions = new List() + { + new AlliedSaleRow() + { + AccountNumber = "1234", + Origin = "LenderWebsite", + ProductType = "GAP", + Status = "Sold", + SaleDate = DateTime.Now.ToString(), + Buyer = "Buyer", + SalesContractNumber = "1", + Issuer = "Abraham TestAssociate1" + } + }; + + var existingActivity = new Activity() + { + Id = 1, + DepartmentId = 55, + EmployeeId = "11774", + Type = ActivityType.Sale, + Product = Product.GAPInsurance, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceSystem = SystemType.Allied, + State = activityState, + Detail = new ActivityDetail() + { + AccountNumber = "867530986", + MemberName = "ORLANDO TESTCUSTOMER10", + SubAccountNumber = "1", + AccountStatus = "Sold" + } + }; + + var newlyCreatedActivity = new Activity() + { + Id = 1, + DepartmentId = 55, + EmployeeId = "11774", + Type = ActivityType.Sale, + Product = Product.GAPInsurance, + Accepted = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceCreated = DateTime.Today.Subtract(TimeSpan.FromDays(7)), + SourceSystem = SystemType.Allied, + State = returnsActivity == true ? activityState : ActivityState.Created, // if an activity does not exist, the new activity is initially in Created state + Detail = new ActivityDetail() + { + AccountNumber = "867530986", + MemberName = "ORLANDO TESTCUSTOMER10", + SubAccountNumber = "1", + AccountStatus = "Sold" + } + }; + + Mock.Get(_activityService) + .Setup(a => a.GetActivityForDetailUpdate(It.IsAny())) + .ReturnsAsync(returnsActivity == true ? existingActivity : (Activity)null); + + Mock.Get(_activityService) + .Setup(a => a.CreateAndRetrieveActivity(It.IsAny())) + .ReturnsAsync(newlyCreatedActivity); + + var resutls = await _service.CreateActivity(transactions); + + Assert.IsNotNull(resutls); + Assert.AreEqual(expectedResultCount, resutls.Count); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Allied/SalesWithCancellationsByEmployee.csv b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Allied/SalesWithCancellationsByEmployee.csv new file mode 100644 index 0000000..8deda23 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Allied/SalesWithCancellationsByEmployee.csv @@ -0,0 +1,13 @@ +Quoter,Issuer,Origin,Product_Type,Status,Sale_Date,Sale_Effective_Date,Cancellation_Date,Cancellation_Reason,Cancelled_By,Sales_Contract_Number,Buyer,Account_Number,LOS_LOAN_APP_ID_TX,Program_Type,Buyer_Cost,Flagged_Reasons +Abraham TestAssociate1,Abraham TestAssociate1,Lender Website,GAP,Sold,11/13/2021,11/13/2021,,,,,RENEE TESTCUSTOMER1,1234567,,Standard,395.00, +Franchelie TestAssociate2,Franchelie TestAssociate2,Lender Website,MBP,Sold,11/13/2021,11/13/2021,,,,86753619-AAG,MAYRIN TESTCUSTOMER2,9876543,,Standard,395.00, +Stefani TestAssociate3,Stefani TestAssociate3,Lender Website,GAP,Sold,11/13/2021,11/13/2021,,,,,JAMES TESTCUSTOMER3,11234567844,,Standard,395.00, +Franchelie TestAssociate2,Franchelie TestAssociate2,Lender Website,GAP,Sold,11/12/2021,11/12/2021,,,,,JOSE TESTCUSTOMER4,19618823214,,Standard,395.00, +Franchelie TestAssociate2,Franchelie TestAssociate2,Lender Website,GAP,Sold,11/12/2021,11/12/2021,,,,,GUSTAVO TESTCUSTOMER5,97456322603,,Standard,395.00, +Amarisa TestAssociate4,Amarisa TestAssociate4,Lender Website,MBP,Sold,11/12/2021,11/12/2021,,,,10253867-AAG,ALESSANDRO TESTCUSTOMER6,75315927194,,Standard,395.00, +Amarisa TestAssociate4,Amarisa TestAssociate4,Lender Website,GAP,Sold,11/12/2021,11/12/2021,,,,,JACOB TESTCUSTOMER7,19137958748,,Standard,395.00, +Amarisa TestAssociate4,Amarisa TestAssociate4,Lender Website,GAP,Sold,11/12/2021,11/15/2021,,,,,FRANK TESTCUSTOMER8,19852347569,,Standard,395.00, +Abraham TestAssociate1,Abraham TestAssociate1,Lender Website,GAP,Flagged,11/12/2021,11/12/2021,,,,,MARIA TESTCUSTOMER9,11103254770,,Standard,395.00, +Amalia TestAssociate5,Amalia TestAssociate5,Lender Website,GAP,Cancelled/Re-Quoted,10/25/2021,10/25/2021,10/26/2021,Buyer Changed Mind,amyxiong@landmarkcu.com,,ORLANDO TESTCUSTOMER10,867530986,,Standard,395.00, +Amalia TestAssociate5,Amalia TestAssociate5,Lender Website,GAP,Cancelled,10/23/2021,10/23/2021,,,,,ORLANDO TESTCUSTOMER11,8675309861,,Standard,395.00, Quoter Changed, +Amalia TestAssociate5,Amalia TestAssociate5,Lender Website,GAP,Cancelled,10/22/2021,10/22/2021,10/27/2021,Loan Did Not Close,PattiGroh@landmarkcu.com,,ORLANDO TESTCUSTOMER12,8675309862,,Standard,395.00, diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/AuthorizationServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/AuthorizationServiceTest.cs new file mode 100644 index 0000000..fceb4ce --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/AuthorizationServiceTest.cs @@ -0,0 +1,491 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Moq; +using NUnit.Framework; + +namespace Application.UnitTests.Services +{ + public class AuthorizationServiceTest + { + private IAuthorizationService _service; + private IDepartmentRepository _departmentRepository; + private IUserAssignmentRepository _userAssignmentRepository; + + [SetUp] + public void Setup() + { + _departmentRepository = Mock.Of(); + _userAssignmentRepository = Mock.Of(); + _service = new AuthorizationService(_departmentRepository, _userAssignmentRepository); + } + + [Test] + public async Task AppUserCanMakeStateChange_UserIsAdmin_ReturnsTrue() + { + var inThePast = DateTime.Now.AddDays(-90); + + var user = new AppUser + { + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 1, + StartDate = inThePast, + EndDate = inThePast.AddDays(-30), + Role = Role.BM + }, + new UserDepartment() + { + DepartmentId = 2, + StartDate = inThePast, + EndDate = null, + Role = Role.Admin + }, + new UserDepartment() + { + DepartmentId = 3, + StartDate = inThePast, + EndDate = null, + Role = Role.Mortgage + } + } + }; + + var result = await _service.AppUserCanMakeStateChange(user, It.IsAny(), It.IsAny(), It.IsAny()); + + Assert.IsTrue(result); + } + + [Test] + public async Task AppUserCanMakeStateChange_UserHasPermissionInTargetDepartment_ReturnsTrue() + { + var inThePast = DateTime.Now.AddDays(-90); + var targetDepartmentId = 2; + var activity = new Activity() + { + State = ActivityState.PendingTransfer, + DepartmentId = 1, + }; + + var user = new AppUser + { + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 1, + StartDate = inThePast, + EndDate = inThePast.AddDays(-30), + Role = Role.Manager + }, + new UserDepartment() + { + DepartmentId = 1, + StartDate = inThePast, + EndDate = null, + Role = Role.Mortgage + }, + new UserDepartment() + { + DepartmentId = targetDepartmentId, + StartDate = inThePast, + EndDate = null, + Role = Role.Manager + } + } + }; + + var result = await _service.AppUserCanMakeStateChange(user, activity, ActivityState.Accepted, targetDepartmentId); + + Assert.IsTrue(result); + } + + [Test] + public async Task AppUserCanMakeStateChange_UserDoesNotHavePermissionInTargetDepartment_ReturnsFalse() + { + var inThePast = DateTime.Now.AddDays(-90); + var targetDepartmentId = 5; + var activity = new Activity() + { + State = ActivityState.PendingTransfer, + DepartmentId = 1, + }; + + var user = new AppUser + { + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 1, + StartDate = inThePast, + EndDate = inThePast.AddDays(-30), + Role = Role.Manager + }, + new UserDepartment() + { + DepartmentId = 2, + StartDate = inThePast, + EndDate = null, + Role = Role.Mortgage + }, + new UserDepartment() + { + DepartmentId = 3, + StartDate = inThePast, + EndDate = null, + Role = Role.Manager + } + } + }; + + var result = await _service.AppUserCanMakeStateChange(user, activity, ActivityState.Accepted, targetDepartmentId); + + Assert.False(result); + } + + [Test] + public async Task AppUserCanMakeStateChange_UserHasAdditionalPermissions_ReturnsTrue() + { + var inThePast = DateTime.Now.AddDays(-90); + var targetDepartmentId = 5; + var activity = new Activity() + { + State = ActivityState.PendingTransfer, + DepartmentId = 1, + }; + + var user = new AppUser + { + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 1, + StartDate = inThePast, + EndDate = null, + Role = Role.Mortgage + } + }, + AdditionalPermissions = new List() { + new UserPermission() + { + Permission = Permission.SetActivityToAcceptedFromPendingTransfer + }, + new UserPermission() + { + Permission = Permission.SetActivityToTransferredFromPendingTransfer + } + } + }; + + var result = await _service.AppUserCanMakeStateChange(user, activity, ActivityState.Accepted, targetDepartmentId); + + Assert.IsTrue(result); + } + + [Test] + public async Task HierarchicalDepartmentPermissionTest_Direct() + { + var testDepartment = new Department() + { + Id = 5, + DepartmentName = "TestName", + RegionCode = Region.North.Code + }; + var yesterday = DateTime.Now.AddDays(-1); + var user = new AppUser + { + AdEmail = "user@landmarkcu.com", EmployeeId = "C0000", + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 5, + Department = testDepartment, + StartDate = yesterday, + Role = Role.BOM + } + } + }; + + Mock.Get(_departmentRepository).Setup(mock => mock.FindDepartmentsByRegionAsync(Region.North)).ReturnsAsync( + new List() + { + testDepartment + }); + + var result = await _service.ValidateHierarchicalDepartmentPermission(user, new List() { Role.BOM }, testDepartment.Id.Value); + Assert.True(result); + result = await _service.ValidateHierarchicalDepartmentPermission(user, new List() { Role.BOM }, 99); + Assert.False(result); + } + + [Test] + public async Task HierarchicalDepartmentPermissionTest_Admin() + { + var testDepartment = new Department() + { + Id = 5, + DepartmentName = "TestName" + }; + var yesterday = DateTime.Now.AddDays(-1); + var user = new AppUser + { + AdEmail = "user@landmarkcu.com", EmployeeId = "C0000", + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 5, + Department = testDepartment, + StartDate = yesterday, + Role = Role.Admin + } + } + }; + + var result = await _service.ValidateHierarchicalDepartmentPermission(user, new List(), testDepartment.Id.Value); + Assert.True(result); + result = await _service.ValidateHierarchicalDepartmentPermission(user, new List(), 99); + Assert.True(result); + } + + + [Test] + [TestCase(true, 1, true)] + [TestCase(false, 1, false)] + public async Task HierarchicalDepartmentPermissionTest_VariedActiveRoleForDepartment(bool isAdmin, int activeDeptId, bool expected) + { + var inThePast = DateTime.Now.AddDays(-90); + + var user = new AppUser + { + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 2, + StartDate = inThePast, + EndDate = inThePast.AddDays(-30), + Role = isAdmin ? Role.Admin : Role.ABM + }, + new UserDepartment() + { + DepartmentId = activeDeptId, + StartDate = inThePast, + EndDate = null, + Role = isAdmin ? Role.Admin : Role.ManagementTraineePFO + } + } + }; + + var result = await _service.ValidateHierarchicalDepartmentPermission(user, Role.DepartmentManagerRoles, activeDeptId); + + Assert.AreEqual(expected, result); + } + + [Test] + public async Task RegionalManagerTest() + { + var testDepartment = new Department() + { + Id = 5, + DepartmentName = "TestName", + RegionCode = Region.North.Code + }; + var yesterday = DateTime.Now.AddDays(-1); + var user = new AppUser + { + AdEmail = "user@landmarkcu.com", EmployeeId = "C0000", + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 5, + Department = testDepartment, + StartDate = yesterday, + Role = Role.RegionalManager + } + } + }; + + Mock.Get(_departmentRepository).Setup(mock => mock.FindDepartmentsByRegionAsync(Region.North)).ReturnsAsync( + new List() + { + testDepartment + }); + + var result = await _service.ValidateIsRegionalManager(user, testDepartment.Id.Value); + Assert.True(result); + result = await _service.ValidateIsRegionalManager(user, 99); + Assert.False(result); + } + + [Test] + public async Task RegionalManager_NoRegion_Test() + { + var testDepartment = new Department() + { + Id = 5, + DepartmentName = "TestName" + }; + var yesterday = DateTime.Now.AddDays(-1); + var user = new AppUser + { + AdEmail = "user@landmarkcu.com", EmployeeId = "C0000", + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 5, + Department = testDepartment, + StartDate = yesterday, + Role = Role.RegionalManager + } + } + }; + + Mock.Get(_departmentRepository).Setup(mock => mock.FindDepartmentsByRegionAsync(Region.North)).ReturnsAsync( + new List() + { + testDepartment + }); + + var result = await _service.ValidateIsRegionalManager(user, testDepartment.Id.Value); + Assert.True(result); + result = await _service.ValidateIsRegionalManager(user, 99); + Assert.False(result); + } + + // User is RM in a non-region department, but is RM of NORTH. + [Test] + public async Task RegionalManager_NoRegion_OfficialRM_Test() + { + var testDepartment = new Department() + { + Id = 5, + DepartmentName = "TestName" + }; + var testDepartmentNorth = new Department() + { + Id = 6, + DepartmentName = "TestName", + RegionCode = Region.North.Code + }; + var yesterday = DateTime.Now.AddDays(-1); + var user = new AppUser + { + AdEmail = "user@landmarkcu.com", EmployeeId = "C0000", + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 5, + Department = testDepartment, + StartDate = yesterday, + Role = Role.RegionalManager + } + } + }; + + Mock.Get(_userAssignmentRepository).Setup(mock => mock.IsUserAssigned(AssignmentType.RegionalManager, user)) + .ReturnsAsync( + new UserAssignment() + { + Key = Region.North.Code + }); + Mock.Get(_departmentRepository).Setup(mock => mock.FindDepartmentsByRegionAsync(Region.North)).ReturnsAsync( + new List() + { + testDepartmentNorth + }); + + var result = await _service.ValidateIsRegionalManager(user, testDepartmentNorth.Id.Value); + Assert.True(result); + result = await _service.ValidateIsRegionalManager(user, 99); + Assert.False(result); + } + + [Test] + public async Task BusinessLineManagerTest() + { + var testDepartment = new Department() + { + Id = 5, + DepartmentName = "TestName", + BusinessLineCode = BusinessLine.Operations.Code + }; + var yesterday = DateTime.Now.AddDays(-1); + var user = new AppUser + { + AdEmail = "user@landmarkcu.com", EmployeeId = "C0000", + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 5, + Department = testDepartment, + StartDate = yesterday, + Role = Role.BusinessLineManager + } + } + }; + + Mock.Get(_departmentRepository) + .Setup(mock => mock.FindDepartmentsByBusinessLineAsync(BusinessLine.Operations)).ReturnsAsync( + new List() + { + testDepartment + }); + + var result = await _service.ValidateIsBusinessLineManager(user, testDepartment.Id.Value); + Assert.True(result); + result = await _service.ValidateIsBusinessLineManager(user, 99); + Assert.False(result); + } + + [Test] + public async Task BusinessLineManager_NoBusinessLine_Test() + { + var testDepartment = new Department() + { + Id = 5, + DepartmentName = "TestName" + }; + var yesterday = DateTime.Now.AddDays(-1); + var user = new AppUser + { + AdEmail = "user@landmarkcu.com", EmployeeId = "C0000", + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 5, + Department = testDepartment, + StartDate = yesterday, + Role = Role.BusinessLineManager + } + } + }; + + Mock.Get(_departmentRepository) + .Setup(mock => mock.FindDepartmentsByBusinessLineAsync(BusinessLine.Operations)).ReturnsAsync( + new List() + { + testDepartment + }); + + var result = await _service.ValidateIsBusinessLineManager(user, testDepartment.Id.Value); + Assert.True(result); + result = await _service.ValidateIsBusinessLineManager(user, 99); + Assert.False(result); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/AutoDecision/ActivityDecisionServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/AutoDecision/ActivityDecisionServiceTest.cs new file mode 100644 index 0000000..4aad86a --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/AutoDecision/ActivityDecisionServiceTest.cs @@ -0,0 +1,677 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Transaction; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace Application.UnitTests.Services.AutoDecision +{ + public class ActivityDecisionServiceTest + { + private ActivityDecisionService _activityDecisionService; + private IActivityService _activityService; + private IActivityDetailService _activityDetailService; + private DebtProtectionAccessoryService _debtProtectionAccessoryService; + private IDnaService _dnaService; + + [SetUp] + public void Setup() + { + var strategies = new List() + { + new ManualDecisionStrategy(), + new ReferralDecisionStrategy(), + new NewCheckingDecisionStrategy(), + new CardSaleDecisionStrategy(), + new MortgageSaleDecisionStrategy(), + new VehicleDecisionStrategy(), + new DebtProtectionEligibleLoanDecisionStrategy() + }; + _activityService = Mock.Of(); + _activityDetailService = Mock.Of(); + _debtProtectionAccessoryService = new DebtProtectionAccessoryService(_activityService, _activityDetailService); + _dnaService = Mock.Of(); + _activityDecisionService = new ActivityDecisionService(strategies, _activityService, _activityDetailService, _debtProtectionAccessoryService, _dnaService); + } + + [Test] + public async Task Decision_Basic_Accept() + { + var activity = new Activity() + { + DepartmentId = 56, + EmployeeId = "11771", + Type = ActivityType.Other, + Product = Product.CheckCard, + Accepted = DateTime.Today, + Detail = new ActivityDetail() + { + AccountStatus = "ACT" + } + }; + var decision = await _activityDecisionService.TryDecisionActivity(activity); + Assert.NotNull(activity); + Assert.AreNotEqual(activity.State, decision.State); + Assert.AreEqual(ActivityState.Accepted, decision.State); + Assert.AreEqual(activity.Id, decision.ActivityId); + } + + [Test] + public async Task Decision_ByState_Basic() + { + var activity = new Activity() + { + DepartmentId = 56, + EmployeeId = "11771", + Type = ActivityType.Other, + Product = Product.CheckCard, + Accepted = DateTime.Today, + Detail = new ActivityDetail() + { + AccountStatus = "ACT" + }, + State = ActivityState.Created + }; + var pagedResult = new PagedResult() + { + Total = 1, + Items = new List() { activity } + }; + Mock.Get(_activityService).Setup(x => x.ListByFilterAsync(It.IsAny())) + .ReturnsAsync(pagedResult); + Mock.Get(_activityService).Setup(x => x.ApplyStateChange(It.IsAny(),true)) + .Verifiable(); + + var changes = await _activityDecisionService.DecisionActivityByState(ActivityState.Created); + + Assert.NotNull(changes); + Assert.IsNotEmpty(changes); + var decision = changes.FirstOrDefault(); + Assert.NotNull(decision); + Assert.AreNotEqual(activity.State, decision.State); + Assert.AreEqual(ActivityState.Accepted, decision.State); + Assert.AreEqual(activity.Id, decision.ActivityId); + + Mock.Get(_activityService).Verify(x => x.ListByFilterAsync(It.IsAny()), Times.Once); + Mock.Get(_activityService).Verify(x => x.ApplyStateChange(It.IsAny(),true), Times.Once); + } + + [Test] + public async Task Decision_ManualReview_Basic() + { + var activity = new Activity() + { + DepartmentId = 56, + EmployeeId = "11771", + Type = ActivityType.Other, + Product = Product.CheckCard, + Accepted = DateTime.Today, + Detail = new ActivityDetail() + { + AccountStatus = "ACT" + }, + State = ActivityState.Created + }; + var pagedResult = new PagedResult() + { + Total = 1, + Items = new List() { activity } + }; + Mock.Get(_activityService).Setup(x => x.ListByFilterAsync(It.IsAny())) + .ReturnsAsync(pagedResult).Verifiable(); + Mock.Get(_activityDetailService) + .Setup(x => x.TryUpdateActivityAccountDetails(It.IsAny>())) + .ReturnsAsync(new List() { activity }).Verifiable(); + Mock.Get(_activityService).Setup(x => x.ApplyStateChange(It.IsAny(),true)) + .Verifiable(); + + var changes = await _activityDecisionService.UpdateAndDecideManualReviewActivities(); + + Assert.NotNull(changes); + Assert.IsNotEmpty(changes); + var decision = changes.FirstOrDefault(); + Assert.NotNull(decision); + Assert.AreNotEqual(activity.State, decision.State); + Assert.AreEqual(ActivityState.Accepted, decision.State); + Assert.AreEqual(activity.Id, decision.ActivityId); + + Mock.Get(_activityService).Verify(); + Mock.Get(_activityDetailService).Verify(); + + Mock.Get(_activityService).Verify(x => x.ListByFilterAsync(It.IsAny()), Times.Once); + Mock.Get(_activityService).Verify(x => x.ApplyStateChange(It.IsAny(),true), Times.Once); + } + + [TestCase(Product.LIC, ActivityType.Sale)] + [TestCase(Product.DirectDeposit, ActivityType.Sale)] + [TestCase(Product.CreditCardBalanceTransfer, ActivityType.Sale)] + public async Task Decision_Basic_NoDecision(Product product, ActivityType type) + { + var activity = new Activity() + { + DepartmentId = 56, + EmployeeId = "11771", + Type = type, + Product = product, + Accepted = DateTime.Today + }; + var decision = await _activityDecisionService.TryDecisionActivity(activity); + Assert.NotNull(activity); + Assert.AreNotEqual(activity.State, decision.State); + Assert.AreEqual(ActivityState.ManualReview, decision.State); + Assert.AreEqual(activity.Id, decision.ActivityId); + } + + [TestCase("APPR", ActivityState.ManualReview)] + [TestCase("ACT", ActivityState.Accepted)] + [TestCase("CLS", ActivityState.Rejected)] + [TestCase("YeeHaw", ActivityState.ManualReview)] + [TestCase(null, ActivityState.Rejected)] + public async Task Decision_Mortgage(string status, ActivityState state) + + { + var activity = new Activity() + { + DepartmentId = 56, + EmployeeId = "11771", + Type = ActivityType.Sale, + Detail = new ActivityDetail() + { + AccountStatus = status + }, + Product = Product.Mortgage, + Accepted = DateTime.Today + }; + var decision = await _activityDecisionService.TryDecisionActivity(activity); + Assert.NotNull(activity); + Assert.AreEqual(state, decision.State); + Assert.AreEqual(activity.Id, decision.ActivityId); + } + + [TestCase(Product.ATMCard, ActivityState.PendingReferral, "60")] + [TestCase(Product.CheckCard, ActivityState.PendingReferral, "60")] + [TestCase(Product.NewChecking, ActivityState.PendingReferral, "60")] + [TestCase(Product.Mortgage, ActivityState.PendingReferral, "60")] + [TestCase(Product.FirstMortgageReferral, ActivityState.PendingReferral, "120")] + [TestCase(Product.DirectDeposit, ActivityState.PendingReferral, "60")] + [TestCase(Product.CreditCardBalanceTransfer, ActivityState.PendingReferral, "60")] + [TestCase(Product.LIC, ActivityState.ManualReview, "Not In Final Station")] + public async Task Decision_Referrals(Product product, ActivityState expectedState, string expectedExpirationDays) + { + var activity = new Activity() + { + DepartmentId = 56, + EmployeeId = "11771", + Type = ActivityType.Referral, + Product = product, + Accepted = DateTime.Today, + Detail = new ActivityDetail() + }; + var decision = await _activityDecisionService.TryDecisionActivity(activity); + Assert.NotNull(activity); + Assert.AreEqual(expectedState, decision.State); + Assert.AreEqual(activity.Id, decision.ActivityId); + Assert.IsTrue(decision.Note.Contains(expectedExpirationDays)); + } + + [TestCase(null, ActivityState.ManualReview)] + [TestCase("NotRealStation", ActivityState.ManualReview)] + [TestCase("LIC Referral-Qualified", ActivityState.Accepted)] + [TestCase("LIC Referral- Not Qualified", ActivityState.Rejected)] + public async Task Decision_LIC_Referrals(String stationName, ActivityState expectedState) + { + var activity = new Activity() + { + DepartmentId = 56, + EmployeeId = "11771", + Type = ActivityType.Referral, + Product = Product.LIC, + Accepted = DateTime.Today, + Detail = new ActivityDetail() + { + StationName = stationName + } + }; + var decision = await _activityDecisionService.TryDecisionActivity(activity); + Assert.NotNull(activity); + Assert.AreEqual(expectedState, decision.State); + Assert.AreEqual(activity.Id, decision.ActivityId); + } + + [TestCase(Product.GAPInsurance)] + [TestCase(Product.MechanicalBreakdownProtection)] + [TestCase(Product.DebtProtectionEligibleLoan)] + [TestCase(Product.Vehicle)] + [TestCase(Product.CreditCard)] + public void Decision_Referrals_Throws(Product product) + { + var activity = new Activity() + { + DepartmentId = 56, + EmployeeId = "11771", + Type = ActivityType.Referral, + Product = product, + Accepted = DateTime.Today + }; + Assert.ThrowsAsync(async () => await _activityDecisionService.TryDecisionActivity(activity)); + } + + + [TestCase(Product.ATMCard, "ACT", ActivityState.Accepted)] + [TestCase(Product.CheckCard, "ACT", ActivityState.Accepted)] + [TestCase(Product.ATMCard, "APPR", ActivityState.Accepted)] + [TestCase(Product.CheckCard, "APPR", ActivityState.Accepted)] + [TestCase(Product.ATMCard, "ORIG", ActivityState.Accepted)] + [TestCase(Product.CheckCard, "ORIG", ActivityState.Accepted)] + [TestCase(Product.ATMCard, "CLS", ActivityState.Accepted)] + [TestCase(Product.CheckCard, "CLS", ActivityState.Accepted)] + public async Task Decision_Cards(Product product, string status, ActivityState state) + { + var activity = new Activity() + { + DepartmentId = 56, + EmployeeId = "11771", + Type = ActivityType.Other, + Detail = new ActivityDetail() + { + AccountStatus = status + }, + Product = product, + Accepted = DateTime.Today + }; + var decision = await _activityDecisionService.TryDecisionActivity(activity); + Assert.NotNull(activity); + Assert.AreEqual(state, decision.State); + Assert.AreEqual(activity.Id, decision.ActivityId); + } + + [TestCase("ACT", ActivityState.Accepted)] + [TestCase("APPR", ActivityState.ManualReview)] + [TestCase("ORIG", ActivityState.ManualReview)] + [TestCase("CLS", ActivityState.Rejected)] + [TestCase(null, ActivityState.Rejected)] + public async Task Decision_NewChecking(string status, ActivityState state) + { + var activity = new Activity() + { + DepartmentId = 56, + EmployeeId = "11771", + Type = ActivityType.Sale, + Detail = new ActivityDetail() + { + AccountStatus = status + }, + Product = Product.NewChecking, + Accepted = DateTime.Today + }; + var decision = await _activityDecisionService.TryDecisionActivity(activity); + Assert.NotNull(activity); + Assert.AreEqual(state, decision.State); + Assert.AreEqual(activity.Id, decision.ActivityId); + } + + [TestCase("ACT", ActivityState.Accepted)] + [TestCase("APPR", ActivityState.ManualReview)] + [TestCase("ORIG", ActivityState.ManualReview)] + [TestCase("CLS", ActivityState.Rejected)] + [TestCase(null, ActivityState.Rejected)] + public async Task Decision_NewDigitalBankingChecking(string status, ActivityState state) + { + var activity = new Activity() + { + DepartmentId = 43, + EmployeeId = "DigitalBankingAppUserEmployeeId", + Type = ActivityType.Sale, + Detail = new ActivityDetail() + { + AccountStatus = status + }, + Product = Product.NewChecking, + Accepted = DateTime.Today + }; + var decision = await _activityDecisionService.TryDecisionActivity(activity); + Assert.NotNull(activity); + Assert.AreEqual(state, decision.State); + Assert.AreEqual(activity.Id, decision.ActivityId); + } + + [TestCase("ACT", ActivityState.Accepted)] + [TestCase("APPR", ActivityState.ManualReview)] + [TestCase("CLS", ActivityState.Rejected)] + [TestCase("ORIG", ActivityState.ManualReview)] + [TestCase(null, ActivityState.Rejected)] + public async Task Decision_Vehicle(string status, ActivityState state) + { + var activity = new Activity() + { + DepartmentId = 56, + EmployeeId = "11771", + Type = ActivityType.Sale, + Detail = new ActivityDetail() + { + AccountStatus = status + }, + Product = Product.Vehicle, + Accepted = DateTime.Today + }; + var decision = await _activityDecisionService.TryDecisionActivity(activity); + Assert.NotNull(activity); + Assert.AreEqual(state, decision.State); + Assert.AreEqual(activity.Id, decision.ActivityId); + } + + [TestCase("ACT", ActivityState.Accepted)] + [TestCase("APPR", ActivityState.ManualReview)] + [TestCase("CLS", ActivityState.Rejected)] + [TestCase("ORIG", ActivityState.ManualReview)] + [TestCase(null, ActivityState.Rejected)] + public async Task Decision_DPEligibleLoan(string status, ActivityState state) + { + var activity = new Activity() + { + DepartmentId = 56, + EmployeeId = "11771", + Type = ActivityType.Sale, + Detail = new ActivityDetail() + { + AccountStatus = status + }, + Product = Product.DebtProtectionEligibleLoan, + Accepted = DateTime.Today + }; + var decision = await _activityDecisionService.TryDecisionActivity(activity); + Assert.NotNull(activity); + Assert.AreEqual(state, decision.State); + Assert.AreEqual(activity.Id, decision.ActivityId); + } + + [Test] + public async Task UpdateAndDecideDirectDepositPendingReferralActivities_OK() + { + var activity = new Activity + { + Id = 123, + DepartmentId = 56, + EmployeeId = "11771", + Type = ActivityType.Referral, + Detail = new ActivityDetail + { + AccountNumber = "12345" + }, + Created = DateTime.Parse("2021/11/23 10:00:00"), + Product = Product.DebtProtectionEligibleLoan, + Accepted = DateTime.Today + }; + + var pagedResult = new PagedResult + { + Items = new[] + { + activity + } + }; + + Mock.Get(_activityService).Setup(x => x.ListByFilterAsync(It.Is(x => + x.Limit.Equals(1000) && x.State.Contains(ActivityState.PendingReferral) && x.Product.Contains(Product.DirectDeposit)))) + .ReturnsAsync(pagedResult).Verifiable(); + + Mock.Get(_dnaService).Setup(x => x.GetTransactionHistory(It.Is(x => x.Equals("12345")), + It.Is(x => x.Equals(DateTime.Parse("2021/11/23 10:00:00"))), It.IsAny(), It.Is>(x => x.Contains("XDEP")))) + .ReturnsAsync(new List + { + new TransactionHistory + { + Id = "1" + } + }).Verifiable(); + + var decision = await _activityDecisionService.UpdateAndDecideDirectDepositPendingReferralActivities(); + + Mock.Get(_activityService).Verify(); + Mock.Get(_dnaService).Verify(); + Assert.NotNull(decision); + var firstDecision = decision.First(); + Assert.AreEqual(ActivityState.Accepted, firstDecision.State); + Assert.AreEqual(123, firstDecision.ActivityId); + } + + [Test] + public async Task UpdateAndDecideDirectDepositPendingReferralActivities_NullReturned_RejectActivity() + { + var activity = new Activity + { + Id = 123, + DepartmentId = 56, + EmployeeId = "11771", + Type = ActivityType.Referral, + Detail = new ActivityDetail + { + AccountNumber = "12345" + }, + Created = DateTime.Parse("2021/11/23 10:00:00"), + Product = Product.DebtProtectionEligibleLoan, + Accepted = DateTime.Today + }; + + var pagedResult = new PagedResult + { + Items = new[] + { + activity + } + }; + + Mock.Get(_activityService).Setup(x => x.ListByFilterAsync(It.Is(x => + x.Limit.Equals(1000) && x.State.Contains(ActivityState.PendingReferral) && x.Product.Contains(Product.DirectDeposit)))) + .ReturnsAsync(pagedResult).Verifiable(); + + Mock.Get(_dnaService).Setup(x => x.GetTransactionHistory(It.Is(x => x.Equals("12345")), + It.Is(x => x.Equals(DateTime.Parse("2021/11/23 10:00:00"))), It.IsAny(), It.Is>(x => x.Contains("XDEP")))) + .ReturnsAsync((List)null).Verifiable(); + + var decision = await _activityDecisionService.UpdateAndDecideDirectDepositPendingReferralActivities(); + + Mock.Get(_activityService).Verify(); + Mock.Get(_dnaService).Verify(); + Assert.NotNull(decision); + var firstDecision = decision.First(); + Assert.AreEqual(ActivityState.Rejected, firstDecision.State); + Assert.AreEqual(123, firstDecision.ActivityId); + } + + [Test] + public async Task UpdateAndDecideDirectDepositPendingReferralActivities_None_NoActivitiesReturned() + { + var pagedResult = new PagedResult(); + + Mock.Get(_activityService).Setup(x => x.ListByFilterAsync(It.Is(x => + x.Limit.Equals(1000) && x.State.Contains(ActivityState.PendingReferral) && x.Product.Contains(Product.DirectDeposit)))) + .ReturnsAsync(pagedResult).Verifiable(); + + var decision = await _activityDecisionService.UpdateAndDecideDirectDepositPendingReferralActivities(); + + Mock.Get(_activityService).Verify(); + Mock.Get(_dnaService).Verify(x => x.GetTransactionHistory(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), + Times.Never); + Assert.IsEmpty(decision); + } + + [Test] + public async Task UpdateAndDecideDirectDepositPendingReferralActivities_Empty_NoActivitiesReturned() + { + var pagedResult = new PagedResult + { + Items = new Activity[] { } + }; + + Mock.Get(_activityService).Setup(x => x.ListByFilterAsync(It.Is(x => + x.Limit.Equals(1000) && x.State.Contains(ActivityState.PendingReferral) && x.Product.Contains(Product.DirectDeposit)))) + .ReturnsAsync(pagedResult).Verifiable(); + + var decision = await _activityDecisionService.UpdateAndDecideDirectDepositPendingReferralActivities(); + + Mock.Get(_activityService).Verify(); + Mock.Get(_dnaService).Verify(x => x.GetTransactionHistory(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), + Times.Never); + Assert.IsEmpty(decision); + } + + [Test] + public async Task UpdateAndDecideDpEligibleLoanActivities_NoPagedResults_Ok() + { + var activity = new Activity + { + Id = 123, + DepartmentId = 56, + EmployeeId = "11771", + Type = ActivityType.Sale, + MatchingDpAccessoryActivityId = 0, + + Detail = new ActivityDetail + { + AccountNumber = "12345", + DebtProtectionAddDate = DateTime.UtcNow, + IsDebtProtectionEligible = true, + IsGapInsuranceEligible = true + }, + Created = DateTime.Parse("2021/11/23 10:00:00"), + Product = Product.Vehicle, + Accepted = DateTime.Today + }; + + var pagedResult = new PagedResult + { + Items = null, + Total = 0 + }; + + Mock.Get(_activityService).Setup(x => x.ListByFilterAsync(It.Is(x => + x.Limit.Equals(1000) && x.State.Contains(ActivityState.Accepted) && x.Product.Contains(Product.Vehicle)))) + .ReturnsAsync(pagedResult).Verifiable(); + + Mock.Get(_activityDetailService).Setup(x => x.TryUpdateActivityAccountDetails(It.IsAny>())) + .ReturnsAsync(new List() { activity }).Verifiable(); + + Mock.Get(_activityDetailService).Setup(x => x.TryUpdateDpEligibleLoanDetail(It.IsAny>())) + .ReturnsAsync(new List() { activity }).Verifiable(); + + var decision = await _activityDecisionService.UpdateAndDecideDpEligibleLoanActivities(); + + Mock.Get(_activityService).Verify(); + Mock.Get(_dnaService) + .Verify(x => x.GetTransactionHistory(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), + Times.Never); + Assert.IsEmpty(decision); + } + + [Test] + public async Task UpdateAndDecideDpEligibleLoanActivities_GapInsuranceWasEligible_DebtProtectionAdded() + { + var activity = new Activity + { + Id = 123, + DepartmentId = 56, + EmployeeId = "11771", + Type = ActivityType.Sale, + MatchingDpAccessoryActivityId = 0, + + Detail = new ActivityDetail + { + AccountNumber = "12345", + DebtProtectionAddDate = DateTime.UtcNow, + IsDebtProtectionEligible = true, + IsGapInsuranceEligible = true + }, + Created = DateTime.Parse("2021/11/23 10:00:00"), + Product = Product.Vehicle, + Accepted = DateTime.Today + }; + + var pagedResult = new PagedResult + { + Items = new[] + { + activity + }, + Total = 1 + }; + + Mock.Get(_activityService).Setup(x => x.ListByFilterAsync(It.Is(x => + x.Limit.Equals(1000) && x.State.Contains(ActivityState.Accepted) && x.Product.Contains(Product.Vehicle)))) + .ReturnsAsync(pagedResult).Verifiable(); + + Mock.Get(_activityDetailService).Setup(x => x.TryUpdateActivityAccountDetails(It.IsAny>())) + .ReturnsAsync(new List() { activity }).Verifiable(); + + Mock.Get(_activityDetailService).Setup(x => x.TryUpdateDpEligibleLoanDetail(It.IsAny>())) + .ReturnsAsync(new List() { activity }).Verifiable(); + + var decision = await _activityDecisionService.UpdateAndDecideDpEligibleLoanActivities(); + + Mock.Get(_activityService).Verify(); + Mock.Get(_dnaService) + .Verify(x => x.GetTransactionHistory(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), + Times.Never); + Assert.NotNull(decision); + } + + [Test] + public async Task UpdateAndDecideDpEligibleLoanActivities_GapInsuranceWasNotEligible_DebtProtectionAdded() + { + var activity = new Activity + { + Id = 123, + DepartmentId = 56, + EmployeeId = "11771", + Type = ActivityType.Sale, + MatchingDpAccessoryActivityId = 0, + + Detail = new ActivityDetail + { + AccountNumber = "12345", + DebtProtectionAddDate = DateTime.UtcNow, + IsDebtProtectionEligible = true, + IsGapInsuranceEligible = false + }, + Created = DateTime.Parse("2021/11/23 10:00:00"), + Product = Product.Vehicle, + Accepted = DateTime.Today + }; + + var pagedResult = new PagedResult + { + Items = new[] + { + activity + }, + Total = 1 + }; + + Mock.Get(_activityService).Setup(x => x.ListByFilterAsync(It.Is(x => + x.Limit.Equals(1000) && x.State.Contains(ActivityState.Accepted) && x.Product.Contains(Product.Vehicle)))) + .ReturnsAsync(pagedResult).Verifiable(); + + Mock.Get(_activityDetailService).Setup(x => x.TryUpdateActivityAccountDetails(It.IsAny>())) + .ReturnsAsync(new List() { activity }).Verifiable(); + + Mock.Get(_activityDetailService).Setup(x => x.TryUpdateDpEligibleLoanDetail(It.IsAny>())) + .ReturnsAsync(new List() { activity }).Verifiable(); + + var decision = await _activityDecisionService.UpdateAndDecideDpEligibleLoanActivities(); + + Mock.Get(_activityService).Verify(); + Mock.Get(_dnaService).Verify(x => x.GetTransactionHistory(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), + Times.Never); + Assert.NotNull(decision); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/AutoDecision/DebtProtectionAccessoryServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/AutoDecision/DebtProtectionAccessoryServiceTest.cs new file mode 100644 index 0000000..2bbbd96 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/AutoDecision/DebtProtectionAccessoryServiceTest.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Moq; +using NUnit.Framework; + +namespace Application.UnitTests.Services.AutoDecision +{ + public class DebtProtectionAccessoryServiceTest + { + private ActivityDecisionService _activityDecisionService; + private IActivityService _activityService; + private IActivityDetailService _activityDetailService; + private DebtProtectionAccessoryService _debtProtectionAccessoryService; + private IDnaService _dnaService; + + [SetUp] + public void Setup() + { + var strategies = new List() + { + new ManualDecisionStrategy(), + new ReferralDecisionStrategy(), + new NewCheckingDecisionStrategy(), + new CardSaleDecisionStrategy(), + new MortgageSaleDecisionStrategy(), + new VehicleDecisionStrategy(), + new DebtProtectionEligibleLoanDecisionStrategy() + }; + _activityService = Mock.Of(); + _activityDetailService = Mock.Of(); + _debtProtectionAccessoryService = new DebtProtectionAccessoryService(_activityService, _activityDetailService); + } + + [Test] + public async Task ProcessDpAccessoryUpdates_Product_Vehicle_DP_Eligible_GapEligible_Ok() + { + var saleactivity = new List() + { + new Activity () + { + Id = 123, + DepartmentId = 56, + EmployeeId = "11771", + Type = ActivityType.Sale, + MatchingDpAccessoryActivityId = 0, + + Detail = new ActivityDetail + { + AccountNumber = "12345", + DebtProtectionAddDate = DateTime.UtcNow, + IsDebtProtectionEligible = true, + IsGapInsuranceEligible = true + }, + Created = DateTime.Parse("2021/11/23 10:00:00"), + Product = Product.Vehicle, + Accepted = DateTime.Today + } + }; + + var updatedActivities = await _debtProtectionAccessoryService.ProcessDpAccessoryUpdates(saleactivity); + Mock.Get(_activityService).Verify(); + Assert.NotNull(updatedActivities); + Assert.AreEqual(updatedActivities.First().Detail.IsDebtProtectionEligible, true); + Assert.AreEqual(updatedActivities.First().Detail.IsGapInsuranceEligible, true); + } + + [Test] + public async Task ProcessDpAccessoryUpdates_Product_Mortgage_DP_NotEligible_Gap_NotEligible_Ok() + { + var saleactivity = new List() + { + new Activity () + { + Id = 123, + DepartmentId = 56, + EmployeeId = "11771", + Type = ActivityType.Sale, + MatchingDpAccessoryActivityId = 0, + + Detail = new ActivityDetail + { + AccountNumber = "12345", + DebtProtectionAddDate = DateTime.UtcNow, + IsDebtProtectionEligible = false, + IsGapInsuranceEligible = false + }, + Created = DateTime.Parse("2021/11/23 10:00:00"), + Product = Product.Vehicle, + Accepted = DateTime.Today + } + }; + + var updatedActivities = await _debtProtectionAccessoryService.ProcessDpAccessoryUpdates(saleactivity); + Mock.Get(_activityService).Verify(); + Assert.AreEqual(0, updatedActivities.Count()); + } + + [Test] + public async Task ProcessDpAccessoryUpdates_Product_DebtProtection_DP_Eligible_Gap_NotEligible_Ok() + { + var saleactivity = new List() + { + new Activity () + { + Id = 123, + DepartmentId = 56, + EmployeeId = "11771", + Type = ActivityType.Sale, + MatchingDpAccessoryActivityId = 0, + + Detail = new ActivityDetail + { + AccountNumber = "12345", + DebtProtectionRemoveDate = DateTime.UtcNow, + DebtProtectionAddDate = DateTime.UtcNow.AddMonths(-1), + IsDebtProtectionEligible = true, + IsGapInsuranceEligible = false + }, + Created = DateTime.Parse("2021/11/23 10:00:00"), + Product = Product.DebtProtectionEligibleLoan, + Accepted = DateTime.Today + } + }; + + var updatedActivities = await _debtProtectionAccessoryService.ProcessDpAccessoryUpdates(saleactivity); + Mock.Get(_activityService).Verify(); + Assert.NotNull(updatedActivities); + Assert.AreEqual(updatedActivities.First().Detail.IsDebtProtectionEligible, true); + Assert.AreEqual(updatedActivities.First().Detail.IsGapInsuranceEligible, false); + } + + [Test] + public async Task ProcessDpAccessoryUpdates_Product_Vehicle_NoMatchingAccessoryNoAddDate_Ok() + { + var saleactivity = new List() + { + new Activity () + { + Id = 123, + DepartmentId = 56, + EmployeeId = "11771", + Type = ActivityType.Sale, + MatchingDpAccessoryActivityId = 0, + + Detail = new ActivityDetail + { + AccountNumber = "12345", + DebtProtectionAddDate = null, + IsDebtProtectionEligible = true, + IsGapInsuranceEligible = true + }, + Created = DateTime.Parse("2021/11/23 10:00:00"), + Product = Product.Vehicle, + Accepted = DateTime.Today + } + }; + + var updatedActivities = await _debtProtectionAccessoryService.ProcessDpAccessoryUpdates(saleactivity); + Mock.Get(_activityService).Verify(); + Assert.NotNull(updatedActivities); + Assert.AreEqual(updatedActivities.Count(), 0); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/CommissionServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/CommissionServiceTest.cs new file mode 100644 index 0000000..95345bb --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/CommissionServiceTest.cs @@ -0,0 +1,156 @@ +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Moq; +using NUnit.Framework; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Linq; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; + +namespace Application.UnitTests.Services +{ + public class CommissionServiceTest + { + private CommissionService _service; + private Mock _mockCommissionRepository; + private Mock _mockUnitOfWork; + + [SetUp] + public void Setup() + { + _mockCommissionRepository = new Mock(); + _mockUnitOfWork = new Mock(); + _service = new CommissionService(_mockCommissionRepository.Object, _mockUnitOfWork.Object); + } + + [Test] + public async Task GetCommissionsAsync_ReturnsCommissionsFromRepo() + { + var commissions = new List + { + new Commission + { + Id = 1, + Product = Product.ATMCard, + RatePerProduct = 5, + Role = Role.Teller + }, + new Commission + { + Id = 2, + Product = Product.CheckCard, + RatePerProduct = 10, + Role = Role.ABM + } + }.AsReadOnly(); + _mockCommissionRepository.Setup(repo => repo.GetCommissionsAsync()).ReturnsAsync(commissions); + + var result = await _service.GetCommissionsAsync(); + + Assert.NotNull(result); + Assert.AreEqual(2, result.Count); + Assert.AreEqual(commissions[0].Id, result.ElementAt(0).Id); + Assert.AreEqual(commissions[0].Product, result.ElementAt(0).Product); + Assert.AreEqual(commissions[0].RatePerProduct, result.ElementAt(0).RatePerProduct); + Assert.AreEqual(commissions[0].Role, result.ElementAt(0).Role); + Assert.AreEqual(commissions[1].Id, result.ElementAt(1).Id); + Assert.AreEqual(commissions[1].Product, result.ElementAt(1).Product); + Assert.AreEqual(commissions[1].RatePerProduct, result.ElementAt(1).RatePerProduct); + Assert.AreEqual(commissions[1].Role, result.ElementAt(1).Role); + } + + [Test] + public async Task SaveCommissionsAsync_NewCommission_CallRepoWithCommissionToSave() + { + var actualUpdates = new List(); + var newCommission = new Commission + { + Id = 0, + Product = Product.ATMCard, + RatePerProduct = 5, + Role = Role.Teller + }; + + var commissionsToUpdate = new List { newCommission }; + + _mockCommissionRepository.Setup(repo => repo.FindExistingCommission(newCommission.Role, newCommission.Product)) + .ReturnsAsync((Commission)null); + _mockCommissionRepository.Setup(repo => repo.SaveCommission(newCommission)); + + await _service.SaveCommissionsAsync(commissionsToUpdate); + + _mockCommissionRepository.Verify(repo => repo.FindExistingCommission(newCommission.Role, newCommission.Product), Times.Once); + _mockCommissionRepository.Verify(repo => repo.SaveCommission(newCommission), Times.Once); + _mockUnitOfWork.Verify(context => context.CompleteAsync(), Times.Once); + } + + [Test] + public void SaveCommissionsAsync_NewCommissionMatchInRepo_ThrowsCommissionExistsException() + { + var newCommission = new Commission + { + Id = 0, + Product = Product.CheckCard, + RatePerProduct = 10, + Role = Role.ABM + }; + var commissions = new List { newCommission }; + + var existingInRepo = new Commission + { + Id = 1, + Product = Product.CheckCard, + RatePerProduct = 15, + Role = Role.ABM + }; + + _mockCommissionRepository.Setup(repo => repo.FindExistingCommission(newCommission.Role, newCommission.Product)) + .ReturnsAsync(existingInRepo); + + var ex = Assert.ThrowsAsync(async () => + { + await _service.SaveCommissionsAsync(commissions); + }); + + Assert.AreEqual("Commission already exists for Role=ABM, Product=CheckCard", ex.Message); + _mockCommissionRepository.Verify(repo => repo.FindExistingCommission(newCommission.Role, newCommission.Product), Times.Once); + _mockCommissionRepository.Verify(repo => repo.SaveCommission(It.IsAny()), Times.Never); + _mockUnitOfWork.Verify(context => context.CompleteAsync(), Times.Never); + _mockCommissionRepository.VerifyNoOtherCalls(); + } + + [Test] + public async Task SaveCommissionsAsync_UpdateCommission_SetsRate() + { + var updates = new Commission + { + Id = 1, + Product = Product.CheckCard, + RatePerProduct = 20, + Role = Role.ABM + }; + var commissions = new List { updates }; + + var commissionToUpdate = new Commission + { + Id = 1, + Product = Product.CheckCard, + RatePerProduct = 15, + Role = Role.ABM + }; + Assert.AreEqual(15, commissionToUpdate.RatePerProduct); + + _mockCommissionRepository.Setup(repo => repo.FindExistingCommission(updates.Id)) + .ReturnsAsync(commissionToUpdate); + + await _service.SaveCommissionsAsync(commissions); + + Assert.AreEqual(20, commissionToUpdate.RatePerProduct); + _mockCommissionRepository.Verify(repo => repo.FindExistingCommission(updates.Id), Times.Once); + _mockUnitOfWork.Verify(context => context.CompleteAsync(), Times.Once); + _mockCommissionRepository.VerifyNoOtherCalls(); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DashboardServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DashboardServiceTest.cs new file mode 100644 index 0000000..096939b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DashboardServiceTest.cs @@ -0,0 +1,214 @@ +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using System.Collections.Generic; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApi.Services.Rules; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.API.Internal.Operations.IncentiveApi.Models; + +namespace Application.UnitTests.Services +{ + public class DashboardServiceTest + { + private DashboardService _dashboardService; + private IActivityRepository _mockActivityRepository; + private IGoalRepository _mockGoalRepository; + private IDepartmentService _mockDepartmentService; + private IBusinessRuleService _mockBusinessRuleService; + + [SetUp] + public void Setup() + { + _mockDepartmentService = Mock.Of(); + _mockActivityRepository = Mock.Of(); + _mockGoalRepository = Mock.Of(); + _mockBusinessRuleService = Mock.Of(); + _dashboardService = new DashboardService(_mockDepartmentService, _mockActivityRepository, + _mockGoalRepository, _mockBusinessRuleService); + } + + // We need a test, given some activity, returns it differently for personal or department progress. + // So we'll need some activities: + // 1. A New Checking Referral + // 2. A New Checking Sale + // 3. An LIC Referral + // 4. An CreditCard Sale + // 5. A First Mortgage Referral and Sale + // This should credit 2 to the person and 1 to the department, unless the department is Member Services. + [TestCase(55, 1,2)] + [TestCase(56 /* Member Services */, 2,2)] + public async Task Special_Rules_Sample_NonMemberServices(int testDepartmentId, int expectedDepartmentTotal, int expectedPersonalTotal) + { + var range = new MonthRange(2021, 1, 2021, 2); + var user = new AppUser() + { + EmployeeId = "11771", + FullName = "Test Worker" + }; + var departments = new List() + { + new Department() + { + Id = testDepartmentId, + State = DepartmentState.Active, + DepartmentName = "Developers", + BusinessLineCode = BusinessLine.MemberServices.Code, + RegionCode = Region.North.Code + } + }; + var userDepartments = new List() + { + new UserDepartment() + { + Department = departments.First(x => x.Id == testDepartmentId), + Role = Role.BM, + Employee = user, + StartDate = range.StartTime, + EndDate = range.EndTime + } + }; + var goals = new List() + { + new Goal() + { + Product = Product.NewChecking, + EndDate = range.EndTime, + StartDate = range.StartTime, + Value = 2, + DepartmentId = testDepartmentId + }, + new Goal() + { + Product = Product.NewChecking, + EndDate = range.EndTime, + StartDate = range.StartTime, + Value = 2, + DepartmentId = testDepartmentId, + EmployeeId = "11771" + } + }; + var activities = new List() + { + new Activity() + { + Id = 100, + Product = Product.NewChecking, + Type = ActivityType.Referral, + State = ActivityState.Accepted, + Accepted = range.StartTime, + Created = range.StartTime, + DepartmentId = testDepartmentId, + EmployeeId = "11771", + Detail = new ActivityDetail(), + MatchingActivityId = 101 + }, + new Activity() + { + Id = 101, + Product = Product.NewChecking, + Type = ActivityType.Sale, + State = ActivityState.Accepted, + Accepted = range.StartTime, + Created = range.StartTime, + DepartmentId = testDepartmentId, + EmployeeId = "11771", + Detail = new ActivityDetail(), + MatchingActivityId = 100 + }, + new Activity() + { + Id = 102, + Product = Product.LIC, + Type = ActivityType.Referral, + State = ActivityState.Accepted, + Accepted = range.StartTime, + Created = range.StartTime, + DepartmentId = testDepartmentId, + EmployeeId = "11771", + Detail = new ActivityDetail() + }, + new Activity() + { + Id = 103, + Product = Product.CreditCard, + Type = ActivityType.Sale, + State = ActivityState.Accepted, + Accepted = range.StartTime, + Created = range.StartTime, + DepartmentId = testDepartmentId, + EmployeeId = "11771", + Detail = new ActivityDetail() + }, + new Activity() + { + Id = 104, + Product = Product.FirstMortgageReferral, + Type = ActivityType.Sale, + State = ActivityState.Accepted, + Accepted = range.StartTime, + Created = range.StartTime, + DepartmentId = testDepartmentId, + EmployeeId = "11771", + Detail = new ActivityDetail() + }, + new Activity() + { + Id = 105, + Product = Product.FirstMortgageReferral, + Type = ActivityType.Referral, + State = ActivityState.PendingReferral, + Accepted = range.StartTime, + Created = range.StartTime, + DepartmentId = testDepartmentId, + EmployeeId = "11771", + Detail = new ActivityDetail() + } + }; + + var departmentRules = new List() + { + new NewCheckingReferralRule(56) // this simulates looking up 200. + }; + var personalRules = new List() + { + }; + + Mock.Get(_mockDepartmentService).Setup(s => s.FindDepartmentAssignmentsForRange(testDepartmentId,range)) + .ReturnsAsync(userDepartments); + Mock.Get(_mockActivityRepository).Setup(s => s.FindDepartmentActivityByMonthRange(testDepartmentId, range)) + .ReturnsAsync(activities); + Mock.Get(_mockGoalRepository).Setup(s => s.FindDepartmentGoalsByMonthRange(testDepartmentId, range)) + .ReturnsAsync(goals.Where(x => x.EmployeeId == null).ToList()); + Mock.Get(_mockGoalRepository).Setup(s => s.FindPersonnelGoalsByMonthRange(testDepartmentId, It.IsAny>(),range)) + .ReturnsAsync(goals.Where(x => x.EmployeeId != null).ToList()); + Mock.Get(_mockBusinessRuleService).Setup(x => x.GetRules(PayoutType.Department)).Returns(departmentRules); + Mock.Get(_mockBusinessRuleService).Setup(x => x.GetRules(PayoutType.Personal)).Returns(personalRules); + + var result = await _dashboardService.GetDepartmentProgressForMonthRangeAsync(testDepartmentId, range); + Assert.NotNull(result); + Assert.AreEqual(expectedDepartmentTotal, result.Progress.ProductActivity[Product.NewChecking]); + Assert.AreEqual(1, result.Progress.ProductActivity[Product.CreditCard]); + Assert.AreEqual(1, result.Progress.ProductActivity[Product.LIC]); + Assert.AreEqual(1, result.Progress.ProductActivity[Product.FirstMortgageReferral]); + Assert.AreEqual(expectedPersonalTotal, result.EmployeeProgress["11771"].ProductActivity[Product.NewChecking]); + Assert.AreEqual(1, result.EmployeeProgress["11771"].ProductActivity[Product.CreditCard]); + Assert.AreEqual(1, result.EmployeeProgress["11771"].ProductActivity[Product.LIC]); + + Mock.Get(_mockDepartmentService).Verify(s => s.FindDepartmentAssignmentsForRange(testDepartmentId, range), + Times.Once); + Mock.Get(_mockActivityRepository).Verify(s => s.FindDepartmentActivityByMonthRange(testDepartmentId, range), + Times.Once); + Mock.Get(_mockGoalRepository).Verify(s => s.FindDepartmentGoalsByMonthRange(testDepartmentId, range), + Times.Once); + Mock.Get(_mockGoalRepository).Verify(s => s.FindPersonnelGoalsByMonthRange(testDepartmentId, It.IsAny>(),range), + Times.Once); + } + + + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DataCleaningServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DataCleaningServiceTest.cs new file mode 100644 index 0000000..bf633c4 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DataCleaningServiceTest.cs @@ -0,0 +1,28 @@ +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using NUnit.Framework; + +namespace Application.UnitTests.Services; + +public class DataCleaningServiceTest +{ + [Test] + public void BasicParsePhoneTest() + { + var result = DataCleaningService.CleanedPhoneNumber("+1( 123\\/-4567890)"); + Assert.AreEqual("1234567890", result); + } + + [Test] + public void BasicParsePhoneTestTwo() + { + var result = DataCleaningService.CleanedPhoneNumber("+1( ^~`&$$*^^<>??:;|[]!##%*-123\\/-4567890)"); + Assert.AreEqual("1234567890", result); + } + + [Test] + public void BasicParsePhoneTestThree() + { + var result = DataCleaningService.CleanedPhoneNumber("262-6064142"); + Assert.AreEqual("2626064142", result); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DataParsingServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DataParsingServiceTest.cs new file mode 100644 index 0000000..abf3d2f --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DataParsingServiceTest.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Linq; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using NUnit.Framework; + +namespace Application.UnitTests.Services; + +public class DataParsingServiceTest +{ + [Test] + public void BasicParsePhoneTest() + { + var result = DataParsingService.MapActivityMetaData(new List + { + "262-6064142" + }); + Assert.AreEqual("2626064142", result.FirstOrDefault()?.MetaData); + Assert.AreEqual(MetaDataType.Phone, result.FirstOrDefault()?.MetaDataType); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DayForceSyncServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DayForceSyncServiceTest.cs new file mode 100644 index 0000000..4f0441b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DayForceSyncServiceTest.cs @@ -0,0 +1,783 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.API.Internal.Operations.IncentiveApi.Services.Payout; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApi.Services.Dayforce; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; +using LCU.Library.Internal.IncentiveApiModels.Results; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace Application.UnitTests.Services +{ + public class DayForceSyncServiceTest + { + private IDayForceClientService _dayForceClientService; + private IDayForceSyncService _dayForceSyncService; + private INotificationApiService _notificationService; + private IPayoutService _payoutService; + private ISettingsService _settingsService; + private IUnitOfWork _unitOfWork; + private IUserService _userService; + private DayForceConfiguration _dayForceConfig; + + [SetUp] + public void Setup() + { + _dayForceClientService = Mock.Of(); + _notificationService = Mock.Of(); + _payoutService = Mock.Of(); + _settingsService = Mock.Of(); + _unitOfWork = Mock.Of(); + _userService = Mock.Of(); + _dayForceConfig = Mock.Of(); + _dayForceConfig.DayForceActiveUserReport = "INCENTIVE_MANAGER_ACTIVE"; + _dayForceConfig.DayForceTerminatedUserReport = "INCENTIVE_MANAGER_TERMINATED"; + _dayForceConfig.DayForceManualReportRecipients = new List() { "user22@testing.com" }; + + _dayForceSyncService = new DayForceSyncService( + _unitOfWork, + _userService, + _notificationService, + _payoutService, + _dayForceClientService, + _settingsService, + _dayForceConfig + ); + } + + [Test] + public void Sync_Active_Throws() + { + Mock.Get(_dayForceClientService) + .Setup(s => s.GetReportData(_dayForceConfig.DayForceActiveUserReport)) + .ReturnsAsync(new Report() + { }); + + Assert.Throws(() => { _dayForceSyncService.SyncActiveDayForceUsers(new List()).GetAwaiter().GetResult(); }); + + Mock.Get(_dayForceClientService) + .Verify(s => s.GetReportData(_dayForceConfig.DayForceActiveUserReport), Times.Once); + Mock.Get(_dayForceClientService) + .Verify(s => s.GetReportData(_dayForceConfig.DayForceTerminatedUserReport), Times.Never); + } + + [Test] + public void Sync_Term_Throws() + { + Mock.Get(_dayForceClientService) + .Setup(s => s.GetReportData(_dayForceConfig.DayForceTerminatedUserReport)) + .ReturnsAsync(new Report() + { }); + + Assert.Throws(() => { _dayForceSyncService.SyncTerminatedDayForceUsers(new List()).GetAwaiter().GetResult(); }); + + Mock.Get(_dayForceClientService) + .Verify(s => s.GetReportData(_dayForceConfig.DayForceActiveUserReport), + Times.Never); + Mock.Get(_dayForceClientService) + .Verify(s => s.GetReportData(_dayForceConfig.DayForceTerminatedUserReport), + Times.Once); + } + + [Test] + public async Task Sync_Active_Empty() + { + Mock.Get(_dayForceClientService) + .Setup(s => s.GetReportData(_dayForceConfig.DayForceActiveUserReport)) + .ReturnsAsync(new Report() + { + XRefCode = _dayForceConfig.DayForceActiveUserReport, + Rows = new List>() + }); + + Mock.Get(_userService) + .Setup(s => s.SyncDayForceEmployee(It.IsAny())) + .ReturnsAsync( + new AppUser() + { + AdEmail = "test2@test.com" + } + ); + + Mock.Get(_notificationService) + .Setup(s => s.SendMissingRoleNotification(It.IsAny>(), It.IsAny>())).Verifiable(); + + Mock.Get(_unitOfWork).Setup(s => s.CompleteAsync()).Verifiable(); + + await _dayForceSyncService.SyncActiveDayForceUsers(new List()); + + Mock.Get(_dayForceClientService) + .Verify(s => s.GetReportData(_dayForceConfig.DayForceActiveUserReport), + Times.Once); + Mock.Get(_unitOfWork).Verify(s => s.CompleteAsync(), Times.Never); + + Mock.Get(_userService).Verify(s => s.SyncDayForceEmployee(It.IsAny()), Times.Never); + Mock.Get(_notificationService) + .Verify(s => s.SendMissingRoleNotification(It.IsAny>(), It.IsAny>()), + Times.Never); + } + + [Test] + public async Task Sync_Active_Basic() + { + var row1 = new Dictionary(); + var row2 = new Dictionary(); + row1["EmploymentStatus_ShortName"] = "Active"; + row1["Employee_DisplayName"] = "Test One"; + row1["EmployeeEmploymentStatus_EmployeeNumber"] = "C0001"; + row1["IdLibrary_LoginName"] = "testone@landmarkcu.com"; + row1["OrgUnit_ShortName"] = "IT"; + row1["Job_ShortName"] = "ELEPHANT"; + row1["EmployeeManager_ManagerEmployeeNumber"] = "11771"; + row1["Employee_HireDate"] = "2021-07-01T00:00:00.000"; + row1["OrgUnit_LedgerCode"] = "101"; + row2["EmploymentStatus_ShortName"] = "Active"; + row2["Employee_DisplayName"] = "Test Two"; + row2["EmployeeEmploymentStatus_EmployeeNumber"] = "C0002"; + row2["IdLibrary_LoginName"] = "testtwo@landmarkcu.com"; + row2["OrgUnit_ShortName"] = "IT"; + row2["Job_ShortName"] = "Developer"; + row2["EmployeeManager_ManagerEmployeeNumber"] = "11771"; + row2["Employee_HireDate"] = "2021-07-01T00:00:00.000"; + row2["OrgUnit_LedgerCode"] = "103"; + + Mock.Get(_dayForceClientService) + .Setup(s => s.GetReportData(_dayForceConfig.DayForceActiveUserReport)) + .ReturnsAsync(new Report() + { + XRefCode = _dayForceConfig.DayForceActiveUserReport, + Rows = new List>() + { row1, row2 } + }); + + Mock.Get(_userService) + .Setup(s => s.SyncDayForceEmployee(It.IsAny())) + .ReturnsAsync( + new AppUser() + { + AdEmail = "test2@test.com" + } + ); + + Mock.Get(_notificationService) + .Setup(s => s.SendMissingRoleNotification(It.IsAny>(), It.IsAny>())) + .Verifiable(); + + Mock.Get(_unitOfWork).Setup(s => s.CompleteAsync()).Verifiable(); + + await _dayForceSyncService.SyncActiveDayForceUsers(new List()); + + Mock.Get(_dayForceClientService) + .Verify(s => s.GetReportData(_dayForceConfig.DayForceActiveUserReport), + Times.Once); + Mock.Get(_unitOfWork).Verify(s => s.CompleteAsync(), Times.Never); + + Mock.Get(_userService).Verify(s => s.SyncDayForceEmployee(It.IsAny()), Times.Exactly(2)); + Mock.Get(_notificationService) + .Verify(s => s.SendMissingRoleNotification(It.IsAny>(), It.IsAny>()), + Times.Never); + } + + [Test] + public async Task Sync_Term_Empty() + { + Mock.Get(_dayForceClientService) + .Setup(s => s.GetReportData(_dayForceConfig.DayForceTerminatedUserReport)) + .ReturnsAsync(new Report() + { + XRefCode = _dayForceConfig.DayForceTerminatedUserReport, + Rows = new List>() + }); + + Mock.Get(_userService) + .Setup(s => s.TerminateDayForceEmployee(It.IsAny())) + .ReturnsAsync( + new AppUser() + { + AdEmail = "test2@test.com" + } + ); + + Mock.Get(_userService) + .Setup(s => s.FindManager(It.IsAny())) + .ReturnsAsync( + new AppUser() + { + AdEmail = "manager@test.com" + } + ); + + Mock.Get(_payoutService) + .Setup(s => s.CreateTerminatedEmployeePayouts(It.IsAny(), It.IsAny())) + .ReturnsAsync( + new TerminatedEmployeeReceipt( + new AppUser() + { + AdEmail = "fired@test.com" + }, new PayoutEntry(), new PayoutEntry(), new AppUser() + { + AdEmail = "bossy@test.com" + }) + ); + + Mock.Get(_notificationService) + .Setup(s => s.SendTerminatedEmployeeNotification(It.IsAny>(), It.IsAny>())) + .Verifiable(); + + await _dayForceSyncService.SyncTerminatedDayForceUsers(new List()); + + Mock.Get(_dayForceClientService) + .Verify(s => s.GetReportData(_dayForceConfig.DayForceTerminatedUserReport), + Times.Once); + + Mock.Get(_userService).Verify(s => s.TerminateDayForceEmployee(It.IsAny()), Times.Never); + Mock.Get(_userService).Verify(s => s.FindManager(It.IsAny()), Times.Never); + Mock.Get(_payoutService).Verify(s => s.CreateTerminatedEmployeePayouts(It.IsAny(), It.IsAny()), Times.Never); + Mock.Get(_notificationService).Verify(s => s.SendTerminatedEmployeeNotification(It.IsAny>(), It.IsAny>()), Times.Never); + } + + [Test] + public async Task Sync_Term_Basic() + { + var row1 = new Dictionary(); + var row2 = new Dictionary(); + row1["Employee_DisplayName"] = "Test One"; + row1["EmployeeEmploymentStatus_EmployeeNumber"] = "C0001"; + row1["IdLibrary_LoginName"] = "testone@landmarkcu.com"; + row1["OrgUnit_ShortName"] = "IT"; + row1["Job_ShortName"] = "ELEPHANT"; + row1["EmployeeManager_ManagerEmployeeNumber"] = "11771"; + row1["Employee_TerminationDate"] = "2021-07-01T00:00:00.000"; + row2["Employee_DisplayName"] = "Test Two"; + row2["EmployeeEmploymentStatus_EmployeeNumber"] = "C0002"; + row2["IdLibrary_LoginName"] = "testtwo@landmarkcu.com"; + row2["OrgUnit_ShortName"] = "IT"; + row2["Job_ShortName"] = "Developer"; + row2["EmployeeManager_ManagerEmployeeNumber"] = "11771"; + row2["Employee_TerminationDate"] = "2021-07-01T00:00:00.000"; + + Mock.Get(_dayForceClientService) + .Setup(s => s.GetReportData(_dayForceConfig.DayForceTerminatedUserReport)) + .ReturnsAsync(new Report() + { + XRefCode = _dayForceConfig.DayForceTerminatedUserReport, + Rows = new List>() { row1, row2 } + }); + + Mock.Get(_userService) + .Setup(s => s.TerminateDayForceEmployee(It.IsAny())) + .ReturnsAsync( + new AppUser() + { + AdEmail = "test2@test.com" + } + ); + + Mock.Get(_userService) + .Setup(s => s.FindManager(It.IsAny())) + .ReturnsAsync( + new AppUser() + { + AdEmail = "manager@test.com" + } + ); + + Mock.Get(_payoutService) + .Setup(s => s.CreateTerminatedEmployeePayouts(It.IsAny(), It.IsAny())) + .ReturnsAsync( + new TerminatedEmployeeReceipt( + new AppUser() + { + AdEmail = "fired@test.com" + }, new PayoutEntry(), new PayoutEntry(), new AppUser() + { + AdEmail = "bossy@test.com" + }) + ); + + Mock.Get(_notificationService) + .Setup(s => s.SendTerminatedEmployeeNotification(It.IsAny>(), It.IsAny>())) + .Verifiable(); + + await _dayForceSyncService.SyncTerminatedDayForceUsers(new List()); + + Mock.Get(_dayForceClientService) + .Verify(s => s.GetReportData(_dayForceConfig.DayForceTerminatedUserReport), + Times.Once); + + Mock.Get(_userService).Verify(s => s.TerminateDayForceEmployee(It.IsAny()), Times.Exactly(2)); + Mock.Get(_userService).Verify(s => s.FindManager(It.IsAny()), Times.Exactly(2)); + Mock.Get(_payoutService).Verify(s => s.CreateTerminatedEmployeePayouts(It.IsAny(), It.IsAny()), Times.Exactly(2)); + Mock.Get(_notificationService).Verify(s => s.SendTerminatedEmployeeNotification(It.IsAny>(), It.IsAny>()), Times.Once); + } + + [Test] + public async Task DayForceSync_ManualReportRecipients_Ok() + { + //setup for Active Users Email + var row3 = new Dictionary(); + var row4 = new Dictionary(); + + row3["EmploymentStatus_ShortName"] = "Active"; + row3["Employee_DisplayName"] = "Test One"; + row3["EmployeeEmploymentStatus_EmployeeNumber"] = "C0001"; + row3["IdLibrary_LoginName"] = "testone@landmarkcu.com"; + row3["OrgUnit_ShortName"] = "IT"; + row3["Job_ShortName"] = "ELEPHANT"; + row3["EmployeeManager_ManagerEmployeeNumber"] = "11771"; + row3["Employee_HireDate"] = "2021-07-01T00:00:00.000"; + row3["OrgUnit_LedgerCode"] = "101"; + + row4["EmploymentStatus_ShortName"] = "Active"; + row4["Employee_DisplayName"] = "Test Two"; + row4["EmployeeEmploymentStatus_EmployeeNumber"] = "C0002"; + row4["IdLibrary_LoginName"] = "testtwo@landmarkcu.com"; + row4["OrgUnit_ShortName"] = "IT"; + row4["Job_ShortName"] = "Developer"; + row4["EmployeeManager_ManagerEmployeeNumber"] = "11771"; + row4["Employee_HireDate"] = "2021-07-01T00:00:00.000"; + row4["OrgUnit_LedgerCode"] = "103"; + + Mock.Get(_dayForceClientService).Setup(s => s.GetReportData(_dayForceConfig.DayForceActiveUserReport)) + .ReturnsAsync(new Report() + { + XRefCode = _dayForceConfig.DayForceActiveUserReport, + Rows = new List>() { row3, row4 } + }); + + Mock.Get(_userService).Setup(s => s.SyncDayForceEmployee(It.IsAny())) + .ReturnsAsync( + new AppUser() + { + AdEmail = "test2@test.com", + Departments = new List() + { + new UserDepartment() + { + Role = Role.MISSING_ROLE + } + } + } + ); + + Mock.Get(_notificationService).Setup(s => s.SendMissingRoleNotification(It.IsAny>(), It.IsAny>())).Verifiable(); + + //Setup for Terminated Email + Mock.Get(_unitOfWork).Setup(s => s.CompleteAsync()).Verifiable(); + + var row1 = new Dictionary(); + var row2 = new Dictionary(); + + row1["Employee_DisplayName"] = "Test One"; + row1["EmployeeEmploymentStatus_EmployeeNumber"] = "C0001"; + row1["IdLibrary_LoginName"] = "testone@landmarkcu.com"; + row1["OrgUnit_ShortName"] = "IT"; + row1["Job_ShortName"] = "ELEPHANT"; + row1["EmployeeManager_ManagerEmployeeNumber"] = "11771"; + row1["Employee_TerminationDate"] = "2021-07-01T00:00:00.000"; + + row2["Employee_DisplayName"] = "Test Two"; + row2["EmployeeEmploymentStatus_EmployeeNumber"] = "C0002"; + row2["IdLibrary_LoginName"] = "testtwo@landmarkcu.com"; + row2["OrgUnit_ShortName"] = "IT"; + row2["Job_ShortName"] = "Developer"; + row2["EmployeeManager_ManagerEmployeeNumber"] = "11771"; + row2["Employee_TerminationDate"] = "2021-07-01T00:00:00.000"; + + Mock.Get(_dayForceClientService).Setup(s => s.GetReportData(_dayForceConfig.DayForceTerminatedUserReport)) + .ReturnsAsync(new Report() + { + XRefCode = _dayForceConfig.DayForceTerminatedUserReport, + Rows = new List>() { row1, row2 } + }); + + Mock.Get(_userService).Setup(s => s.TerminateDayForceEmployee(It.IsAny())) + .ReturnsAsync( + new AppUser() + { + AdEmail = "test2@test.com" + } + ); + + Mock.Get(_userService).Setup(s => s.FindManager(It.IsAny())) + .ReturnsAsync( + new AppUser() + { + AdEmail = "manager@test.com" + } + ); + + Mock.Get(_payoutService).Setup(s => s.CreateTerminatedEmployeePayouts(It.IsAny(), It.IsAny())) + .ReturnsAsync( + new TerminatedEmployeeReceipt( + new AppUser() + { + AdEmail = "user22@test.com" + }, new PayoutEntry(), new PayoutEntry(), new AppUser() + { + AdEmail = "user44@test.com" + }) + ); + + Mock.Get(_notificationService).Setup(s => s.SendTerminatedEmployeeNotification(It.IsAny>(), It.IsAny>())).Verifiable(); + + var request = new DayforceProcessRequest(); + await _dayForceSyncService.SyncDayForceUsers(request); + + //Active Role Checks + Mock.Get(_dayForceClientService).Verify(s => s.GetReportData(_dayForceConfig.DayForceActiveUserReport), Times.AtLeastOnce); + Mock.Get(_unitOfWork).Verify(s => s.CompleteAsync(), Times.Never); + Mock.Get(_userService).Verify(s => s.SyncDayForceEmployee(It.IsAny()), Times.Exactly(2)); + Mock.Get(_notificationService).Verify(s => s.SendMissingRoleNotification(It.IsAny>(), It.IsAny>()), Times.Once); + + Mock.Equals(request.MissingRoleNotification, _dayForceConfig.DayForceManualReportRecipients); + Assert.AreNotSame(request.MissingRoleNotification, "testing123@test.com"); + + //Terminated User Checks + Mock.Get(_userService).Verify(s => s.TerminateDayForceEmployee(It.IsAny()), Times.Exactly(2)); + Mock.Get(_userService).Verify(s => s.FindManager(It.IsAny()), Times.Exactly(2)); + Mock.Get(_payoutService).Verify(s => s.CreateTerminatedEmployeePayouts(It.IsAny(), It.IsAny()), Times.Exactly(2)); + Mock.Get(_dayForceClientService).Verify(s => s.GetReportData(_dayForceConfig.DayForceTerminatedUserReport), Times.Once); + Mock.Get(_notificationService).Verify(s => s.SendTerminatedEmployeeNotification(It.IsAny>(), It.IsAny>()), Times.Once); + } + + [Test] + public async Task DayForceSync_RequestReportRecipients_Ok() + { + //setup for Active Users Email + var row3 = new Dictionary(); + var row4 = new Dictionary(); + + row3["EmploymentStatus_ShortName"] = "Active"; + row3["Employee_DisplayName"] = "Test One"; + row3["EmployeeEmploymentStatus_EmployeeNumber"] = "C0001"; + row3["IdLibrary_LoginName"] = "testone@landmarkcu.com"; + row3["OrgUnit_ShortName"] = "IT"; + row3["Job_ShortName"] = "ELEPHANT"; + row3["EmployeeManager_ManagerEmployeeNumber"] = "11771"; + row3["Employee_HireDate"] = "2021-07-01T00:00:00.000"; + row3["OrgUnit_LedgerCode"] = "101"; + + row4["EmploymentStatus_ShortName"] = "Active"; + row4["Employee_DisplayName"] = "Test Two"; + row4["EmployeeEmploymentStatus_EmployeeNumber"] = "C0002"; + row4["IdLibrary_LoginName"] = "testtwo@landmarkcu.com"; + row4["OrgUnit_ShortName"] = "IT"; + row4["Job_ShortName"] = "Developer"; + row4["EmployeeManager_ManagerEmployeeNumber"] = "11771"; + row4["Employee_HireDate"] = "2021-07-01T00:00:00.000"; + row4["OrgUnit_LedgerCode"] = "103"; + + Mock.Get(_dayForceClientService).Setup(s => s.GetReportData(_dayForceConfig.DayForceActiveUserReport)) + .ReturnsAsync(new Report() + { + XRefCode = _dayForceConfig.DayForceActiveUserReport, + Rows = new List>() { row3, row4 } + }); + + Mock.Get(_userService).Setup(s => s.SyncDayForceEmployee(It.IsAny())) + .ReturnsAsync( + new AppUser() + { + AdEmail = "test2@test.com", + Departments = new List() + { + new UserDepartment() + { + Role = Role.MISSING_ROLE + } + } + } + ); + + Mock.Get(_notificationService).Setup(s => s.SendMissingRoleNotification(It.IsAny>(), It.IsAny>())).Verifiable(); + + //Setup for Terminated Email + Mock.Get(_unitOfWork).Setup(s => s.CompleteAsync()).Verifiable(); + + var row1 = new Dictionary(); + var row2 = new Dictionary(); + + row1["Employee_DisplayName"] = "Test One"; + row1["EmployeeEmploymentStatus_EmployeeNumber"] = "C0001"; + row1["IdLibrary_LoginName"] = "testone@landmarkcu.com"; + row1["OrgUnit_ShortName"] = "IT"; + row1["Job_ShortName"] = "ELEPHANT"; + row1["EmployeeManager_ManagerEmployeeNumber"] = "11771"; + row1["Employee_TerminationDate"] = "2021-07-01T00:00:00.000"; + + row2["Employee_DisplayName"] = "Test Two"; + row2["EmployeeEmploymentStatus_EmployeeNumber"] = "C0002"; + row2["IdLibrary_LoginName"] = "testtwo@landmarkcu.com"; + row2["OrgUnit_ShortName"] = "IT"; + row2["Job_ShortName"] = "Developer"; + row2["EmployeeManager_ManagerEmployeeNumber"] = "11771"; + row2["Employee_TerminationDate"] = "2021-07-01T00:00:00.000"; + + Mock.Get(_dayForceClientService).Setup(s => s.GetReportData(_dayForceConfig.DayForceTerminatedUserReport)) + .ReturnsAsync(new Report() + { + XRefCode = _dayForceConfig.DayForceTerminatedUserReport, + Rows = new List>() { row1, row2 } + }); + + Mock.Get(_userService).Setup(s => s.TerminateDayForceEmployee(It.IsAny())) + .ReturnsAsync( + new AppUser() + { + AdEmail = "test2@test.com" + } + ); + + Mock.Get(_userService).Setup(s => s.FindManager(It.IsAny())) + .ReturnsAsync( + new AppUser() + { + AdEmail = "manager@test.com" + } + ); + + Mock.Get(_payoutService).Setup(s => s.CreateTerminatedEmployeePayouts(It.IsAny(), It.IsAny())) + .ReturnsAsync( + new TerminatedEmployeeReceipt( + new AppUser() + { + AdEmail = "user22@test.com" + }, new PayoutEntry(), new PayoutEntry(), new AppUser() + { + AdEmail = "user44@test.com" + }) + ); + + Mock.Get(_notificationService).Setup(s => s.SendTerminatedEmployeeNotification(It.IsAny>(), It.IsAny>())).Verifiable(); + + var request = new DayforceProcessRequest(); + request.MissingRoleNotification = new List() { "testing123@test.com" }; + await _dayForceSyncService.SyncDayForceUsers(request); + + //Active Role Checks + Mock.Get(_dayForceClientService).Verify(s => s.GetReportData(_dayForceConfig.DayForceActiveUserReport), Times.AtLeastOnce); + Mock.Get(_unitOfWork).Verify(s => s.CompleteAsync(), Times.Never); + Mock.Get(_userService).Verify(s => s.SyncDayForceEmployee(It.IsAny()), Times.Exactly(2)); + Mock.Get(_notificationService).Verify(s => s.SendMissingRoleNotification(It.IsAny>(), It.IsAny>()), Times.Once); + + Mock.Equals(request.MissingRoleNotification, "testing123@test.com"); + Assert.AreNotEqual(request.MissingRoleNotification, _dayForceConfig.DayForceManualReportRecipients); + + //Terminated User Checks + Mock.Get(_userService).Verify(s => s.TerminateDayForceEmployee(It.IsAny()), Times.Exactly(2)); + Mock.Get(_userService).Verify(s => s.FindManager(It.IsAny()), Times.Exactly(2)); + Mock.Get(_payoutService).Verify(s => s.CreateTerminatedEmployeePayouts(It.IsAny(), It.IsAny()), Times.Exactly(2)); + Mock.Get(_dayForceClientService).Verify(s => s.GetReportData(_dayForceConfig.DayForceTerminatedUserReport), Times.Once); + Mock.Get(_notificationService).Verify(s => s.SendTerminatedEmployeeNotification(It.IsAny>(), It.IsAny>()), Times.Once); + } + + [Test] + public async Task GetMatchingHcmUsersReturnsEmptyListOnEmptySearch() + { + var result = await _dayForceSyncService.GetMatchingHcmUsers(); + Assert.IsEmpty(result); + } + + [Test] + public async Task EmployeeDetailsUpdateInitializesWithNullAppUserAndSuccessSetToFalse() + { + var result = new EmployeeDetailsUpdateResult(); + Assert.IsNull(result.AppUser); + Assert.IsFalse(result.UpdatedSuccessfully); + } + + [Test] + public async Task EmployeeDetailsUpdateFailsWhenNewAndOldDetailsMatch() + { + var updateRequest = new EmployeeDetailsUpdate + { + OldEmail = "TESTTESTERSON@LANDMARKCU.COM", + OldEmployeeId = "12345", + OldFullName = "TESTERSON, TEST", + NewEmail = "TESTTESTERSON@LANDMARKCU.COM", + NewEmployeeId = "12345", + NewFullName = "TESTERSON, TEST" + }; + + Mock.Get(_userService) + .Setup(s => s.GetUserByAdEmailAsync("TESTTESTERSON@LANDMARKCU.COM")) + .ReturnsAsync(new AppUser + { + AdEmail = "TESTTESTERSON@LANDMARKCU.COM", + Id = Guid.NewGuid(), + EmployeeId = "12345", + FullName = "TESTERSON, TEST" + } + ); + + var result = await _dayForceSyncService.UpdateEmployeeDetails(updateRequest); + Assert.IsNotNull(result); + Assert.IsNotNull(result.AppUser); + Assert.IsFalse(result.UpdatedSuccessfully); + } + + [Test] + public async Task EmployeeDetailsUpdateFailsWhenNoAppUserFound() + { + var updateRequest = new EmployeeDetailsUpdate + { + OldEmail = "TESTTESTERSON@LANDMARKCU.COM", + OldEmployeeId = "12345", + OldFullName = "TESTERSON, TEST", + NewEmail = "TESTTESTERSON@LANDMARKCU.COM", + NewEmployeeId = "12345", + NewFullName = "TESTERSON, TEST" + }; + + var result = await _dayForceSyncService.UpdateEmployeeDetails(updateRequest); + Assert.IsNotNull(result); + Assert.IsNull(result.AppUser); + Assert.IsFalse(result.UpdatedSuccessfully); + } + + [Test] + public async Task EmployeeDetailsUpdateFailsWhenNoHcmUserFound() + { + var updateRequest = new EmployeeDetailsUpdate + { + OldEmail = "TESTTESTERSON@LANDMARKCU.COM", + OldEmployeeId = "12345", + OldFullName = "TESTERSON, TEST", + NewEmail = "NEWTESTTESTERSON@LANDMARKCU.COM", + NewEmployeeId = "12345", + NewFullName = "TESTERSON, TEST" + }; + + Mock.Get(_userService) + .Setup(s => s.GetUserByAdEmailAsync("TESTTESTERSON@LANDMARKCU.COM")) + .ReturnsAsync(new AppUser + { + AdEmail = "TESTTESTERSON@LANDMARKCU.COM", + Id = Guid.NewGuid(), + EmployeeId = "12345", + FullName = "TESTERSON, TEST" + } + ); + + var row1 = new Dictionary(); + var row2 = new Dictionary(); + row1["EmploymentStatus_ShortName"] = "Active"; + row1["Employee_DisplayName"] = "Test One"; + row1["EmployeeEmploymentStatus_EmployeeNumber"] = "C0001"; + row1["IdLibrary_LoginName"] = "testone@landmarkcu.com"; + row1["OrgUnit_ShortName"] = "IT"; + row1["Job_ShortName"] = "ELEPHANT"; + row1["EmployeeManager_ManagerEmployeeNumber"] = "11771"; + row1["Employee_HireDate"] = "2021-07-01T00:00:00.000"; + row1["OrgUnit_LedgerCode"] = "101"; + row2["EmploymentStatus_ShortName"] = "Active"; + row2["Employee_DisplayName"] = "Test Two"; + row2["EmployeeEmploymentStatus_EmployeeNumber"] = "C0002"; + row2["IdLibrary_LoginName"] = "testtwo@landmarkcu.com"; + row2["OrgUnit_ShortName"] = "IT"; + row2["Job_ShortName"] = "Developer"; + row2["EmployeeManager_ManagerEmployeeNumber"] = "11771"; + row2["Employee_HireDate"] = "2021-07-01T00:00:00.000"; + row2["OrgUnit_LedgerCode"] = "103"; + Mock.Get(_dayForceClientService) + .Setup(s => s.GetReportData(_dayForceConfig.DayForceActiveUserReport)) + .ReturnsAsync(new Report() + { + XRefCode = _dayForceConfig.DayForceActiveUserReport, + Rows = new List>() + { row1, row2 } + }); + + var result = await _dayForceSyncService.UpdateEmployeeDetails(updateRequest); + Assert.IsNotNull(result); + Assert.IsNotNull(result.AppUser); + Assert.IsFalse(result.UpdatedSuccessfully); + } + + [Test] + public async Task EmployeeDetailsUpdateSucceedsWhenOneHcmUserFound() + { + var updateRequest = new EmployeeDetailsUpdate + { + OldEmail = "TESTTESTERSON@LANDMARKCU.COM", + OldEmployeeId = "12345", + OldFullName = "TESTERSON, TEST", + NewEmail = "NEWTESTTESTERSON@LANDMARKCU.COM", + NewEmployeeId = "12345", + NewFullName = "TESTERSON, TEST" + }; + + Mock.Get(_userService) + .Setup(s => s.GetUserByAdEmailAsync("TESTTESTERSON@LANDMARKCU.COM")) + .ReturnsAsync(new AppUser + { + AdEmail = "TESTTESTERSON@LANDMARKCU.COM", + Id = Guid.NewGuid(), + EmployeeId = "12345", + FullName = "TESTERSON, TEST" + } + ); + + var row1 = new Dictionary(); + var row2 = new Dictionary(); + row1["EmploymentStatus_ShortName"] = "Active"; + row1["Employee_DisplayName"] = updateRequest.NewFullName; + row1["EmployeeEmploymentStatus_EmployeeNumber"] = updateRequest.NewEmployeeId; + row1["IdLibrary_LoginName"] = updateRequest.NewEmail; + row1["OrgUnit_ShortName"] = "IT"; + row1["Job_ShortName"] = "ELEPHANT"; + row1["EmployeeManager_ManagerEmployeeNumber"] = "11111"; + row1["Employee_HireDate"] = "2021-07-01T00:00:00.000"; + row1["OrgUnit_LedgerCode"] = "101"; + row2["EmploymentStatus_ShortName"] = "Active"; + row2["Employee_DisplayName"] = "Test Two"; + row2["EmployeeEmploymentStatus_EmployeeNumber"] = "98765"; + row2["IdLibrary_LoginName"] = "testtwo@landmarkcu.com"; + row2["OrgUnit_ShortName"] = "IT"; + row2["Job_ShortName"] = "Developer"; + row2["EmployeeManager_ManagerEmployeeNumber"] = "11111"; + row2["Employee_HireDate"] = "2021-07-01T00:00:00.000"; + row2["OrgUnit_LedgerCode"] = "103"; + Mock.Get(_dayForceClientService) + .Setup(s => s.GetReportData(_dayForceConfig.DayForceActiveUserReport)) + .ReturnsAsync(new Report() + { + XRefCode = _dayForceConfig.DayForceActiveUserReport, + Rows = new List>() + { row1, row2 } + }); + + Mock.Get(_userService) + .Setup(s => s.UpdateEmployeeDetails(updateRequest)) + .ReturnsAsync(new EmployeeDetailsUpdateResult{ + AppUser = new AppUser + { + AdEmail = updateRequest.NewEmail, + Id = Guid.NewGuid(), + EmployeeId = updateRequest.NewEmployeeId, + FullName = updateRequest.NewFullName + }, + UpdateMessage = "Employee Details Update Succeeded", + UpdatedSuccessfully = true + } + ); + + var result = await _dayForceSyncService.UpdateEmployeeDetails(updateRequest); + Assert.IsNotNull(result); + Assert.IsNotNull(result.AppUser); + Assert.AreEqual(result.AppUser.AdEmail, updateRequest.NewEmail); + Assert.AreEqual(result.AppUser.EmployeeId, updateRequest.NewEmployeeId); + Assert.AreEqual(result.AppUser.FullName, updateRequest.NewFullName); + Assert.IsTrue(result.UpdatedSuccessfully); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DeadActivityServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DeadActivityServiceTest.cs new file mode 100644 index 0000000..10d7543 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DeadActivityServiceTest.cs @@ -0,0 +1,360 @@ +using System; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using Moq; +using NUnit.Framework; +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; + +namespace Application.UnitTests.Services +{ + public class DeadActivityServiceTest + { + private IDeadActivityService _service; + private Mock _mockDeadActivityRepository; + private Mock _mockUnitOfWork; + + [SetUp] + public void Setup() + { + _mockDeadActivityRepository = new Mock(); + _mockUnitOfWork = new Mock(); + _service = new DeadActivityService(_mockDeadActivityRepository.Object, _mockUnitOfWork.Object); + } + + [Test] + public async Task SaveDeadActivity_Ok() + { + var deadActivity = new DeadActivity("12345", SystemType.IncentiveLog, DeadActivityReason.Other, + "Sad Incentive Manager Noises"); + + _mockDeadActivityRepository.Setup(x => x.SaveDeadActivityRange(It.IsAny>())) + .ReturnsAsync(new List() { deadActivity }); + _mockDeadActivityRepository.Setup(x => x.GetDeadActivitiesBySourceIds(It.IsAny>())) + .ReturnsAsync(new List() {}); + + + var result = await _service.SaveDeadActivity(deadActivity); + + Assert.NotNull(result); + Assert.AreEqual(deadActivity, result); + } + + [Test] + public async Task SaveDeadActivity_DidntSave_Ok() + { + var deadActivity = new DeadActivity("12345", SystemType.IncentiveLog, DeadActivityReason.Other, + "Sad Incentive Manager Noises"); + + _mockDeadActivityRepository.Setup(x => x.SaveDeadActivityRange(It.IsAny>())) + .ReturnsAsync(new List() {}); + _mockDeadActivityRepository.Setup(x => x.GetDeadActivitiesBySourceIds(It.IsAny>())) + .ReturnsAsync(new List() {}); + + + var result = await _service.SaveDeadActivity(deadActivity); + + Assert.Null(result); + } + + [Test] + public async Task SaveDeadActivity_FilteredOut_Ok() + { + var deadActivity = new DeadActivity("12345", SystemType.IncentiveLog, DeadActivityReason.Other, + "Sad Incentive Manager Noises"); + + _mockDeadActivityRepository.Setup(x => x.SaveDeadActivityRange(It.IsAny>())) + .ReturnsAsync(new List() {deadActivity}); + _mockDeadActivityRepository.Setup(x => x.GetDeadActivitiesBySourceIds(It.IsAny>())) + .ReturnsAsync(new List() {deadActivity}); + + + var result = await _service.SaveDeadActivity(deadActivity); + + Assert.Null(result); + } + + [Test] + public async Task SaveDeadActivityRange_OK() + { + var deadActivities = new List() + { + new DeadActivity("1", SystemType.Action, DeadActivityReason.Other, "Oh"), + new DeadActivity("2", SystemType.Action, DeadActivityReason.Other, "No"), + new DeadActivity("3", SystemType.Action, DeadActivityReason.Other, "Dead"), + new DeadActivity("4", SystemType.Action, DeadActivityReason.Other, "Activity") + }; + + _mockDeadActivityRepository.Setup(x => x.SaveDeadActivityRange(It.IsAny>())) + .ReturnsAsync(deadActivities); + _mockDeadActivityRepository.Setup(x => x.GetDeadActivitiesBySourceIds(It.IsAny>())) + .ReturnsAsync(new List() {}); + + var result = await _service.SaveDeadActivityRange(deadActivities); + + _mockDeadActivityRepository.Verify(x => x.SaveDeadActivityRange(It.Is>(x => x.Count == 4))); + Assert.NotNull(result); + Assert.AreEqual(4, result.Count); + } + + [Test] + public async Task SaveDeadActivityRange_FilteredOut_OK() + { + var deadActivities = new List() + { + new DeadActivity("1", SystemType.Action, DeadActivityReason.Other, "Oh"), + new DeadActivity("2", SystemType.Action, DeadActivityReason.Other, "No"), + new DeadActivity("3", SystemType.Action, DeadActivityReason.Other, "Dead"), + new DeadActivity("4", SystemType.Action, DeadActivityReason.Other, "Activity") + }; + + _mockDeadActivityRepository.Setup(x => x.SaveDeadActivityRange(It.IsAny>())) + .ReturnsAsync(deadActivities); + _mockDeadActivityRepository.Setup(x => x.GetDeadActivitiesBySourceIds(It.IsAny>())) + .ReturnsAsync(new List() + { + new DeadActivity("1", SystemType.Action, DeadActivityReason.Other, "Oh"), + new DeadActivity("2", SystemType.Action, DeadActivityReason.Other, "No"), + }); + + var result = await _service.SaveDeadActivityRange(deadActivities); + + _mockDeadActivityRepository.Verify(x => x.SaveDeadActivityRange(It.Is>(x => x.Count == 2))); + Assert.NotNull(result); + } + + + [Test] + public async Task SaveDeadActivityRange_FilteredOutAll_OK() + { + var deadActivities = new List() + { + new DeadActivity("1", SystemType.Action, DeadActivityReason.Other, "Oh"), + new DeadActivity("2", SystemType.Action, DeadActivityReason.Other, "No"), + new DeadActivity("3", SystemType.Action, DeadActivityReason.Other, "Dead"), + new DeadActivity("4", SystemType.Action, DeadActivityReason.Other, "Activity") + }; + + _mockDeadActivityRepository.Setup(x => x.SaveDeadActivityRange(It.IsAny>())) + .ReturnsAsync(deadActivities); + _mockDeadActivityRepository.Setup(x => x.GetDeadActivitiesBySourceIds(It.IsAny>())) + .ReturnsAsync(deadActivities); + + var result = await _service.SaveDeadActivityRange(deadActivities); + + Assert.NotNull(result); + Assert.AreEqual(0, result.Count); + } + + [TestCase("Product", Product.Mortgage, true)] + [TestCase("Product", Product.Vehicle, false)] + [TestCase("Product", null, false)] + [TestCase("Type",ActivityType.Other, false)] + [TestCase("Type",null, false)] + [TestCase("SourceId", "1", false)] + [TestCase("SourceSystem", SystemType.Redtail, false)] + [TestCase("EmployeeId", "1234", false)] + [TestCase("EmployeeId", null, true)] + [TestCase("EmployeeEmail", "test@landmarkcu.com", true)] + [TestCase("EmployeeEmail", "TEST@landmarkcu.com", true)] + [TestCase("EmployeeEmail", "Different@landmarkcu.com", false)] + [TestCase("EmployeeEmail", null, false)] + [TestCase("DepartmentId", -1, false)] + [TestCase("DepartmentId", null, true)] + [TestCase("DepartmentId", 100, false)] + public async Task FilterActivitiesByIdentifyingFields_OK(string property, object value, bool expectFilteredOut) + { + var deadActivities = new List() + { + new DeadActivity("1234", SystemType.Dna, DeadActivityReason.Other, "Missing Info") + { + Product = Product.Mortgage, + Type = ActivityType.Sale, + SourceId = "1234", + SourceSystem = SystemType.Dna, + EmployeeId = null, + EmployeeEmail = "test@landmarkcu.com", + DepartmentId = null, + DeadDetail = new DeadActivityDetail() + { + AgreementNumber = "abc" + } + } + }; + + + var existingActivity = + new DeadActivity("1234", SystemType.Dna, DeadActivityReason.Other, "Missing Info") + { + Product = Product.Mortgage, + Type = ActivityType.Sale, + SourceId = "1234", + SourceSystem = SystemType.Dna, + EmployeeId = null, + EmployeeEmail = "test@landmarkcu.com", + DepartmentId = null, + DeadDetail = new DeadActivityDetail() + { + AgreementNumber = "abc" + } + }; + + existingActivity.GetType().GetProperty(property)?.SetValue(existingActivity, value, null); + + _mockDeadActivityRepository.Setup(x => x.SaveDeadActivityRange(It.IsAny>())) + .ReturnsAsync(deadActivities); + _mockDeadActivityRepository.Setup(x => x.GetDeadActivitiesBySourceIds(It.IsAny>())) + .ReturnsAsync(new List(){existingActivity}); + + var results = await _service.SaveDeadActivityRange(deadActivities); + Assert.AreEqual(expectFilteredOut, results.Count == 0); + } + + [Test] + public async Task FilterActivitiesByIdentifyingFields_Details_OK() + { + var deadActivities = new List() + { + new DeadActivity("1234", SystemType.Dna, DeadActivityReason.Other, "Missing Info") + { + Product = Product.Mortgage, + Type = ActivityType.Sale, + SourceId = "1234", + SourceSystem = SystemType.Dna, + EmployeeId = null, + EmployeeEmail = "test@landmarkcu.com", + DepartmentId = null, + DeadDetail = new DeadActivityDetail() + { + //We dont filter on this field + AgreementNumber = "abc" + } + } + }; + + var existingActivity = + new DeadActivity("1234", SystemType.Dna, DeadActivityReason.Other, "Missing Info") + { + Product = Product.Mortgage, + Type = ActivityType.Sale, + SourceId = "1234", + SourceSystem = SystemType.Dna, + EmployeeId = null, + EmployeeEmail = "test@landmarkcu.com", + DepartmentId = null, + DeadDetail = new DeadActivityDetail() + { + //We dont filter on this field + AgreementNumber = null + } + }; + + + _mockDeadActivityRepository.Setup(x => x.SaveDeadActivityRange(It.IsAny>())) + .ReturnsAsync(deadActivities); + _mockDeadActivityRepository.Setup(x => x.GetDeadActivitiesBySourceIds(It.IsAny>())) + .ReturnsAsync(new List(){existingActivity}); + + var results = await _service.SaveDeadActivityRange(deadActivities); + Assert.AreEqual(true, results.Count == 0); + } + + [Test] + public async Task FilterActivitiesByIdentifyingFields_NullDetails_OK() + { + var deadActivities = new List() + { + new DeadActivity("1234", SystemType.Dna, DeadActivityReason.Other, "Missing Info") + { + Product = Product.Mortgage, + Type = ActivityType.Sale, + SourceId = "1234", + SourceSystem = SystemType.Dna, + EmployeeId = null, + EmployeeEmail = "test@landmarkcu.com", + DepartmentId = null, + DeadDetail = new DeadActivityDetail() + { + AccountNumber = "123" + } + } + }; + + var existingActivity = + new DeadActivity("1234", SystemType.Dna, DeadActivityReason.Other, "Missing Info") + { + Product = Product.Mortgage, + Type = ActivityType.Sale, + SourceId = "1234", + SourceSystem = SystemType.Dna, + EmployeeId = null, + EmployeeEmail = "test@landmarkcu.com", + DepartmentId = null, + DeadDetail = null + }; + + + _mockDeadActivityRepository.Setup(x => x.SaveDeadActivityRange(It.IsAny>())) + .ReturnsAsync(deadActivities); + _mockDeadActivityRepository.Setup(x => x.GetDeadActivitiesBySourceIds(It.IsAny>())) + .ReturnsAsync(new List(){existingActivity}); + + var results = await _service.SaveDeadActivityRange(deadActivities); + Assert.AreEqual(false, results.Count == 0); + } + + [Test] + public async Task GetActivity_ReturnsActivity() + { + var activityFromRepo = new DeadActivity(new Activity + { + Id = 100, + DepartmentId = 32, + EmployeeId = "1", + Type = ActivityType.Sale, + Product = Product.CheckCard, + Accepted = DateTime.Today, + Detail = new ActivityDetail(), + ActivityPayouts = new List() + }, DeadActivityReason.Other, "Test"); + //pretend there is more than 1 in repository + activityFromRepo.Id = 3; + _mockDeadActivityRepository.Setup(mock => mock.GetDeadActivityById(3)) + .ReturnsAsync(activityFromRepo); + + var result = await _service.GetDeadActivity(3); + + Assert.NotNull(result); + Assert.AreEqual(3, result.Id); + } + + [Test] + public async Task GetActivity_ThrowsWhenNull() + { + _mockDeadActivityRepository.Setup(mock => mock.GetDeadActivityById(3)) + .ReturnsAsync((DeadActivity) null); + + Assert.ThrowsAsync(async () => await _service.GetDeadActivity(3)); + } + + [Test] + public async Task Updates_DeadActivity() + { + var activity = new DeadActivity("1234", SystemType.Action, DeadActivityReason.Other, "test"); + activity.Id = 4; + activity.LivingActivityId = null; + + _mockDeadActivityRepository.Setup(mock => mock.GetDeadActivityById(4)) + .ReturnsAsync(activity); + _mockUnitOfWork.Setup(x => x.CompleteAsync()).Verifiable(); + + Assert.IsNull(activity.LivingActivityId); + var activity2 = await _service.ProcessDeadActivity(4, 1); + Assert.AreEqual(activity.Id,activity2.Id); + Assert.AreEqual(1, activity2.LivingActivityId); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DepartmentServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DepartmentServiceTest.cs new file mode 100644 index 0000000..ca71bfb --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DepartmentServiceTest.cs @@ -0,0 +1,522 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Microsoft.EntityFrameworkCore; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; + +namespace Application.UnitTests.Services +{ + public class DepartmentServiceTest + { + private DepartmentService _departmentService; + private IDepartmentRepository _departmentRepository; + private ICurrentUserService _currentUserService; + private IUserAssignmentService _userAssignmentService; + private ApplicationDbContext _context; + + [SetUp] + public void Setup() + { + var mockDateTime = new DateTimeService(); + _currentUserService = Mock.Of(); + _userAssignmentService = Mock.Of(); + + Mock.Get(_currentUserService).Setup(s => s.GetCurrentUser()).Returns(new ApplicationUser() + { + Email = "JohnTest@test.com" + }); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .EnableSensitiveDataLogging(true) + .Options; + _context = new ApplicationDbContext(options, _currentUserService, mockDateTime); + _departmentRepository = new DepartmentRepository(_context); + + _departmentService = + new DepartmentService(_departmentRepository, _userAssignmentService, new UnitOfWork(_context)); + } + + [Test] + public async Task Basic_Flow() + { + //Retrieve Empty + var departments = await _departmentService.GetAllDepartmentsAsync(); + Assert.IsNotNull(departments); + Assert.IsEmpty(departments); + + //Add New Department + var department = await _departmentService.SaveDepartmentAsync(new Department() + { + BusinessLineCode = "1", + DayforceDepartmentName = "TestName", + DayForceGlCode = "1", + DepartmentName = "TestName", + DnaOrgNbr = "1" + }); + Assert.NotNull(department); + Assert.True(department.DepartmentName == "TestName"); + + //New Department Can Be Retrieved + departments = await _departmentService.GetAllDepartmentsAsync(); + Assert.IsNotNull(departments); + Assert.True(departments.Count == 1); + Assert.True(departments.SingleOrDefault()?.DepartmentName == "TestName"); + + var id = departments.SingleOrDefault()?.Id; + if (id == null) + { + Assert.Fail("Department ID was null, a save failed"); + return; + } + + //Department can be updated + department = + await _departmentService.UpdateDepartmentAsync( + new Department() + { + DepartmentName = "NewName", State = DepartmentState.Active, BusinessLineCode = "BUS", + RegionCode = "CENTRAL" + }, (int)id); + Assert.NotNull(department); + Assert.True(department.DepartmentName == "NewName"); + Assert.True(department.BusinessLineCode == "BUS"); + Assert.True(department.RegionCode == "CENTRAL"); + Assert.True(department.State == DepartmentState.Active); + + //Updated department can be retrieved individually + department = await _departmentService.GetDepartmentByIdAsync((int)id); + Assert.True(department.DepartmentName == "NewName"); + + //Updated department can be retrieved by all + departments = await _departmentService.GetAllDepartmentsAsync(); + Assert.IsNotNull(departments); + Assert.True(departments.Count == 1); + } + + [Test] + public async Task Throws_Duplicate_Exception() + { + //Add New Department + var testDepartment = await _departmentService.SaveDepartmentAsync(new Department() + { + BusinessLineCode = "1", + DayforceDepartmentName = "TestName", + DayForceGlCode = "1", + DepartmentName = "TestName", + DnaOrgNbr = "1" + }); + Assert.NotNull(testDepartment); + Assert.True(testDepartment.DepartmentName == "TestName"); + + //Add New Department + var testDepartment2 = await _departmentService.SaveDepartmentAsync(new Department() + { + BusinessLineCode = "1", + DayforceDepartmentName = "TestName2", + DayForceGlCode = "1", + DepartmentName = "TestName2", + DnaOrgNbr = "1" + }); + Assert.NotNull(testDepartment2); + Assert.True(testDepartment2.DepartmentName == "TestName2"); + + //Add New Department With Same Name + Assert.ThrowsAsync(async () => + { + await _departmentService.SaveDepartmentAsync(new Department() { BusinessLineCode = "1", + DayforceDepartmentName = "TestName", + DayForceGlCode = "1", + DepartmentName = "TestName", + DnaOrgNbr = "1" }); + }); + + //Add New Department With Same Name Ignore Case + Assert.ThrowsAsync(async () => + { + await _departmentService.SaveDepartmentAsync(new Department() { BusinessLineCode = "1", + DayforceDepartmentName = "TestName", + DayForceGlCode = "1", + DepartmentName = "testname", + DnaOrgNbr = "1" }); + }); + + + //Update Department To Already Existing Name + Assert.ThrowsAsync(async () => + { + await _departmentService.UpdateDepartmentAsync(new Department() { DepartmentName = "TestName" }, + testDepartment2.Id ?? 1); + }); + + //Allow Update to Update itself (Same Name), should not throw + try + { + var updatedDepartment = await _departmentService.UpdateDepartmentAsync( + new Department() { DepartmentName = "TestName2", BusinessLineCode = "2" }, testDepartment2.Id ?? 1); + Assert.AreEqual("TestName2", updatedDepartment.DepartmentName); + } + catch (Exception) + { + Assert.Fail(); + } + } + + [Test] + public async Task GetDepartmentDetailsAsync_Basic() + { + var department = new Department() + { + DepartmentName = "Dept1", + Id = 55, + State = DepartmentState.Active + }; + + var user = new AppUser + { + HomeDepartment = "Home", + Role = Role.Admin, + AdEmail = "AbrahamTestAssociate1@test.com", + EmployeeId = "11771", + FullName = "Abraham TestAssociate1", + BaseRole = "UNKNOWN", + Departments = new List() + }; + + user.Departments.ForEach(x => x.Employee = user); + + var yesterday = DateTime.Now.AddDays(-1); + + var testDepartment = await _departmentService.SaveDepartmentAsync(new Department() + { + BusinessLineCode = "1", + DayforceDepartmentName = "TestName", + DayForceGlCode = "1", + DepartmentName = "TestName", + DnaOrgNbr = "1", + Assignments = new List + { + new UserDepartment() + { + Id = 11, + Department = department, + Employee = user, + Role = Role.BM, + IsHome = true, + StartDate = yesterday, + EndDate = null + }, + new UserDepartment() + { + Id = 12, + Department = department, + Employee = user, + Role = Role.BM, + IsHome = false, + StartDate = yesterday, + EndDate = null + } + } + }); + + var result = await _departmentService.GetDepartmentDetailsAsync(testDepartment.Id.Value); + + Assert.AreEqual("TestName", result.Department.DepartmentName); + Assert.AreEqual(testDepartment.Assignments.Count, result.Assignments.Count); + Assert.AreEqual(11, result.Assignments.ElementAt(0).Id); + Assert.AreEqual(12, result.Assignments.ElementAt(1).Id); + Assert.IsNull(result.RegionalManager); + + Mock.Get(_userAssignmentService).Verify( + mock => mock.GetUserAssignment(AssignmentType.RegionalManager, It.IsAny()), + Times.Never); + } + + [Test] + public void GetDepartmentDetailsAsync_NotFoundException() + { + var exception = Assert.ThrowsAsync(async () => { await _departmentService.GetDepartmentDetailsAsync(1); }); + + Assert.AreEqual("Could Not Find Department With ID 1", exception.Message); + } + + [Test] + public async Task GetDepartmentDetailsAsync_RegionCodeLookup() + { + var user = new AppUser + { + HomeDepartment = "Home", + Role = Role.Admin, + AdEmail = "user@landmarkcu.com", + EmployeeId = "C0000", + FullName = "Abraham TestAssociate1", + BaseRole = "UNKNOWN", + PersonId = 0, + Departments = new List() + }; + var yesterday = DateTime.Now.AddDays(-1); + var testDepartment = await _departmentService.SaveDepartmentAsync(new Department() + { + BusinessLineCode = "1", + DayforceDepartmentName = "TestName", + DayForceGlCode = "1", + DepartmentName = "TestName", + DnaOrgNbr = "1", + Assignments = new List + { + new UserDepartment { Id = 11, StartDate = yesterday, Employee = user, IsHome = true, Role = Role.Admin}, + new UserDepartment { Id = 12, StartDate = yesterday, Employee = user, IsHome = false, Role = Role.Admin} + }, + RegionCode = "NORTH" + }); + var regionalManager = new AppUser + { AdEmail = "regionalManager@landmarkcu.com", EmployeeId = "RM000", PersonId = 1 }; + var regionalManagerAssignment = new UserAssignment + { + Type = AssignmentType.RegionalManager, + Key = testDepartment.RegionCode, + AppUser = regionalManager + }; + Mock.Get(_userAssignmentService) + .Setup(mock => mock.GetUserAssignment(AssignmentType.RegionalManager, It.IsAny())) + .ReturnsAsync(regionalManagerAssignment); + + + var result = await _departmentService.GetDepartmentDetailsAsync(testDepartment.Id.Value); + + Assert.AreEqual("regionalManager@landmarkcu.com", result.RegionalManager.AdEmail); + Assert.AreEqual("RM000", result.RegionalManager.EmployeeId); + Assert.AreEqual(1, result.RegionalManager.PersonId); + + Mock.Get(_userAssignmentService).Verify( + mock => mock.GetUserAssignment(AssignmentType.RegionalManager, It.IsAny()), + Times.Once); + } + + [Test] + public async Task GetHierarchicalDepartmentsForEmployeeAsync_Basic_ADMIN() + { + var departmentRepository = Mock.Of(); + var userAssignmentService = Mock.Of(); + var mockUnitOfWork = new Mock(); + var departmentService = + new DepartmentService(departmentRepository, userAssignmentService, mockUnitOfWork.Object); + + var user = new AppUser() + { + AdEmail = "tester@test.com", + BaseRole = "Tester", + EmployeeId = "11771", + FullName = "Test McTester", + HomeDepartment = "HOME", + PersonId = 565, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 55, + Role = Role.Admin + } + } + }; + + Mock.Get(departmentRepository).Setup(s => s.GetAllDepartmentsAsync()).ReturnsAsync(new List() + { + new Department() + { + DepartmentName = "testdep1" + } + }); + + Mock.Get(departmentRepository).Setup(s => s.FindDepartmentsByRegionAsync(It.IsAny())).ReturnsAsync(new List() + { + new Department() + { + DepartmentName = "testdep2" + } + }); + + var results = await departmentService.GetHierarchicalDepartmentsForEmployeeAsync(user, false); + Assert.NotNull(results); + Assert.IsNotEmpty(results); + Mock.Get(departmentRepository).Verify(s => s.GetAllDepartmentsAsync(), Times.Once); + Mock.Get(departmentRepository).Verify(s => s.FindDepartmentsByRegionAsync(It.IsAny()), Times.Never); + } + + [Test] + public async Task GetHierarchicalDepartmentsForEmployeeAsync_Basic_RM() + { + var departmentRepository = Mock.Of(); + var userAssignmentService = Mock.Of(); + var mockUnitOfWork = new Mock(); + var departmentService = + new DepartmentService(departmentRepository, userAssignmentService, mockUnitOfWork.Object); + + var user = new AppUser() + { + AdEmail = "tester@test.com", + BaseRole = "Tester", + EmployeeId = "11771", + FullName = "Test McTester", + HomeDepartment = "HOME", + PersonId = 565, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 55, + Role = Role.RegionalManager, + Department = new Department() + { + RegionCode = Region.East.Code + } + } + } + }; + + Mock.Get(departmentRepository).Setup(s => s.GetAllDepartmentsAsync()).ReturnsAsync(new List() + { + new Department() + { + DepartmentName = "testdep1" + } + }); + + Mock.Get(departmentRepository).Setup(s => s.FindDepartmentsByRegionAsync(It.IsAny())).ReturnsAsync(new List() + { + new Department() + { + DepartmentName = "testdep2" + } + }); + + var results = await departmentService.GetHierarchicalDepartmentsForEmployeeAsync(user, false); + Assert.NotNull(results); + Assert.IsNotEmpty(results); + Mock.Get(departmentRepository).Verify(s => s.GetAllDepartmentsAsync(), Times.Never); + Mock.Get(departmentRepository).Verify(s => s.FindDepartmentsByRegionAsync(It.IsAny()), Times.Once); + } + + // if you are RM but there is no region, you'll just get the branch where you are RM back... + [Test] + public async Task GetHierarchicalDepartmentsForEmployeeAsync_Basic_RM_null_region() + { + var departmentRepository = Mock.Of(); + var userAssignmentService = Mock.Of(); + var mockUnitOfWork = new Mock(); + var departmentService = + new DepartmentService(departmentRepository, userAssignmentService, mockUnitOfWork.Object); + + var user = new AppUser() + { + AdEmail = "tester@test.com", + BaseRole = "Tester", + EmployeeId = "11771", + FullName = "Test McTester", + HomeDepartment = "HOME", + PersonId = 565, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 55, + Role = Role.RegionalManager, + Department = new Department() + { + DepartmentName = "testdep3" + // No Region. + }, + StartDate = DateTime.Today + } + } + }; + + Mock.Get(departmentRepository).Setup(s => s.GetAllDepartmentsAsync()).ReturnsAsync(new List() + { + new Department() + { + DepartmentName = "testdep1" + } + }); + + Mock.Get(departmentRepository).Setup(s => s.FindDepartmentsByRegionAsync(It.IsAny())).ReturnsAsync(new List() + { + new Department() + { + DepartmentName = "testdep2" + } + }); + + var results = await departmentService.GetHierarchicalDepartmentsForEmployeeAsync(user, false); + Assert.NotNull(results); + Assert.IsNotEmpty(results); + Mock.Get(departmentRepository).Verify(s => s.GetAllDepartmentsAsync(), Times.Never); + Mock.Get(departmentRepository).Verify(s => s.FindDepartmentsByRegionAsync(It.IsAny()), Times.Never); + } + + [Test] + public async Task GetHierarchicalDepartmentsForEmployeeAsync_Basic_ABM_MANAGER_BM() + { + var departmentRepository = Mock.Of(); + var userAssignmentService = Mock.Of(); + var mockUnitOfWork = new Mock(); + var departmentService = + new DepartmentService(departmentRepository, userAssignmentService, mockUnitOfWork.Object); + + var user = new AppUser() + { + AdEmail = "tester@test.com", + BaseRole = "Tester", + EmployeeId = "11771", + FullName = "Test McTester", + HomeDepartment = "HOME", + PersonId = 565, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 55, + Role = Role.ABM, + Department = new Department() + { + DepartmentName = "testdeptabm" + } + } + } + }; + + Mock.Get(departmentRepository).Setup(s => s.GetAllDepartmentsAsync()).ReturnsAsync(new List() + { + new Department() + { + DepartmentName = "testdep1" + } + }); + + Mock.Get(departmentRepository).Setup(s => s.FindDepartmentsByRegionAsync(It.IsAny())).ReturnsAsync(new List() + { + new Department() + { + DepartmentName = "testdep2" + } + }); + + var results = await departmentService.GetHierarchicalDepartmentsForEmployeeAsync(user, true); + Assert.NotNull(results); + Assert.IsNotEmpty(results); + Mock.Get(departmentRepository).Verify(s => s.GetAllDepartmentsAsync(), Times.Never); + Mock.Get(departmentRepository).Verify(s => s.FindDepartmentsByRegionAsync(It.IsAny()), Times.Never); + + results = await departmentService.GetHierarchicalDepartmentsForEmployeeAsync(user, false); + Assert.NotNull(results); + Assert.IsEmpty(results); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DispersalStrategy/DispersalStrategyServiceTestData.json b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DispersalStrategy/DispersalStrategyServiceTestData.json new file mode 100644 index 0000000..bda7905 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DispersalStrategy/DispersalStrategyServiceTestData.json @@ -0,0 +1,1107 @@ +{ + "BaseLine": { + "AllPersonnel": [{ + "EmployeeId": "1A", + "AdditionalPermissions": [], + "Departments": [{ + "DepartmentId": 10 + }] + }, + { + "EmployeeId": "2B", + "AdditionalPermissions": [], + "Departments": [{ + "DepartmentId": 10 + }] + } + ], + "ModelMonthPersonnelGoals": [{ + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "Mortgage", + "Value": 25, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "Vehicle", + "Value": 2, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "CheckCard", + "Value": 1, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "DirectDeposit", + "Value": 1, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "Mortgage", + "Value": 25, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "Vehicle", + "Value": 2, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "CheckCard", + "Value": 1, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "DirectDeposit", + "Value": 7, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + } + ], + "DispersalMonthPersonnelGoals": [{ + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "Mortgage", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "Vehicle", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "CheckCard", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "DirectDeposit", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "Mortgage", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "Vehicle", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "CheckCard", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "DirectDeposit", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + } + ] + }, + "UserNoLongerInDepartment": { + "PersonnelInDispersalMonth": [{ + "EmployeeId": "1A", + "AdditionalPermissions": [], + "Departments": [{ + "DepartmentId": 10 + }] + }], + "PersonnelInModelMonth": [{ + "EmployeeId": "1A", + "AdditionalPermissions": [], + "Departments": [{ + "DepartmentId": 10 + }] + }, + { + "EmployeeId": "PersonA", + "AdditionalPermissions": [], + "Departments": [{ + "DepartmentId": 10 + }] + }, + { + "EmployeeId": "PersonB", + "AdditionalPermissions": [], + "Departments": [{ + "DepartmentId": 10 + }] + } + ], + "ModelMonthPersonnelGoals": [{ + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "Mortgage", + "Value": 25, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "Vehicle", + "Value": 2, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "CheckCard", + "Value": 1, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "DirectDeposit", + "Value": 1, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "PersonA", + "Product": "Mortgage", + "Value": 25, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "PersonB", + "Product": "Vehicle", + "Value": 2, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "PersonA", + "Product": "CheckCard", + "Value": 1, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "PersonB", + "Product": "DirectDeposit", + "Value": 7, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + } + ], + "DispersalMonthPersonnelGoals": [{ + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "Mortgage", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "Vehicle", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "CheckCard", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "DirectDeposit", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + } + ] + }, + "DispersedToUserWithoutPermission": { + "AllPersonnel": [{ + "EmployeeId": "1A", + "AdditionalPermissions": [], + "Departments": [{ + "DepartmentId": 10 + }] + }, + { + "EmployeeId": "2B", + "AdditionalPermissions": [], + "Departments": [{ + "DepartmentId": 10 + }] + }, + { + "EmployeeId": "3C", + "AdditionalPermissions": [], + "Departments": [{ + "DepartmentId": 10 + }] + }, + { + "EmployeeId": "4D", + "AdditionalPermissions": [], + "Departments": [{ + "DepartmentId": 10 + }] + } + ], + "ModelMonthPersonnelGoals": [{ + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "Mortgage", + "Value": 25, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "Vehicle", + "Value": 2, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "CheckCard", + "Value": 1, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "DirectDeposit", + "Value": 1, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "Mortgage", + "Value": 25, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "Vehicle", + "Value": 2, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "CheckCard", + "Value": 1, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "DirectDeposit", + "Value": 7, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "3C", + "Product": "Mortgage", + "Value": 0, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "3C", + "Product": "Vehicle", + "Value": 0, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "3C", + "Product": "CheckCard", + "Value": 0, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "3C", + "Product": "DirectDeposit", + "Value": 0, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + } + ], + "DispersalMonthPersonnelGoals": [{ + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "Mortgage", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "Vehicle", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "CheckCard", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "DirectDeposit", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "Mortgage", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "Vehicle", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "CheckCard", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "DirectDeposit", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "3C", + "Product": "Mortgage", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "3C", + "Product": "Vehicle", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "3C", + "Product": "CheckCard", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "3C", + "Product": "DirectDeposit", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + } + ] + }, + "NewUserRequiresManualEntry": { + "AllPersonnel": [{ + "EmployeeId": "1A", + "AdditionalPermissions": [], + "Departments": [{ + "DepartmentId": 10 + }] + }, + { + "EmployeeId": "2B", + "AdditionalPermissions": [], + "Departments": [{ + "DepartmentId": 10 + }] + }, + { + "EmployeeId": "3C", + "AdditionalPermissions": [], + "Departments": [{ + "DepartmentId": 10 + }] + }, + { + "EmployeeId": "4D", + "AdditionalPermissions": [], + "Departments": [{ + "DepartmentId": 10 + }] + }, + { + "EmployeeId": "5E", + "AdditionalPermissions": [], + "Departments": [{ + "DepartmentId": 10 + }] + } + ], + "ModelMonthPersonnelGoals": [{ + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "Mortgage", + "Value": 25, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "Vehicle", + "Value": 2, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "CheckCard", + "Value": 1, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "DirectDeposit", + "Value": 1, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "Mortgage", + "Value": 25, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "Vehicle", + "Value": 2, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "CheckCard", + "Value": 1, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "DirectDeposit", + "Value": 7, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "3C", + "Product": "Mortgage", + "Value": 0, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "3C", + "Product": "Vehicle", + "Value": 0, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "3C", + "Product": "CheckCard", + "Value": 0, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "3C", + "Product": "DirectDeposit", + "Value": 0, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + } + ], + "DispersalMonthPersonnelGoals": [{ + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "Mortgage", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "Vehicle", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "CheckCard", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "DirectDeposit", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "Mortgage", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "Vehicle", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "CheckCard", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "DirectDeposit", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "3C", + "Product": "Mortgage", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "3C", + "Product": "Vehicle", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "3C", + "Product": "CheckCard", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "3C", + "Product": "DirectDeposit", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + } + ] + }, + "NoPermission": { + "AllPersonnel": [{ + "EmployeeId": "1A", + "AdditionalPermissions": [], + "Departments": [{ + "DepartmentId": 10 + }] + }, + { + "EmployeeId": "2B", + "AdditionalPermissions": [], + "Departments": [{ + "DepartmentId": 10 + }] + }, + { + "EmployeeId": "3C", + "AdditionalPermissions": [], + "Departments": [{ + "DepartmentId": 10 + }] + }, + { + "EmployeeId": "4D", + "AdditionalPermissions": [], + "Departments": [{ + "DepartmentId": 10 + }] + } + ], + "ModelMonthPersonnelGoals": [{ + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "Mortgage", + "Value": 25, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "Vehicle", + "Value": 2, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "CheckCard", + "Value": 1, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "DirectDeposit", + "Value": 1, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "Mortgage", + "Value": 25, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "Vehicle", + "Value": 2, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "CheckCard", + "Value": 1, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "DirectDeposit", + "Value": 7, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "3C", + "Product": "Mortgage", + "Value": 0, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "3C", + "Product": "Vehicle", + "Value": 0, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "3C", + "Product": "CheckCard", + "Value": 0, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "3C", + "Product": "DirectDeposit", + "Value": 0, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + } + ], + "DispersalMonthPersonnelGoals": [{ + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "Mortgage", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "Vehicle", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "CheckCard", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "DirectDeposit", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "Mortgage", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "Vehicle", + "Value": 0, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "CheckCard", + "Value": 0, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "DirectDeposit", + "Value": 0, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "3C", + "Product": "Mortgage", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "3C", + "Product": "Vehicle", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "3C", + "Product": "CheckCard", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "3C", + "Product": "DirectDeposit", + "Value": 1000, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + } + ] + }, + "NewValueSame": { + "AllPersonnel": [{ + "EmployeeId": "1A", + "AdditionalPermissions": [], + "Departments": [{ + "DepartmentId": 10 + }] + }, + { + "EmployeeId": "2B", + "AdditionalPermissions": [], + "Departments": [{ + "DepartmentId": 10 + }] + } + ], + "ModelMonthPersonnelGoals": [{ + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "Mortgage", + "Value": 25, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "Vehicle", + "Value": 2, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "CheckCard", + "Value": 1, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "DirectDeposit", + "Value": 1, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "Mortgage", + "Value": 25, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "Vehicle", + "Value": 2, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "CheckCard", + "Value": 1, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "DirectDeposit", + "Value": 7, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + } + ], + "DispersalMonthPersonnelGoals": [{ + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "Mortgage", + "Value": 25, + "StartDate": "2021-07-01", + "EndDate": "2021-07-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "Vehicle", + "Value": 2, + "StartDate": "2021-08-01", + "EndDate": "2021-08-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "CheckCard", + "Value": 1, + "StartDate": "2021-08-01", + "EndDate": "2021-08-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "1A", + "Product": "DirectDeposit", + "Value": 1, + "StartDate": "2021-08-01", + "EndDate": "2021-08-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "Mortgage", + "Value": 25, + "StartDate": "2021-08-01", + "EndDate": "2021-08-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "Vehicle", + "Value": 2, + "StartDate": "2021-08-01", + "EndDate": "2021-08-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "CheckCard", + "Value": 1, + "StartDate": "2021-08-01", + "EndDate": "2021-08-31T23:59:59" + }, + { + "DepartmentId": 10, + "EmployeeId": "2B", + "Product": "DirectDeposit", + "Value": 7, + "StartDate": "2021-08-01", + "EndDate": "2021-08-31T23:59:59" + } + ] + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DispersalStrategy/DispersalStrategyTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DispersalStrategy/DispersalStrategyTest.cs new file mode 100644 index 0000000..052d17f --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DispersalStrategy/DispersalStrategyTest.cs @@ -0,0 +1,1636 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.API.Internal.Operations.IncentiveApi.Models.GoalDispersal; +using LCU.API.Internal.Operations.IncentiveApi.Services.DispersalStrategy; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Extensions; +using Moq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using NUnit.Framework; + +namespace Application.UnitTests.Services.DispersalStrategy +{ + public class GoalDispersalServiceTest + { + public JObject JsonData; + public Mock _mockUserService; + public IDispersalStrategyService _dispersalStrategyService; // = new DispersalStrategyService(new Mock()); + + private readonly PerProductGoal _ModelMonthDepartmentGoals = new PerProductGoal() + { + ProductGoals = new Dictionary() + { + { Product.Mortgage, 105 }, + { Product.Vehicle, 0 }, + { Product.CheckCard, 5 }, + { Product.DirectDeposit, 18 } + } + }; + + private readonly PerProductGoal _DispersalMonthDepartmentGoals = new PerProductGoal() + { + ProductGoals = new Dictionary() + { + { Product.Mortgage, 200 }, + { Product.Vehicle, 10 }, + { Product.CheckCard, 20 }, + { Product.DirectDeposit, 35 } + } + }; + + [SetUp] + public void SetUp() + { + _mockUserService = new Mock(); + _dispersalStrategyService = new DispersalStrategyService(_mockUserService.Object); + _mockUserService.Setup(mock => mock.GetActiveUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync(new AppUser + { + Id = Guid.NewGuid(), + AdEmail = "NewGuidUser@landmarkcu.com", + EmployeeId = "C0000", + Departments = + { + new UserDepartment + { + DepartmentId = 1 + } + } + }); + + JsonData = JObject.Parse( + File.ReadAllText("Services/DispersalStrategy/DispersalStrategyServiceTestData.json")); + } + + + [Test] + public void ExtrapolateFromExistingGoals_Baseline_OK() + { + var testData = JsonData["BaseLine"]; + var personnel = JsonConvert.DeserializeObject>(testData["AllPersonnel"].ToString()); + var modelPersonnelGoalList = + JsonConvert.DeserializeObject>(testData["ModelMonthPersonnelGoals"].ToString()); + var dispersePersonnelGoalList = + JsonConvert.DeserializeObject>(testData["DispersalMonthPersonnelGoals"].ToString()); + + //Set All Users to BM + personnel = personnel.Select(user => + { + user.Departments.First().Role = Role.BM; + return user; + }).ToList(); + + var modelPersonnelGoal = new PersonnelGoals(10, 2021, 07, personnel); + modelPersonnelGoal.ApplyAll(modelPersonnelGoalList); + + //These are all set arbitrarily high so that there is no situation where the newValue is the same as the current value + var dispersalMonthPersonnelGoal = new PersonnelGoals(10, 2021, 08, personnel); + dispersalMonthPersonnelGoal.ApplyAll(dispersePersonnelGoalList); + + var extrapolationGoalDispersalData = new ExtrapolationGoalDispersalData() + { + DepartmentId = 10, + ModelDepartmentGoals = _ModelMonthDepartmentGoals, + DispersalMonthDepartmentGoals = _DispersalMonthDepartmentGoals, + DispersalMonthPersonnelGoals = dispersalMonthPersonnelGoal, + ModelPersonnelGoals = modelPersonnelGoal, + DispersalMonthPersonnel = personnel, + DispersalMonthStartDate = DateTime.Parse("2021-08-1").StartOfMonth(), + }; + + var results = + _dispersalStrategyService.ExtrapolateFromExistingGoals(extrapolationGoalDispersalData); + + Assert.AreEqual(8, results.GoalChanges.Count); + Assert.AreEqual(0, results.GoalDispersalErrors.Count); + Assert.AreEqual(48, + results.GoalChanges.Where(x => x.PersonId == "1A").First(x => x.Product == Product.Mortgage).Value); + Assert.AreEqual(0, + results.GoalChanges.Where(x => x.PersonId == "1A").First(x => x.Product == Product.Vehicle).Value); + Assert.AreEqual(4, + results.GoalChanges.Where(x => x.PersonId == "1A").First(x => x.Product == Product.CheckCard).Value); + Assert.AreEqual(2, + results.GoalChanges.Where(x => x.PersonId == "1A").First(x => x.Product == Product.DirectDeposit) + .Value); + Assert.AreEqual(48, + results.GoalChanges.Where(x => x.PersonId == "2B").First(x => x.Product == Product.Mortgage).Value); + Assert.AreEqual(0, + results.GoalChanges.Where(x => x.PersonId == "2B").First(x => x.Product == Product.Vehicle).Value); + Assert.AreEqual(4, + results.GoalChanges.Where(x => x.PersonId == "2B").First(x => x.Product == Product.CheckCard).Value); + Assert.AreEqual(14, + results.GoalChanges.Where(x => x.PersonId == "2B").First(x => x.Product == Product.DirectDeposit) + .Value); + } + + [Test] + public void ExtrapolateFromExistingGoals_UserNoLongerInDepartment_OK() + { + var testData = JsonData["UserNoLongerInDepartment"]; + var modelMonthPersonnel = + JsonConvert.DeserializeObject>(testData["PersonnelInModelMonth"].ToString()); + var dispersalMonthPersonnel = + JsonConvert.DeserializeObject>(testData["PersonnelInDispersalMonth"].ToString()); + var modelPersonnelGoalList = + JsonConvert.DeserializeObject>(testData["ModelMonthPersonnelGoals"].ToString()); + var dispersePersonnelGoalList = + JsonConvert.DeserializeObject>(testData["DispersalMonthPersonnelGoals"].ToString()); + + //Set All Users to BM + dispersalMonthPersonnel = dispersalMonthPersonnel.Select(user => + { + user.Departments.First().Role = Role.BM; + return user; + }).ToList(); + modelMonthPersonnel = modelMonthPersonnel.Select(user => + { + user.Departments.First().Role = Role.BM; + return user; + }).ToList(); + + //Contains 2 users who do not exist in dispersal month (PersonID "PersonA" and "PersonB") + //Contains 1 user who does exist in dispersal mont (PersonId "1A") + var modelPersonnelGoal = new PersonnelGoals(10, 2021, 07, modelMonthPersonnel); + modelPersonnelGoal.ApplyAll(modelPersonnelGoalList); + + var dispersalMonthPersonnelGoal = new PersonnelGoals(10, 2021, 08, dispersalMonthPersonnel); + dispersalMonthPersonnelGoal.ApplyAll(dispersePersonnelGoalList); + + var extrapolationGoalDispersalData = new ExtrapolationGoalDispersalData() + { + DepartmentId = 10, + ModelDepartmentGoals = _ModelMonthDepartmentGoals, + DispersalMonthDepartmentGoals = _DispersalMonthDepartmentGoals, + DispersalMonthPersonnelGoals = dispersalMonthPersonnelGoal, + ModelPersonnelGoals = modelPersonnelGoal, + //The users that are in our modelMonthPersonnel goals are not in + DispersalMonthPersonnel = dispersalMonthPersonnel, + DispersalMonthStartDate = DateTime.Parse("2021-08-1").StartOfMonth(), + }; + + var results = + _dispersalStrategyService.ExtrapolateFromExistingGoals(extrapolationGoalDispersalData); + + Assert.AreEqual(4, results.GoalChanges.Count); + Assert.AreEqual(2, results.GoalDispersalErrors.Count); + Assert.AreEqual(48, + results.GoalChanges.Where(x => x.PersonId == "1A").First(x => x.Product == Product.Mortgage).Value); + Assert.AreEqual(0, + results.GoalChanges.Where(x => x.PersonId == "1A").First(x => x.Product == Product.Vehicle).Value); + Assert.AreEqual(4, + results.GoalChanges.Where(x => x.PersonId == "1A").First(x => x.Product == Product.CheckCard).Value); + Assert.AreEqual(2, + results.GoalChanges.Where(x => x.PersonId == "1A").First(x => x.Product == Product.DirectDeposit) + .Value); + Assert.True(results.GoalDispersalErrors.Any(x => + x.EmployeeId == "PersonA" && x.ErrorType == GoalDispersalError.UserNoLongerInDepartment)); + Assert.True(results.GoalDispersalErrors.Any(x => + x.EmployeeId == "PersonB" && x.ErrorType == GoalDispersalError.UserNoLongerInDepartment)); + } + + [Test] + public void ExtrapolateFromExistingGoals_DispersedToUserWithoutPermission_OK() + { + var testData = JsonData["DispersedToUserWithoutPermission"]; + var personnel = JsonConvert.DeserializeObject>(testData["AllPersonnel"].ToString()); + var modelPersonnelGoalList = + JsonConvert.DeserializeObject>(testData["ModelMonthPersonnelGoals"].ToString()); + var dispersePersonnelGoalList = + JsonConvert.DeserializeObject>(testData["DispersalMonthPersonnelGoals"].ToString()); + + //Set 1A to BM, they have hassalesgoal permission + //Set 2B and 3C to Teller, they do not have hassalesgoals permission + personnel = personnel.Select(user => + { + user.Departments.First().Role = (user.EmployeeId == "1A") ? Role.BM : Role.Teller; + return user; + }).ToList(); + + var modelPersonnelGoal = new PersonnelGoals(10, 2021, 07, personnel); + modelPersonnelGoal.ApplyAll(modelPersonnelGoalList); + + //These are all set arbitrarily high so that there is no situation where the newValue is the same as the current value + var dispersalMonthPersonnelGoal = new PersonnelGoals(10, 2021, 08, personnel); + dispersalMonthPersonnelGoal.ApplyAll(dispersePersonnelGoalList); + + var extrapolationGoalDispersalData = new ExtrapolationGoalDispersalData() + { + DepartmentId = 10, + ModelDepartmentGoals = _ModelMonthDepartmentGoals, + DispersalMonthDepartmentGoals = _DispersalMonthDepartmentGoals, + DispersalMonthPersonnelGoals = dispersalMonthPersonnelGoal, + ModelPersonnelGoals = modelPersonnelGoal, + DispersalMonthPersonnel = personnel, + DispersalMonthStartDate = DateTime.Parse("2021-08-1").StartOfMonth(), + }; + + var results = + _dispersalStrategyService.ExtrapolateFromExistingGoals(extrapolationGoalDispersalData); + + Assert.AreEqual(12, results.GoalChanges.Count); + Assert.AreEqual(1, results.GoalDispersalErrors.Count); + Assert.AreEqual(48, + results.GoalChanges.Where(x => x.PersonId == "1A").First(x => x.Product == Product.Mortgage).Value); + Assert.AreEqual(0, + results.GoalChanges.Where(x => x.PersonId == "1A").First(x => x.Product == Product.Vehicle).Value); + Assert.AreEqual(4, + results.GoalChanges.Where(x => x.PersonId == "1A").First(x => x.Product == Product.CheckCard).Value); + Assert.AreEqual(2, + results.GoalChanges.Where(x => x.PersonId == "1A").First(x => x.Product == Product.DirectDeposit) + .Value); + Assert.AreEqual(48, + results.GoalChanges.Where(x => x.PersonId == "2B").First(x => x.Product == Product.Mortgage).Value); + Assert.AreEqual(0, + results.GoalChanges.Where(x => x.PersonId == "2B").First(x => x.Product == Product.Vehicle).Value); + Assert.AreEqual(4, + results.GoalChanges.Where(x => x.PersonId == "2B").First(x => x.Product == Product.CheckCard).Value); + Assert.AreEqual(14, + results.GoalChanges.Where(x => x.PersonId == "2B").First(x => x.Product == Product.DirectDeposit) + .Value); + //All of 3Cs goal changes are request values of 0. This means they have not been assigned any goals. Effectively 3Cs goals have been "Cleared" + //Because of this we do not want to issue a "DispersedToUserWithoutPermission" error + Assert.AreEqual(0, + results.GoalChanges.Where(x => x.PersonId == "3C").First(x => x.Product == Product.Mortgage).Value); + Assert.AreEqual(0, + results.GoalChanges.Where(x => x.PersonId == "3C").First(x => x.Product == Product.Vehicle).Value); + Assert.AreEqual(0, + results.GoalChanges.Where(x => x.PersonId == "3C").First(x => x.Product == Product.CheckCard).Value); + Assert.AreEqual(0, + results.GoalChanges.Where(x => x.PersonId == "3C").First(x => x.Product == Product.DirectDeposit) + .Value); + //User 4D has no goal changes, this should not dispatch the DispersedToUserWithoutPermission error + Assert.AreEqual(0, results.GoalChanges.Count(x => x.PersonId == "4D")); + Assert.True(results.GoalDispersalErrors.Exists(x => + x.EmployeeId == "2B" && x.ErrorType == GoalDispersalError.DispersedToUserWithoutPermission)); + } + + [Test] + //Verify this alert is only sent when there are no values > 0 + public void ExtrapolateFromExistingGoals_NewUserRequiresManualEntry_OK() + { + var testData = JsonData["NewUserRequiresManualEntry"]; + var personnel = JsonConvert.DeserializeObject>(testData["AllPersonnel"].ToString()); + var modelPersonnelGoalList = + JsonConvert.DeserializeObject>(testData["ModelMonthPersonnelGoals"].ToString()); + var dispersePersonnelGoalList = + JsonConvert.DeserializeObject>(testData["DispersalMonthPersonnelGoals"].ToString()); + + //All users hasSalesGoal permission except for user 5D + personnel = personnel.Select(user => + { + user.Departments.First().Role = (user.EmployeeId == "5E") ? Role.Teller : Role.BM; + return user; + }).ToList(); + + var modelPersonnelGoal = new PersonnelGoals(10, 2021, 07, personnel); + modelPersonnelGoal.ApplyAll(modelPersonnelGoalList); + + //These are all set arbitrarily high so that there is no situation where the newValue is the same as the current value + var dispersalMonthPersonnelGoal = new PersonnelGoals(10, 2021, 08, personnel); + dispersalMonthPersonnelGoal.ApplyAll(dispersePersonnelGoalList); + + var extrapolationGoalDispersalData = new ExtrapolationGoalDispersalData() + { + DepartmentId = 10, + ModelDepartmentGoals = _ModelMonthDepartmentGoals, + DispersalMonthDepartmentGoals = _DispersalMonthDepartmentGoals, + DispersalMonthPersonnelGoals = dispersalMonthPersonnelGoal, + ModelPersonnelGoals = modelPersonnelGoal, + DispersalMonthPersonnel = personnel, + DispersalMonthStartDate = DateTime.Parse("2021-08-1").StartOfMonth(), + }; + + var results = + _dispersalStrategyService.ExtrapolateFromExistingGoals(extrapolationGoalDispersalData); + + Assert.AreEqual(12, results.GoalChanges.Count); + Assert.AreEqual(2, results.GoalDispersalErrors.Count); + Assert.AreEqual(48, + results.GoalChanges.Where(x => x.PersonId == "1A").First(x => x.Product == Product.Mortgage).Value); + Assert.AreEqual(0, + results.GoalChanges.Where(x => x.PersonId == "1A").First(x => x.Product == Product.Vehicle).Value); + Assert.AreEqual(4, + results.GoalChanges.Where(x => x.PersonId == "1A").First(x => x.Product == Product.CheckCard).Value); + Assert.AreEqual(2, + results.GoalChanges.Where(x => x.PersonId == "1A").First(x => x.Product == Product.DirectDeposit) + .Value); + Assert.AreEqual(48, + results.GoalChanges.Where(x => x.PersonId == "2B").First(x => x.Product == Product.Mortgage).Value); + Assert.AreEqual(0, + results.GoalChanges.Where(x => x.PersonId == "2B").First(x => x.Product == Product.Vehicle).Value); + Assert.AreEqual(4, + results.GoalChanges.Where(x => x.PersonId == "2B").First(x => x.Product == Product.CheckCard).Value); + Assert.AreEqual(14, + results.GoalChanges.Where(x => x.PersonId == "2B").First(x => x.Product == Product.DirectDeposit) + .Value); + //All of 3Cs goal changes are request values of 0, this should dispatch because they havesales role + Assert.AreEqual(0, + results.GoalChanges.Where(x => x.PersonId == "3C").First(x => x.Product == Product.Mortgage).Value); + Assert.AreEqual(0, + results.GoalChanges.Where(x => x.PersonId == "3C").First(x => x.Product == Product.Vehicle).Value); + Assert.AreEqual(0, + results.GoalChanges.Where(x => x.PersonId == "3C").First(x => x.Product == Product.CheckCard).Value); + Assert.AreEqual(0, + results.GoalChanges.Where(x => x.PersonId == "3C").First(x => x.Product == Product.DirectDeposit) + .Value); + //4D has no goal changes, but they should still dispatch this notification because they have hassalesgoal role + Assert.AreEqual(0, results.GoalChanges.Count(x => x.PersonId == "4D")); + //5D has no goal changes, but they do not have the hassalesgoal permission, no error should be dispatched + Assert.AreEqual(0, results.GoalChanges.Count(x => x.PersonId == "5E")); + Assert.True(results.GoalDispersalErrors.Exists(x => + x.EmployeeId == "3C" && x.ErrorType == GoalDispersalError.NewUserRequiresManualEntry)); + Assert.True(results.GoalDispersalErrors.Exists(x => + x.EmployeeId == "4D" && x.ErrorType == GoalDispersalError.NewUserRequiresManualEntry)); + } + + [Test] + public void ExtrapolateFromExistingGoals_NoPermission_OK() + { + //1A has permission + //2B Has No permission and No Model Month Goals + //3C Has No permission and No Dispersal Month Goals + //4D Hs No Permission and No Goals + var testData = JsonData["NoPermission"]; + var personnel = JsonConvert.DeserializeObject>(testData["AllPersonnel"].ToString()); + var modelPersonnelGoalList = + JsonConvert.DeserializeObject>(testData["ModelMonthPersonnelGoals"].ToString()); + var dispersePersonnelGoalList = + JsonConvert.DeserializeObject>(testData["DispersalMonthPersonnelGoals"].ToString()); + + //Set All Users to BM + personnel = personnel.Select(user => + { + user.Departments.First().Role = (user.EmployeeId == "1A") ? Role.BM : Role.Teller; + return user; + }).ToList(); + + var modelPersonnelGoal = new PersonnelGoals(10, 2021, 07, personnel); + modelPersonnelGoal.ApplyAll(modelPersonnelGoalList); + + //These are all set arbitrarily high so that there is no situation where the newValue is the same as the current value + var dispersalMonthPersonnelGoal = new PersonnelGoals(10, 2021, 08, personnel); + dispersalMonthPersonnelGoal.ApplyAll(dispersePersonnelGoalList); + + var extrapolationGoalDispersalData = new ExtrapolationGoalDispersalData() + { + DepartmentId = 10, + ModelDepartmentGoals = _ModelMonthDepartmentGoals, + DispersalMonthDepartmentGoals = _DispersalMonthDepartmentGoals, + DispersalMonthPersonnelGoals = dispersalMonthPersonnelGoal, + ModelPersonnelGoals = modelPersonnelGoal, + DispersalMonthPersonnel = personnel, + DispersalMonthStartDate = DateTime.Parse("2021-08-1").StartOfMonth(), + }; + + var results = + _dispersalStrategyService.ExtrapolateFromExistingGoals(extrapolationGoalDispersalData); + + Assert.AreEqual(11, results.GoalChanges.Count); + Assert.AreEqual(1, results.GoalDispersalErrors.Count); + Assert.AreEqual(48, + results.GoalChanges.Where(x => x.PersonId == "1A").First(x => x.Product == Product.Mortgage).Value); + Assert.AreEqual(0, + results.GoalChanges.Where(x => x.PersonId == "1A").First(x => x.Product == Product.Vehicle).Value); + Assert.AreEqual(4, + results.GoalChanges.Where(x => x.PersonId == "1A").First(x => x.Product == Product.CheckCard).Value); + Assert.AreEqual(2, + results.GoalChanges.Where(x => x.PersonId == "1A").First(x => x.Product == Product.DirectDeposit) + .Value); + Assert.AreEqual(48, + results.GoalChanges.Where(x => x.PersonId == "2B").First(x => x.Product == Product.Mortgage).Value); + //Person 1B Product.Vehicle NewValue is 0 -> Does not save goal because value is already 0 + Assert.AreEqual(4, + results.GoalChanges.Where(x => x.PersonId == "2B").First(x => x.Product == Product.CheckCard).Value); + Assert.AreEqual(14, + results.GoalChanges.Where(x => x.PersonId == "2B").First(x => x.Product == Product.DirectDeposit) + .Value); + Assert.AreEqual(0, + results.GoalChanges.Where(x => x.PersonId == "3C").First(x => x.Product == Product.Mortgage).Value); + Assert.AreEqual(0, + results.GoalChanges.Where(x => x.PersonId == "3C").First(x => x.Product == Product.Vehicle).Value); + Assert.AreEqual(0, + results.GoalChanges.Where(x => x.PersonId == "3C").First(x => x.Product == Product.CheckCard).Value); + Assert.AreEqual(0, + results.GoalChanges.Where(x => x.PersonId == "3C").First(x => x.Product == Product.DirectDeposit) + .Value); + Assert.AreEqual(0, results.GoalChanges.Count(x => x.PersonId == "4D")); + } + + [Test] + public void ExtrapolateFromExistingGoals_NoGoalChangeWhenNewValueSame_Ok() + { + var testData = JsonData["NewValueSame"]; + var personnel = JsonConvert.DeserializeObject>(testData["AllPersonnel"].ToString()); + var modelPersonnelGoalList = + JsonConvert.DeserializeObject>(testData["ModelMonthPersonnelGoals"].ToString()); + var dispersePersonnelGoalList = + JsonConvert.DeserializeObject>(testData["DispersalMonthPersonnelGoals"].ToString()); + + //Set All Users to BM + personnel = personnel.Select(user => + { + user.Departments.First().Role = Role.BM; + return user; + }).ToList(); + + //Model Personnel Goals and Dispersal Personnel Goal Values are Identical + var modelPersonnelGoal = new PersonnelGoals(10, 2021, 07, personnel); + modelPersonnelGoal.ApplyAll(modelPersonnelGoalList); + var dispersalMonthPersonnelGoal = new PersonnelGoals(10, 2021, 08, personnel); + dispersalMonthPersonnelGoal.ApplyAll(dispersePersonnelGoalList); + + var extrapolationGoalDispersalData = new ExtrapolationGoalDispersalData() + { + DepartmentId = 10, + //Department Goals are Identical + ModelDepartmentGoals = _DispersalMonthDepartmentGoals, + DispersalMonthDepartmentGoals = _DispersalMonthDepartmentGoals, + DispersalMonthPersonnelGoals = dispersalMonthPersonnelGoal, + ModelPersonnelGoals = modelPersonnelGoal, + DispersalMonthPersonnel = personnel, + DispersalMonthStartDate = DateTime.Parse("2021-08-1").StartOfMonth(), + }; + + var results = + _dispersalStrategyService.ExtrapolateFromExistingGoals(extrapolationGoalDispersalData); + + Assert.AreEqual(0, results.GoalChanges.Count); + } + + [Test] + public void DefaultWeightedDispersal_OK() + { + var users = new List() + { + new AppUser() + { + EmployeeId = "ABM1", + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.ABM + } + } + }, + new AppUser() + { + EmployeeId = "BM1", + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.BM + } + } + }, + new AppUser() + { + EmployeeId = "BM2", + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.BM + } + } + }, + new AppUser() + { + EmployeeId = "PFO1", + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.PFO + } + } + }, + new AppUser() + { + EmployeeId = "PFO2", + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.PFO + } + } + }, + new AppUser() + { + EmployeeId = "PFO3", + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.PFO + } + } + } + }; + + var results = _dispersalStrategyService.DefaultWeightedDispersal( + new DefaultWeightedGoalDispersalData() + { + DepartmentId = 10, + DispersalMonthStartDate = DateTime.Parse("2021-08-01"), + DispersalMonthPersonnel = users, + DispersalMonthDepartmentGoals = _DispersalMonthDepartmentGoals + }); + Assert.AreEqual(24, results.GoalChanges.Count); + Assert.AreEqual(GoalDispersalStrategy.DefaultDisperseWithWeights, results.Strategy); + Assert.AreEqual(30, + results.GoalChanges.Where(x => x.PersonId == "ABM1").First(x => x.Product == Product.Mortgage).Value); + Assert.AreEqual(2, + results.GoalChanges.Where(x => x.PersonId == "ABM1").First(x => x.Product == Product.Vehicle).Value); + Assert.AreEqual(3, + results.GoalChanges.Where(x => x.PersonId == "ABM1").First(x => x.Product == Product.CheckCard).Value); + Assert.AreEqual(6, + results.GoalChanges.Where(x => x.PersonId == "ABM1").First(x => x.Product == Product.DirectDeposit) + .Value); + + Assert.AreEqual(25, + results.GoalChanges.Where(x => x.PersonId == "BM1").First(x => x.Product == Product.Mortgage).Value); + Assert.AreEqual(2, + results.GoalChanges.Where(x => x.PersonId == "BM1").First(x => x.Product == Product.Vehicle).Value); + Assert.AreEqual(3, + results.GoalChanges.Where(x => x.PersonId == "BM1").First(x => x.Product == Product.CheckCard).Value); + Assert.AreEqual(5, + results.GoalChanges.Where(x => x.PersonId == "BM1").First(x => x.Product == Product.DirectDeposit) + .Value); + Assert.AreEqual(25, + results.GoalChanges.Where(x => x.PersonId == "BM2").First(x => x.Product == Product.Mortgage).Value); + Assert.AreEqual(2, + results.GoalChanges.Where(x => x.PersonId == "BM2").First(x => x.Product == Product.Vehicle).Value); + Assert.AreEqual(3, + results.GoalChanges.Where(x => x.PersonId == "BM2").First(x => x.Product == Product.CheckCard).Value); + Assert.AreEqual(5, + results.GoalChanges.Where(x => x.PersonId == "BM2").First(x => x.Product == Product.DirectDeposit) + .Value); + + Assert.AreEqual(40, + results.GoalChanges.Where(x => x.PersonId == "PFO1").First(x => x.Product == Product.Mortgage).Value); + Assert.AreEqual(2, + results.GoalChanges.Where(x => x.PersonId == "PFO1").First(x => x.Product == Product.Vehicle).Value); + Assert.AreEqual(4, + results.GoalChanges.Where(x => x.PersonId == "PFO1").First(x => x.Product == Product.CheckCard).Value); + Assert.AreEqual(7, + results.GoalChanges.Where(x => x.PersonId == "PFO1").First(x => x.Product == Product.DirectDeposit) + .Value); + Assert.AreEqual(40, + results.GoalChanges.Where(x => x.PersonId == "PFO2").First(x => x.Product == Product.Mortgage).Value); + Assert.AreEqual(2, + results.GoalChanges.Where(x => x.PersonId == "PFO2").First(x => x.Product == Product.Vehicle).Value); + Assert.AreEqual(4, + results.GoalChanges.Where(x => x.PersonId == "PFO2").First(x => x.Product == Product.CheckCard).Value); + Assert.AreEqual(7, + results.GoalChanges.Where(x => x.PersonId == "PFO2").First(x => x.Product == Product.DirectDeposit) + .Value); + Assert.AreEqual(40, + results.GoalChanges.Where(x => x.PersonId == "PFO3").First(x => x.Product == Product.Mortgage).Value); + Assert.AreEqual(2, + results.GoalChanges.Where(x => x.PersonId == "PFO3").First(x => x.Product == Product.Vehicle).Value); + Assert.AreEqual(4, + results.GoalChanges.Where(x => x.PersonId == "PFO3").First(x => x.Product == Product.CheckCard).Value); + Assert.AreEqual(7, + results.GoalChanges.Where(x => x.PersonId == "PFO3").First(x => x.Product == Product.DirectDeposit) + .Value); + } + + [TestCase(10, 2, 3, "PFO")] + [TestCase(10, 1, 6, "PFO")] + [TestCase(10, 10, 1, "PFO")] + [TestCase(10, 2, 2, "BM")] + [TestCase(10, 1, 3, "BM")] + [TestCase(10, 10, 1, "BM")] + [TestCase(100, 2, 8, "ABM")] + [TestCase(100, 1, 15, "ABM")] + [TestCase(100, 10, 2, "ABM")] + public void CalculateDefaultWeightedValue_OK(int value, int numberOfUsersWithRole, int expect, + string userRoleName) + { + var userRole = Role.GetRolesList().Find(x => x.Name == userRoleName); + + List allRolesInDept = new List(); + + for (int i = 0; i < numberOfUsersWithRole; i++) + { + allRolesInDept.Add(userRole); + } + + Assert.AreEqual(expect, + _dispersalStrategyService.CalculateDefaultWeightedValue(userRole, value, allRolesInDept)); + } + + [TestCase(67, 81, 95, 79)] + [TestCase(35, 58, 8, 5)] + [TestCase(78, 95, 82, 68)] + [TestCase(53, 95, 9, 6)] + [TestCase(31, 78, 58, 24)] + [TestCase(6, 60, 80, 8)] + [TestCase(25, 62, 35, 15)] + [TestCase(96, 6, 61, 976)] + [TestCase(92, 39, 30, 71)] + [TestCase(24, 41, 73, 43)] + [TestCase(0, 100, 50, 0)] + [TestCase(100, 0, 50, 0)] + [TestCase(100, 100, 0, 0)] + [TestCase(1, 100000, 10, 1)] + public void ExtrapolateAndCreateEmployeeGoalChanges_OK(int modelPersonnelValue, int modelDeptValue, + int dispersalDeptValue, int expected) + { + var modelDeptGoals = new PerProductGoal() + { + ProductGoals = new Dictionary() + { + { Product.Vehicle, modelDeptValue }, + } + }; + + var dispersalDepartmentGoals = new PerProductGoal() + { + ProductGoals = new Dictionary() + { + { Product.Vehicle, dispersalDeptValue }, + } + }; + + var personnel = new List() + { + new AppUser() + { + EmployeeId = "ABM1", + Role = Role.ABM + }, + }; + + var modelPersonnelGoalList = new List() + { + new Goal() + { + Id = 10, + EmployeeId = "ABM1", + Product = Product.Vehicle, + Value = modelPersonnelValue, + StartDate = DateTime.Parse("2021-07-01").StartOfMonth(), + EndDate = DateTime.Parse("2021-07-01").EndOfMonth(), + } + }; + + var dispersalPersonnelGoalList = new List() + { + new Goal() + { + Id = 10, + EmployeeId = "ABM1", + Product = Product.Vehicle, + //set arbitrarily high so that newValue is never the same + Value = Int32.MaxValue, + StartDate = DateTime.Parse("2021-07-01").StartOfMonth(), + EndDate = DateTime.Parse("2021-07-01").EndOfMonth(), + } + }; + + + var modelPersonnelGoal = new PersonnelGoals(10, 2021, 07, personnel); + modelPersonnelGoal.ApplyAll(modelPersonnelGoalList); + + //These are all set arbitrarily high so that there is no situation where the newValue is the same as the current value + var dispersalMonthPersonnelGoal = new PersonnelGoals(10, 2021, 08, personnel); + dispersalMonthPersonnelGoal.ApplyAll(dispersalPersonnelGoalList); + + var extrapolationGoalDispersalData = new ExtrapolationGoalDispersalData() + { + DepartmentId = 10, + ModelDepartmentGoals = modelDeptGoals, + DispersalMonthDepartmentGoals = dispersalDepartmentGoals, + DispersalMonthPersonnelGoals = dispersalMonthPersonnelGoal, + ModelPersonnelGoals = modelPersonnelGoal, + DispersalMonthPersonnel = personnel, + DispersalMonthStartDate = DateTime.Parse("2021-08-1").StartOfMonth(), + }; + + var results = + _dispersalStrategyService.ExtrapolateFromExistingGoals(extrapolationGoalDispersalData); + + Assert.AreEqual(expected, + results.GoalChanges.FirstOrDefault(x => x.PersonId == "ABM1" && x.Product == Product.Vehicle).Value); + } + + //Employee1 = 10, Employee2 = 15, Employee3 = 7 + [TestCase(10, 100, 100, 150, 70)] + [TestCase(25, 50, 20, 30, 14)] + [TestCase(80, 40, 5, 8, 4)] + [TestCase(33, 57, 18, 26, 13)] + [TestCase(57, 33, 6, 9, 5)] + public void ExtrapolateSingleDepartmentProduct_OK(int oldDepartmentGoal, int newDepartmentGoal, int expect1, + int expect2, int expect3) + { + var appUsers = new List() + { + new AppUser() + { + Id = Guid.NewGuid(), + EmployeeId = "1" + }, + new AppUser() + { + Id = Guid.NewGuid(), + EmployeeId = "2" + }, + new AppUser() + { + Id = Guid.NewGuid(), + EmployeeId = "3" + } + }; + + var personnelGoals = new PersonnelGoals(5, DateTime.Today.Month, DateTime.Today.Year, appUsers); + + personnelGoals.ApplyAll(new List() + { + new Goal() + { + AppUserId = appUsers[0].Id, + EmployeeId = appUsers[0].EmployeeId, + DepartmentId = 5, + StartDate = DateTime.Now.StartOfMonth(), + EndDate = DateTime.Now.EndOfMonth(), + Product = Product.Mortgage, + Value = 10 + }, + new Goal() + { + AppUserId = appUsers[1].Id, + EmployeeId = appUsers[1].EmployeeId, + DepartmentId = 5, + StartDate = DateTime.Now.StartOfMonth(), + EndDate = DateTime.Now.EndOfMonth(), + Product = Product.Mortgage, + Value = 15 + }, + new Goal() + { + AppUserId = appUsers[2].Id, + EmployeeId = appUsers[2].EmployeeId, + DepartmentId = 5, + StartDate = DateTime.Now.StartOfMonth(), + EndDate = DateTime.Now.EndOfMonth(), + Product = Product.Mortgage, + Value = 7 + }, + new Goal() + { + AppUserId = appUsers[0].Id, + EmployeeId = appUsers[0].EmployeeId, + DepartmentId = 5, + StartDate = DateTime.Now.StartOfMonth(), + EndDate = DateTime.Now.EndOfMonth(), + Product = Product.Vehicle, + Value = 1 + }, + new Goal() + { + AppUserId = appUsers[1].Id, + EmployeeId = appUsers[1].EmployeeId, + DepartmentId = 5, + StartDate = DateTime.Now.StartOfMonth(), + EndDate = DateTime.Now.EndOfMonth(), + Product = Product.Vehicle, + Value = 1 + }, + new Goal() + { + AppUserId = appUsers[2].Id, + EmployeeId = appUsers[2].EmployeeId, + DepartmentId = 5, + StartDate = DateTime.Now.StartOfMonth(), + EndDate = DateTime.Now.EndOfMonth(), + Product = Product.Vehicle, + Value = 1 + } + }); + + var results = _dispersalStrategyService.ExtrapolateSingleDepartmentProduct( + new DepartmentGoalExtrapolationRequest() + { + DepartmentId = 5, + DepartmentGoalStartDate = DateTime.Today.StartOfMonth(), + DepartmentGoalEndDate = DateTime.Today.EndOfMonth(), + NewDepartmentGoal = newDepartmentGoal, + OriginalDepartmentGoal = oldDepartmentGoal, + Product = Product.Mortgage + }, personnelGoals); + + Assert.NotNull(results); + Assert.AreEqual(3, results.GoalChanges.Count()); + Assert.AreEqual(expect1, + results.GoalChanges.Single(x => x.PersonId == "1" && x.Product == Product.Mortgage).Value); + Assert.AreEqual(expect2, + results.GoalChanges.Single(x => x.PersonId == "2" && x.Product == Product.Mortgage).Value); + Assert.AreEqual(expect3, + results.GoalChanges.Single(x => x.PersonId == "3" && x.Product == Product.Mortgage).Value); + } + + [TestCase(10, 10)] + [TestCase(1000, 999)] + public void ExtrapolateSingleDepartmentProduct_NoChanges_OK(int oldDepartmentGoal, int newDepartmentGoal) + { + var personnelGoals = new PersonnelGoals(5, DateTime.Today.Month, DateTime.Today.Year, new List() + { + new AppUser() + { + EmployeeId = "1" + }, + new AppUser() + { + EmployeeId = "2" + }, + new AppUser() + { + EmployeeId = "3" + }, + }); + + personnelGoals.ApplyAll(new List() + { + new Goal() + { + EmployeeId = "1", + DepartmentId = 5, + StartDate = DateTime.Now.StartOfMonth(), + EndDate = DateTime.Now.EndOfMonth(), + Product = Product.Mortgage, + Value = 10 + }, + new Goal() + { + EmployeeId = "2", + DepartmentId = 5, + StartDate = DateTime.Now.StartOfMonth(), + EndDate = DateTime.Now.EndOfMonth(), + Product = Product.Mortgage, + Value = 15 + }, + new Goal() + { + EmployeeId = "3", + DepartmentId = 5, + StartDate = DateTime.Now.StartOfMonth(), + EndDate = DateTime.Now.EndOfMonth(), + Product = Product.Mortgage, + Value = 7 + }, + new Goal() + { + EmployeeId = "1", + DepartmentId = 5, + StartDate = DateTime.Now.StartOfMonth(), + EndDate = DateTime.Now.EndOfMonth(), + Product = Product.Vehicle, + Value = 1 + }, + new Goal() + { + EmployeeId = "2", + DepartmentId = 5, + StartDate = DateTime.Now.StartOfMonth(), + EndDate = DateTime.Now.EndOfMonth(), + Product = Product.Vehicle, + Value = 1 + }, + new Goal() + { + EmployeeId = "3", + DepartmentId = 5, + StartDate = DateTime.Now.StartOfMonth(), + EndDate = DateTime.Now.EndOfMonth(), + Product = Product.Vehicle, + Value = 1 + } + }); + + var results = _dispersalStrategyService.ExtrapolateSingleDepartmentProduct( + new DepartmentGoalExtrapolationRequest() + { + DepartmentId = 5, + DepartmentGoalStartDate = DateTime.Today.StartOfMonth(), + DepartmentGoalEndDate = DateTime.Today.EndOfMonth(), + NewDepartmentGoal = newDepartmentGoal, + OriginalDepartmentGoal = oldDepartmentGoal, + Product = Product.Mortgage + }, personnelGoals); + + Assert.NotNull(results); + Assert.AreEqual(0, results.GoalChanges.Count()); + } + + //This is here to prove behaviour, the business may want to change this later + [TestCase(100, 0)] + public void ExtrapolateSingleDepartmentProduct_NewValue0ClearsGoals_OK(int oldDepartmentGoal, + int newDepartmentGoal) + { + var personnelGoals = new PersonnelGoals(5, DateTime.Today.Month, DateTime.Today.Year, new List() + { + new AppUser() + { + EmployeeId = "1" + }, + new AppUser() + { + EmployeeId = "2" + }, + new AppUser() + { + EmployeeId = "3" + }, + }); + + personnelGoals.ApplyAll(new List() + { + new Goal() + { + EmployeeId = "1", + DepartmentId = 5, + StartDate = DateTime.Now.StartOfMonth(), + EndDate = DateTime.Now.EndOfMonth(), + Product = Product.Mortgage, + Value = 10 + }, + new Goal() + { + EmployeeId = "2", + DepartmentId = 5, + StartDate = DateTime.Now.StartOfMonth(), + EndDate = DateTime.Now.EndOfMonth(), + Product = Product.Mortgage, + Value = 15 + }, + new Goal() + { + EmployeeId = "3", + DepartmentId = 5, + StartDate = DateTime.Now.StartOfMonth(), + EndDate = DateTime.Now.EndOfMonth(), + Product = Product.Mortgage, + Value = 7 + }, + new Goal() + { + EmployeeId = "1", + DepartmentId = 5, + StartDate = DateTime.Now.StartOfMonth(), + EndDate = DateTime.Now.EndOfMonth(), + Product = Product.Vehicle, + Value = 1 + }, + new Goal() + { + EmployeeId = "2", + DepartmentId = 5, + StartDate = DateTime.Now.StartOfMonth(), + EndDate = DateTime.Now.EndOfMonth(), + Product = Product.Vehicle, + Value = 1 + }, + new Goal() + { + EmployeeId = "3", + DepartmentId = 5, + StartDate = DateTime.Now.StartOfMonth(), + EndDate = DateTime.Now.EndOfMonth(), + Product = Product.Vehicle, + Value = 1 + } + }); + + var results = _dispersalStrategyService.ExtrapolateSingleDepartmentProduct( + new DepartmentGoalExtrapolationRequest() + { + DepartmentId = 5, + DepartmentGoalStartDate = DateTime.Today.StartOfMonth(), + DepartmentGoalEndDate = DateTime.Today.EndOfMonth(), + NewDepartmentGoal = newDepartmentGoal, + OriginalDepartmentGoal = oldDepartmentGoal, + Product = Product.Mortgage + }, personnelGoals); + + Assert.NotNull(results); + Assert.AreEqual(3, results.GoalChanges.Count()); + Assert.True(results.GoalChanges.All(x => x.Value == 0)); + } + + [TestCase(0, 100)] + public void ExtrapolateSingleDepartmentProduct_OrigValue0CarriesOverExistingGoals_OK(int oldDepartmentGoal, + int newDepartmentGoal) + { + var personnelGoals = new PersonnelGoals(5, DateTime.Today.Month, DateTime.Today.Year, new List() + { + new AppUser() + { + EmployeeId = "1" + }, + new AppUser() + { + EmployeeId = "2" + }, + new AppUser() + { + EmployeeId = "3" + }, + }); + + personnelGoals.ApplyAll(new List() + { + new Goal() + { + EmployeeId = "1", + DepartmentId = 5, + StartDate = DateTime.Now.StartOfMonth(), + EndDate = DateTime.Now.EndOfMonth(), + Product = Product.Mortgage, + Value = 10 + }, + new Goal() + { + EmployeeId = "2", + DepartmentId = 5, + StartDate = DateTime.Now.StartOfMonth(), + EndDate = DateTime.Now.EndOfMonth(), + Product = Product.Mortgage, + Value = 15 + }, + new Goal() + { + EmployeeId = "3", + DepartmentId = 5, + StartDate = DateTime.Now.StartOfMonth(), + EndDate = DateTime.Now.EndOfMonth(), + Product = Product.Mortgage, + Value = 7 + }, + new Goal() + { + EmployeeId = "1", + DepartmentId = 5, + StartDate = DateTime.Now.StartOfMonth(), + EndDate = DateTime.Now.EndOfMonth(), + Product = Product.Vehicle, + Value = 1 + }, + new Goal() + { + EmployeeId = "2", + DepartmentId = 5, + StartDate = DateTime.Now.StartOfMonth(), + EndDate = DateTime.Now.EndOfMonth(), + Product = Product.Vehicle, + Value = 1 + }, + new Goal() + { + EmployeeId = "3", + DepartmentId = 5, + StartDate = DateTime.Now.StartOfMonth(), + EndDate = DateTime.Now.EndOfMonth(), + Product = Product.Vehicle, + Value = 1 + } + }); + + var results = _dispersalStrategyService.ExtrapolateSingleDepartmentProduct( + new DepartmentGoalExtrapolationRequest() + { + DepartmentId = 5, + DepartmentGoalStartDate = DateTime.Today.StartOfMonth(), + DepartmentGoalEndDate = DateTime.Today.EndOfMonth(), + NewDepartmentGoal = newDepartmentGoal, + OriginalDepartmentGoal = oldDepartmentGoal, + Product = Product.Mortgage + }, personnelGoals); + + Assert.NotNull(results); + Assert.AreEqual(0, results.GoalChanges.Count()); + } + + [Test] + public void CustomWeightedDispersal_OK_1() + { + var customWeightedDispersalRequest = new CustomWeightedDispersalRequest() + { + DepartmentId = 10, + month = 10, + year = 2021, + DispersalProduct = Product.Mortgage, + DispersalWeightByRole = new Dictionary() { { "PFO", .25 } } + }; + + var users = new List() + { + new AppUser() + { + EmployeeId = "ABM1", + PersonId = 1, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.ABM + } + } + }, + new AppUser() + { + EmployeeId = "BM1", + PersonId = 2, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.BM + } + } + }, + new AppUser() + { + EmployeeId = "BM2", + PersonId = 3, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.BM + } + } + }, + new AppUser() + { + EmployeeId = "PFO1", + PersonId = 4, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.PFO + } + } + }, + new AppUser() + { + EmployeeId = "PFO2", + PersonId = 5, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.PFO + } + } + }, + new AppUser() + { + EmployeeId = "PFO3", + PersonId = 6, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.PFO + } + } + } + }; + var receipt = _dispersalStrategyService.CustomWeightedDispersal(customWeightedDispersalRequest, users, 100); + + Assert.AreEqual(3, receipt.GoalChanges.Count()); + Assert.True(receipt.GoalChanges.TrueForAll(x => x.StartDate.Equals(DateTime.Parse("2021-10-01")))); + Assert.NotNull(receipt.GoalChanges.SingleOrDefault(x => x.PersonId == "PFO1")); + Assert.NotNull(receipt.GoalChanges.SingleOrDefault(x => x.PersonId == "PFO2")); + Assert.NotNull(receipt.GoalChanges.SingleOrDefault(x => x.PersonId == "PFO3")); + //25% of 100 -> 25 + //25/3 -> Ceil 8.33 -> 9 + Assert.True(receipt.GoalChanges.All(x => x.Value == 9 && x.Product == Product.Mortgage)); + } + + [Test] + public void CustomWeightedDispersal_OK_2() + { + var customWeightedDispersalRequest = new CustomWeightedDispersalRequest() + { + DepartmentId = 10, + month = 10, + year = 2021, + DispersalProduct = Product.Mortgage, + DispersalWeightByRole = new Dictionary() { { "BM", .10 } } + }; + + var users = new List() + { + new AppUser() + { + EmployeeId = "ABM1", + PersonId = 1, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.ABM + } + } + }, + new AppUser() + { + EmployeeId = "BM1", + PersonId = 2, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.BM + } + } + }, + new AppUser() + { + EmployeeId = "BM2", + PersonId = 3, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.BM + } + } + }, + new AppUser() + { + EmployeeId = "PFO1", + PersonId = 4, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.PFO + } + } + }, + new AppUser() + { + EmployeeId = "PFO2", + PersonId = 5, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.PFO + } + } + }, + new AppUser() + { + EmployeeId = "PFO3", + PersonId = 6, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.PFO + } + } + } + }; + var receipt = _dispersalStrategyService.CustomWeightedDispersal(customWeightedDispersalRequest, users, 100); + + Assert.AreEqual(2, receipt.GoalChanges.Count()); + Assert.NotNull(receipt.GoalChanges.SingleOrDefault(x => x.PersonId == "BM1")); + Assert.NotNull(receipt.GoalChanges.SingleOrDefault(x => x.PersonId == "BM2")); + + //10% of 100 -> 10 + //10/2 -> Ceil 5 -> 5 + Assert.True(receipt.GoalChanges.All(x => x.Value == 5 && x.Product == Product.Mortgage)); + } + + [Test] + public void CustomWeightedDispersal_OK_3() + { + var customWeightedDispersalRequest = new CustomWeightedDispersalRequest() + { + DepartmentId = 10, + month = 10, + year = 2021, + DispersalProduct = Product.Mortgage, + DispersalWeightByRole = new Dictionary() { { "BM", .10 }, { "PFO", .25 } } + }; + + var users = new List() + { + new AppUser() + { + EmployeeId = "ABM1", + PersonId = 1, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.ABM + } + } + }, + new AppUser() + { + EmployeeId = "BM1", + PersonId = 2, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.BM + } + } + }, + new AppUser() + { + EmployeeId = "BM2", + PersonId = 3, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.BM + } + } + }, + new AppUser() + { + EmployeeId = "PFO1", + PersonId = 4, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.PFO + } + } + }, + new AppUser() + { + EmployeeId = "PFO2", + PersonId = 5, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.PFO + } + } + }, + new AppUser() + { + EmployeeId = "PFO3", + PersonId = 6, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.PFO + } + } + } + }; + var receipt = _dispersalStrategyService.CustomWeightedDispersal(customWeightedDispersalRequest, users, 100); + + Assert.AreEqual(5, receipt.GoalChanges.Count()); + Assert.False(receipt.GoalChanges.Any(x => x.PersonId == "ABM1")); + Assert.AreEqual(2, receipt.GoalChanges.Count(x => x.Value == 5 && x.Product == Product.Mortgage)); + Assert.AreEqual(3, receipt.GoalChanges.Count(x => x.Value == 9 && x.Product == Product.Mortgage)); + } + + [Test] + public void CustomWeightedDispersal_RolesDontExist_OK() + { + var customWeightedDispersalRequest = new CustomWeightedDispersalRequest() + { + DepartmentId = 10, + month = 10, + year = 2021, + DispersalProduct = Product.Mortgage, + DispersalWeightByRole = new Dictionary() { { "BWAT", .10 }, { "BWOW", .25 } } + }; + + var users = new List() + { + new AppUser() + { + EmployeeId = "ABM1", + PersonId = 1, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.ABM + } + } + }, + new AppUser() + { + EmployeeId = "BM1", + PersonId = 2, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.BM + } + } + }, + new AppUser() + { + EmployeeId = "BM2", + PersonId = 3, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.BM + } + } + }, + new AppUser() + { + EmployeeId = "PFO1", + PersonId = 4, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.PFO + } + } + }, + new AppUser() + { + EmployeeId = "PFO2", + PersonId = 5, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.PFO + } + } + }, + new AppUser() + { + EmployeeId = "PFO3", + PersonId = 6, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.PFO + } + } + } + }; + var receipt = _dispersalStrategyService.CustomWeightedDispersal(customWeightedDispersalRequest, users, 100); + + Assert.AreEqual(0, receipt.GoalChanges.Count()); + } + + [Test] + public void CustomWeightedDispersal_NoRolesInDepartment_OK() + { + var customWeightedDispersalRequest = new CustomWeightedDispersalRequest() + { + DepartmentId = 23, + month = 10, + year = 2021, + DispersalProduct = Product.Mortgage, + DispersalWeightByRole = new Dictionary() + { { "PFO", .10 }, { "BM", .25 }, { "BOWOW", .50 } } + }; + + var users = new List() + { + new AppUser() + { + EmployeeId = "ABM1", + PersonId = 1, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.ABM + } + } + }, + new AppUser() + { + EmployeeId = "BM1", + PersonId = 2, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.BM + } + } + }, + new AppUser() + { + EmployeeId = "BM2", + PersonId = 3, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.BM + } + } + }, + new AppUser() + { + EmployeeId = "PFO1", + PersonId = 4, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.PFO + } + } + }, + new AppUser() + { + EmployeeId = "PFO2", + PersonId = 5, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.PFO + } + } + }, + new AppUser() + { + EmployeeId = "PFO3", + PersonId = 6, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.PFO + } + } + } + }; + var receipt = _dispersalStrategyService.CustomWeightedDispersal(customWeightedDispersalRequest, users, 100); + + Assert.AreEqual(0, receipt.GoalChanges.Count()); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DnaActivitySyncServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DnaActivitySyncServiceTest.cs new file mode 100644 index 0000000..c0609cd --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DnaActivitySyncServiceTest.cs @@ -0,0 +1,146 @@ +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision; +using LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using System.Collections.Generic; +using System; +using System.Threading.Tasks; + +namespace Application.UnitTests.Services +{ + public class DnaActivitySyncServiceTest + { + private Mock _mockActivityService; + private Mock _mockActivityDecisionService; + private Mock _mockExternalActivityservice; + private Mock _mockProcessorHistoryService; + private DnaActivitySyncService _dnaSyncService; + private Mock _mockDeadActivityService; + + // [SetUp] + // public void Setup() + // { + // _mockActivityService = new Mock(); + // _mockActivityDecisionService = new Mock(); + // _mockProcessorHistoryService = new Mock(); + // _mockExternalActivityservice = new Mock(); + // _mockDeadActivityService = new Mock(); + + // _dnaSyncService = new DnaActivitySyncService( + // _mockActivityService.Object, + // _mockProcessorHistoryService.Object, + // _mockActivityDecisionService.Object, + // _mockDeadActivityService.Object, + // _mockExternalActivityservice.Object); + // } + + // [Test] + // public async Task HandleIncomingActivityDecision_SaveDeadActivity_OK() + // { + // _mockActivityService.Setup(x => x.SaveActivityAsync(It.IsAny())) + // .ThrowsAsync(new DeadActivityException("Missing Employee ID")); + + // _mockDeadActivityService.Setup(x => x.SaveDeadActivity(It.IsAny())) + // .ReturnsAsync(new DeadActivity("6", SystemType.Dna, DeadActivityReason.Other, "Test") { Id = 5 }); + + // var testActivity = new Activity() + // { + // SourceId = "10", + // Detail = new ActivityDetail() + // { + // AccountNumber = "12345" + // }, + // EmployeeId = null, + // DepartmentId = -1, + // EmployeeEmail = "Test@test.com" + // }; + + // await _dnaSyncService.HandleIncomingActivityDecision(testActivity, Product.NewChecking, ActivityType.Sale); + + // _mockDeadActivityService.Verify(x => x.SaveDeadActivity(It.Is(x => + // x.Reason == DeadActivityReason.MissingRequiredField && + // x.ReasonNote == "Missing Employee ID" && + // x.SourceId == "10" && + // x.DeadDetail.AccountNumber == "12345" && + // x.EmployeeId == null && + // x.DepartmentId == null && + // x.EmployeeEmail == "Test@test.com" + // )), Times.Once); + // } + + // [Test] + // public async Task HandleMapExternalActivities_OK() + // { + + // var activities = + // new List() + // { + // new NewLoanSaleActivity() + // { + // ActivityNbr = 5, + // MinorAccountType = "DV01" + // }, + // new NewLoanSaleActivity() + // { + // ActivityNbr = 6, + // MinorAccountType = "DV01" + // }, + // new NewLoanSaleActivity() + // { + // ActivityNbr = 7, + // MinorAccountType = "DV01" + // } + // }; + + // var result = await _dnaSyncService.HandleMapExternalActivities(activities, Product.Vehicle, ActivityType.Sale); + + // Assert.NotNull(result); + // } + + // [Test] + // public async Task UpdateProcessorHistory_Ok() + // { + // var testProcess = new ProcessorHistoryRecord() + // { + // Process = Process.ImportAtmCheckCardSalesFromDna, + // LastProcessedRecordId = 200, + // LastProcessedRecordTimestamp = DateTime.Parse("2021-07-10"), + // LastSuccessfulRunTime = DateTime.Parse("2021-07-10") + // }; + + // _mockProcessorHistoryService.Setup(x => x.GetProcessorHistory(It.IsAny())).ReturnsAsync(testProcess).Verifiable(); + + // await _dnaSyncService.UpdateProcessorHistory(Process.ImportAtmCheckCardSalesFromDna, 200, DateTime.Parse("2021-07-10")); + + // _mockProcessorHistoryService.Verify(mock => mock.SaveProcessorHistory(It.IsAny()), Times.Once); + // } + + // [Test] + // public async Task RunLatestDnaActivitySales_No_New_Activities() + // { + // _mockProcessorHistoryService.Setup(x => x.GetProcessorHistory(It.IsAny())).ReturnsAsync( + // new ProcessorHistoryRecord() + // { + // Process = Process.ImportAtmCheckCardSalesFromDna, + // LastProcessedRecordId = 200, + // LastProcessedRecordTimestamp = DateTime.Parse("2021-07-10"), + // LastSuccessfulRunTime = DateTime.Parse("2021-07-10") + // }).Verifiable(); + + // _mockActivityService.Setup( + // x => x.FilterExternalActivitiesByExistingSourceId(It.IsAny>())).ReturnsAsync(new List() { }); + + // var result = await _dnaSyncService.GetLastActivityProcessedData(Process.ImportAtmCheckCardSalesFromDna); + // Assert.NotNull(result); + + // _mockProcessorHistoryService.Verify(x => x.GetProcessorHistory(It.Is(process => process == Process.ImportAtmCheckCardSalesFromDna)), Times.Once); + // _mockProcessorHistoryService.Verify(x => x.GetProcessorHistory(It.IsAny()), Times.Once); + // _mockProcessorHistoryService.Verify(mock => mock.SaveProcessorHistory(It.IsAny()), Times.Never); + // } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DnaServiceTests.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DnaServiceTests.cs new file mode 100644 index 0000000..3de26b9 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DnaServiceTests.cs @@ -0,0 +1,299 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Accounts; +using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Activity; +using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Transaction; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; + +namespace Application.UnitTests.Services +{ + public class DnaServiceTests + { + private IDnaService _dnaService; + private Mock _mockCoreApiWrapperService; + + [SetUp] + public void SetUp() + { + _mockCoreApiWrapperService = new Mock(); + _dnaService = new DnaService(_mockCoreApiWrapperService.Object); + } + + [Test] + public async Task GetNewAccountDetailsForActivities_OK() + { + _mockCoreApiWrapperService.Setup(x => x.GetUpdatedAccountInformation(It.IsAny>())).ReturnsAsync( + new List() + { + new ActivityAccountDetails() + { + AccountNumber = 1 + }, + new ActivityAccountDetails() + { + AccountNumber = 2 + }, + new ActivityAccountDetails() + { + AccountNumber = 3 + } + }); + + var results = await _dnaService.GetNewAccountDetailsForActivities(new List() + { + new Activity() + { + Id = 1, + Detail = new ActivityDetail() + { + AccountNumber = "1" + } + }, + new Activity() + { + Id = 2, + Detail = new ActivityDetail() + { + AccountNumber = "2" + } + }, + new Activity() + { + Id = 3, + Detail = new ActivityDetail() + { + AccountNumber = "3" + } + } + }); + + Assert.NotNull(results); + Assert.AreEqual(3, results.Count); + + _mockCoreApiWrapperService.Verify(x => + x.GetUpdatedAccountInformation(It.Is>(x => + x.Contains(1) && x.Contains(2) && x.Contains(3)))); + + } + + [Test] + public async Task GetNewAccountDetailsForActivities_OnlyKnown_OK() + { + _mockCoreApiWrapperService.Setup(x => x.GetUpdatedAccountInformation(It.IsAny>())).ReturnsAsync( + new List() + { + new ActivityAccountDetails() + { + AccountNumber = 1 + }, + new ActivityAccountDetails() + { + AccountNumber = 3 + } + }); + + var results = await _dnaService.GetNewAccountDetailsForActivities(new List() + { + new Activity() + { + Id = 1, + Detail = new ActivityDetail() + { + AccountNumber = "1" + } + }, + //Null Account Number + new Activity() + { + Id = 2, + Detail = new ActivityDetail() + { + } + }, + new Activity() + { + Id = 3, + Detail = new ActivityDetail() + { + AccountNumber = "3" + } + } + }); + + Assert.NotNull(results); + Assert.AreEqual(2, results.Count); + + _mockCoreApiWrapperService.Verify(x => + x.GetUpdatedAccountInformation(It.Is>(x => + x.Contains(1) && x.Contains(3)))); + + } + + [Test] + public async Task GetNewAccountDetailsForActivities_BadAccountNumber_OK() + { + _mockCoreApiWrapperService.Setup(x => x.GetUpdatedAccountInformation(It.IsAny>())).ReturnsAsync( + new List() + { + new ActivityAccountDetails() + { + AccountNumber = 1 + }, + new ActivityAccountDetails() + { + AccountNumber = 3 + } + }); + + var results = await _dnaService.GetNewAccountDetailsForActivities(new List() + { + new Activity() + { + Id = 1, + Detail = new ActivityDetail() + { + AccountNumber = "ADJFALKDSFJA" + } + }, + //Null Account Number + new Activity() + { + Id = 2, + Detail = new ActivityDetail() + { + AccountNumber = "1" + } + }, + new Activity() + { + Id = 3, + Detail = new ActivityDetail() + { + AccountNumber = "12aDFKADFSF" + } + } + }); + + Assert.NotNull(results); + Assert.AreEqual(2, results.Count); + + _mockCoreApiWrapperService.Verify(x => + x.GetUpdatedAccountInformation(It.Is>(x => + x.Contains(1)))); + + } + + [Test] + public async Task QueryActivityManagerForActivities_OK() + { + _mockCoreApiWrapperService.Setup(x => x.GetActivityManagerActivities(It.IsAny())).ReturnsAsync( + new List() + { + new NewActivityManagerReferral() + { + ActivityId = 1 + }, + new NewActivityManagerReferral() + { + ActivityId = 2 + }, + new NewActivityManagerReferral() + { + ActivityId = 3 + } + }); + + var results = await _dnaService.QueryActivityManagerForActivities(new List() + { + "1","2","3" + }); + + Assert.NotNull(results); + Assert.AreEqual(3, results.Count); + + _mockCoreApiWrapperService.Verify(x => + x.GetActivityManagerActivities(It.Is(x => + x.ActivityIds.Contains(1) && x.ActivityIds.Contains(2) && x.ActivityIds.Contains(3)))); + + } + + [Test] + public async Task QueryActivityManagerForActivities_Null_OK() + { + _mockCoreApiWrapperService.Setup(x => x.GetActivityManagerActivities(It.IsAny())).ReturnsAsync( + new List() + { + new NewActivityManagerReferral() + { + ActivityId = 1 + }, + new NewActivityManagerReferral() + { + ActivityId = 3 + } + }); + + var results = await _dnaService.QueryActivityManagerForActivities(new List() + { + "1",null,"3" + }); + + Assert.NotNull(results); + Assert.AreEqual(2, results.Count); + + _mockCoreApiWrapperService.Verify(x => + x.GetActivityManagerActivities(It.Is(x => + x.ActivityIds.Contains(1) && x.ActivityIds.Contains(3)))); + } + + [Test] + public async Task GetTransactionHistory_OK() + { + var accountNumber = "123456"; + var startDate = DateTime.Now.AddDays(-1); + var endDate = DateTime.Now; + var transactionCodes = new List { "XDEP" }; + + _mockCoreApiWrapperService.Setup(x => x.GetTransactionHistory(accountNumber, startDate, endDate, transactionCodes)).ReturnsAsync( + new List() + { + new TransactionHistory() + { + Id = "1" + }, + new TransactionHistory() + { + Id = "2" + } + }.AsReadOnly).Verifiable(); + + var results = await _dnaService.GetTransactionHistory(accountNumber, startDate, endDate, transactionCodes); + + _mockCoreApiWrapperService.Verify(); + Assert.NotNull(results); + Assert.AreEqual(2, results.Count); + } + + [Test] + public async Task GetTransactionHistory_Null() + { + var accountNumber = "123456"; + var startDate = DateTime.Now.AddDays(-1); + var endDate = DateTime.Now; + var transactionCodes = new List { "XDEP" }; + + _mockCoreApiWrapperService.Setup(x => x.GetTransactionHistory(accountNumber, startDate, endDate, transactionCodes)).ReturnsAsync((IReadOnlyCollection)null).Verifiable(); + + var results = await _dnaService.GetTransactionHistory(accountNumber, startDate, endDate, transactionCodes); + + _mockCoreApiWrapperService.Verify(); + Assert.Null(results); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DuplicateDetection/DefaultDuplicateDetectionStrategyTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DuplicateDetection/DefaultDuplicateDetectionStrategyTest.cs new file mode 100644 index 0000000..f9c8b20 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DuplicateDetection/DefaultDuplicateDetectionStrategyTest.cs @@ -0,0 +1,33 @@ +using LCU.API.Internal.Operations.IncentiveApi.Services.DuplicateDetection; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Moq; +using NUnit.Framework; + +namespace Application.UnitTests.Services.DuplicateDetection +{ + public class DefaultDuplicateDetectionStrategyTest + { + private IActivityRepository _activityRepository; + private DefaultDuplicateDetectionStrategy _duplicateDetectionStrategy; + + [SetUp] + public void Setup() + { + _activityRepository = Mock.Of(); + _duplicateDetectionStrategy = new DefaultDuplicateDetectionStrategy(_activityRepository); + } + + [Test] + public void CanDecide() + { + var activity = new Activity(); // defaults to Sale + Assert.True(_duplicateDetectionStrategy.CanDecideActivity(activity)); + activity.Type = ActivityType.Sale; + Assert.True(_duplicateDetectionStrategy.CanDecideActivity(activity)); + activity.Type = ActivityType.Other; + Assert.True(_duplicateDetectionStrategy.CanDecideActivity(activity)); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DuplicateDetection/DuplicateDetectionServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DuplicateDetection/DuplicateDetectionServiceTest.cs new file mode 100644 index 0000000..41dae2e --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DuplicateDetection/DuplicateDetectionServiceTest.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.API.Internal.Operations.IncentiveApi.Services.DuplicateDetection; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Moq; +using NUnit.Framework; + +namespace Application.UnitTests.Services.DuplicateDetection +{ + public class DuplicateDetectionServiceTest + { + private IDuplicateDetectionService _service; + IActivityRepository _activityRepository; + private IDuplicateDetectionStrategy _strategy; + + [SetUp] + public void Setup() + { + _activityRepository = Mock.Of(); + _strategy = Mock.Of(); + var strategies = new List() + { + _strategy + }; + _service = new DuplicateDetectionService(strategies, _activityRepository); + } + + [Test] + public async Task AllTypesCoveredOnce() + { + var activity = new Activity() + { + Id = 1, + Type = ActivityType.Other, + Product = Product.Mortgage, + State = ActivityState.Accepted + }; + var duplicates = new List() { activity }; + Mock.Get(_strategy).Setup(x => x.CanDecideActivity(It.IsAny())).Returns(true); + Mock.Get(_strategy).Setup(x => x.FindDuplicates(It.IsAny())).ReturnsAsync(duplicates); + + var results = await _service.TryFindDuplicates(activity); + Assert.NotNull(results); + Assert.IsNotEmpty(results); + + Mock.Get(_strategy).Verify(x => x.CanDecideActivity(It.IsAny()), Times.Once); + Mock.Get(_strategy).Verify(x => x.FindDuplicates(It.IsAny()), Times.Once); + } + + [Test] + public void ThrowsWithoutStrategy() + { + var activity = new Activity() + { + Id = 1, + Type = ActivityType.Other, + Product = Product.Mortgage, + State = ActivityState.Accepted + }; + var strategies = new List(); + var service = new DuplicateDetectionService(strategies, _activityRepository); + Assert.ThrowsAsync(async () => + { + await service.TryFindDuplicates(activity); + }); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DuplicateDetection/ReferralDuplicateDetectionStrategyTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DuplicateDetection/ReferralDuplicateDetectionStrategyTest.cs new file mode 100644 index 0000000..89e149e --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/DuplicateDetection/ReferralDuplicateDetectionStrategyTest.cs @@ -0,0 +1,35 @@ +using LCU.API.Internal.Operations.IncentiveApi.Services.DuplicateDetection; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Moq; +using NUnit.Framework; + +namespace Application.UnitTests.Services.DuplicateDetection +{ + public class ReferralDuplicateDetectionStrategyTest + { + IActivityRepository _activityRepository; + ReferralDuplicateDetectionStrategy _duplicateDetectionStrategy; + + [SetUp] + public void Setup() + { + _activityRepository = Mock.Of(); + _duplicateDetectionStrategy = new ReferralDuplicateDetectionStrategy(_activityRepository); + } + + [Test] + public void CanDecide() + { + var activity = new Activity(); + Assert.False(_duplicateDetectionStrategy.CanDecideActivity(activity)); + activity.Type = ActivityType.Sale; + Assert.False(_duplicateDetectionStrategy.CanDecideActivity(activity)); + activity.Type = ActivityType.Other; + Assert.False(_duplicateDetectionStrategy.CanDecideActivity(activity)); + activity.Type = ActivityType.Referral; + Assert.True(_duplicateDetectionStrategy.CanDecideActivity(activity)); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/EStatementServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/EStatementServiceTest.cs new file mode 100644 index 0000000..4b5da55 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/EStatementServiceTest.cs @@ -0,0 +1,600 @@ +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Moq; +using NUnit.Framework; +using System.Linq; +using System.Threading.Tasks; +using System.Collections.Generic; +using System; +using Microsoft.EntityFrameworkCore; + +namespace Application.UnitTests.Services +{ + public class EStatementServiceTest + { + private EStatementService _referralService; + private IActivityRepository _activityRepository; + private IUnitOfWork _unitOfWork; + private Mock _mockProcessorHistoryService; + private Mock _mockCoreApiWrapperService; + private Mock _mockActivityService; + + [SetUp] + public void Setup() + { + var mockDateTime = new DateTimeService(); + var mockCurrentUserService = Mock.Of(); + + Mock.Get(mockCurrentUserService).Setup(s => s.GetCurrentUser()).Returns(new ApplicationUser() + { + Email = "JohnTest@test.com", + }); + + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .EnableSensitiveDataLogging(true) + .Options; + var context = new ApplicationDbContext(options, mockCurrentUserService, mockDateTime); + + _activityRepository = new ActivityRepository(context); + _unitOfWork = new UnitOfWork(context); + + _mockProcessorHistoryService = new Mock(); + _mockCoreApiWrapperService = new Mock(); + _mockActivityService = new Mock(); + + _referralService = + new EStatementService(_mockProcessorHistoryService.Object, _mockCoreApiWrapperService.Object, + _mockActivityService.Object, _activityRepository); + } + + [TestCase("1111", null)] + [TestCase(null, "1111")] + [Test] + public async Task MatchOrExpireEstatementReferral_MatchOnPersonOrOrgNumber_MatchNoExpire(string supjectPersonNbr, string subjectOrgNbr) + { + // Delivery methods activity dates must precede referral source created date + var deliveryMethodDaysInPast = -2; + var referralDaysInPast = -5; + + var matchedEStatementReferral = new Activity() + { + Product = Product.eStatement, + EmployeeId = "1", + SourceId = "2", + Type = ActivityType.Referral, + State = ActivityState.PendingReferral, + SourceCreated = DateTime.Now.AddDays(referralDaysInPast), + Detail = new ActivityDetail + { + AccountNumber = null, + SubjectPersonNbr = supjectPersonNbr, + SubjectOrgNbr = subjectOrgNbr + }, + MetaData = new List { } + }; + + matchedEStatementReferral.Id = await _activityRepository.SaveActivityAsync(matchedEStatementReferral); + await _unitOfWork.CompleteAsync(); + + var lastRun = new ProcessorHistoryRecord() { }; + _mockProcessorHistoryService.Setup(mock => mock.GetProcessorHistory(It.IsAny())).ReturnsAsync(lastRun); + + var matchedDeliveryMethod = new EStatementDeliveryMethod() + { + ActivityNumber = 1, + AccountNumber = 11111, + PersonNumber = supjectPersonNbr, + OrganizationNumber = subjectOrgNbr ?? "0", + ActivityDateTime = DateTime.Now.AddDays(deliveryMethodDaysInPast), + PhoneNumbers = "0987654321", + EmailAddresses = "mail@mail.com" + }; + + var methodWithNonMatchingNumbers = new EStatementDeliveryMethod() + { + ActivityNumber = 2, + AccountNumber = 22222, + PersonNumber = "1234", + OrganizationNumber = "0", + ActivityDateTime = DateTime.Now.AddDays(deliveryMethodDaysInPast), + PhoneNumbers = null, + EmailAddresses = null + }; + + var methodWithMetadata = new EStatementDeliveryMethod() + { + ActivityNumber = 3, + AccountNumber = 33333, + PersonNumber = "5678", + OrganizationNumber = "0", + ActivityDateTime = DateTime.Now.AddDays(deliveryMethodDaysInPast), + PhoneNumbers = "2629999999,2622344555,2626788888,2624324444", + EmailAddresses = "Testy1@Test.com,testy@test.com" + }; + + var eStatmentActivities = new List() { matchedDeliveryMethod, methodWithNonMatchingNumbers, methodWithMetadata }; + _mockCoreApiWrapperService.Setup(mock => mock.GetEStatmentDeliveryMethods(It.IsAny())).ReturnsAsync(eStatmentActivities); + var results = await _referralService.UpdateOrExpireEStatementReferral(); + + _mockProcessorHistoryService.Verify(mock => mock.SaveProcessorHistory(It.IsAny()), Times.Once); + + Assert.IsNotNull(results); + Assert.IsTrue(results.Count == 1); + Assert.IsTrue(results.First().Note.Contains(matchedDeliveryMethod.AccountNumber.ToString())); + Assert.AreEqual(matchedDeliveryMethod.AccountNumber.ToString(), matchedEStatementReferral.Detail.AccountNumber); + Assert.AreEqual(matchedEStatementReferral.Id, results.First().ActivityId); + Assert.AreEqual(ActivityState.Accepted, results.First().State); + } + + [Test] + public async Task MatchOrExpireEstatementReferral_MatchOnPhoneMetaData_MatchNoExpire() + { + // Delivery methods activity dates must precede referral source created date + var deliveryMethodDaysInPast = -2; + var referralDaysInPast = -5; + + var matchedEStatementReferral = new Activity() + { + Product = Product.eStatement, + EmployeeId = "1", + SourceId = "2", + Type = ActivityType.Referral, + State = ActivityState.PendingReferral, + SourceCreated = DateTime.Now.AddDays(referralDaysInPast), + Detail = new ActivityDetail + { + AccountNumber = null, + SubjectPersonNbr = null, + SubjectOrgNbr = null + }, + MetaData = new List { + new ActivityMetaData { + MetaData = "1234567890", + MetaDataType = MetaDataType.Phone + } + } + }; + + matchedEStatementReferral.Id = await _activityRepository.SaveActivityAsync(matchedEStatementReferral); + await _unitOfWork.CompleteAsync(); + + var lastRun = new ProcessorHistoryRecord() { }; + _mockProcessorHistoryService.Setup(mock => mock.GetProcessorHistory(It.IsAny())).ReturnsAsync(lastRun); + + var matchedDeliveryMethod = new EStatementDeliveryMethod() + { + ActivityNumber = 1, + AccountNumber = 11111, + PersonNumber = "1234", + OrganizationNumber = "0", + ActivityDateTime = DateTime.Now.AddDays(deliveryMethodDaysInPast), + PhoneNumbers = "1234567890,0987654321", // Additional phone number should be added to referral activity + EmailAddresses = "mail@mail.com" // This email address should be added to the referral activity + }; + + var methodWithNonMatchingMetadata = new EStatementDeliveryMethod() + { + ActivityNumber = 2, + AccountNumber = 22222, + PersonNumber = "5678", + OrganizationNumber = "0", + ActivityDateTime = DateTime.Now.AddDays(deliveryMethodDaysInPast), + PhoneNumbers = "0987654321", + EmailAddresses = "mail@mail.com, mail2@mail.com" + }; + + var methodWithoutMetadata = new EStatementDeliveryMethod() + { + ActivityNumber = 3, + AccountNumber = 33333, + PersonNumber = "9134", + OrganizationNumber = "0", + ActivityDateTime = DateTime.Now.AddDays(deliveryMethodDaysInPast), + PhoneNumbers = null, + EmailAddresses = null + }; + + var eStatmentActivities = new List() { matchedDeliveryMethod, methodWithNonMatchingMetadata, methodWithoutMetadata }; + _mockCoreApiWrapperService.Setup(mock => mock.GetEStatmentDeliveryMethods(It.IsAny())).ReturnsAsync(eStatmentActivities); + var results = await _referralService.UpdateOrExpireEStatementReferral(); + + _mockProcessorHistoryService.Verify(mock => mock.SaveProcessorHistory(It.IsAny()), Times.Once); + + Assert.IsNotNull(results); + Assert.IsTrue(results.Count == 1); + Assert.IsTrue(results.First().Note.Contains(matchedDeliveryMethod.AccountNumber.ToString())); + Assert.AreEqual(matchedEStatementReferral.Id, results.First().ActivityId); + Assert.AreEqual(ActivityState.Accepted, results.First().State); + Assert.AreEqual(2, matchedEStatementReferral.MetaData.Where(x => x.MetaDataType == MetaDataType.Phone).Count()); // Added one new phone number + Assert.AreEqual(1, matchedEStatementReferral.MetaData.Where(x => x.MetaDataType == MetaDataType.Email).Count()); // Added one new email address + } + + [Test] + public async Task MatchOrExpireReferralsWithoutSales_MatchOnEmailMetaData_MatchNoExpire() + { + // Delivery methods activity dates must precede referral source created date + var deliveryMethodDaysInPast = -2; + var referralDaysInPast = -5; + + var matchedEStatementReferral = new Activity() + { + Product = Product.eStatement, + EmployeeId = "1", + SourceId = "2", + Type = ActivityType.Referral, + State = ActivityState.PendingReferral, + SourceCreated = DateTime.Now.AddDays(referralDaysInPast), + Detail = new ActivityDetail + { + AccountNumber = null, + SubjectPersonNbr = null, + SubjectOrgNbr = null + }, + MetaData = new List { + new ActivityMetaData { + MetaData = "mail@mail.com", + MetaDataType = MetaDataType.Email + } + } + }; + + matchedEStatementReferral.Id = await _activityRepository.SaveActivityAsync(matchedEStatementReferral); + await _unitOfWork.CompleteAsync(); + + var lastRun = new ProcessorHistoryRecord() { }; + _mockProcessorHistoryService.Setup(mock => mock.GetProcessorHistory(It.IsAny())).ReturnsAsync(lastRun); + + var matchedDeliveryMethod = new EStatementDeliveryMethod() + { + ActivityNumber = 1, + AccountNumber = 11111, + PersonNumber = "1234", + OrganizationNumber = "0", + ActivityDateTime = DateTime.Now.AddDays(deliveryMethodDaysInPast), + PhoneNumbers = "1234567890", // This phone number should be added to referral activity + EmailAddresses = "mail@mail.com, mail2@mail.com" // Additional email address should be added to the referral activity + }; + + var methodWithNonMatchingMetadata = new EStatementDeliveryMethod() + { + ActivityNumber = 2, + AccountNumber = 22222, + PersonNumber = "5678", + OrganizationNumber = "0", + ActivityDateTime = DateTime.Now.AddDays(deliveryMethodDaysInPast), + PhoneNumbers = "0987654321", + EmailAddresses = "mail3@mail.com, mail4@mail.com" + }; + + var methodWithoutMetadata = new EStatementDeliveryMethod() + { + ActivityNumber = 3, + AccountNumber = 33333, + PersonNumber = "9123", + OrganizationNumber = "0", + ActivityDateTime = DateTime.Now.AddDays(deliveryMethodDaysInPast), + PhoneNumbers = null, + EmailAddresses = null + }; + + var eStatmentActivities = new List() { matchedDeliveryMethod, methodWithNonMatchingMetadata, methodWithoutMetadata }; + _mockCoreApiWrapperService.Setup(mock => mock.GetEStatmentDeliveryMethods(It.IsAny())).ReturnsAsync(eStatmentActivities); + var results = await _referralService.UpdateOrExpireEStatementReferral(); + + _mockProcessorHistoryService.Verify(mock => mock.SaveProcessorHistory(It.IsAny()), Times.Once); + + Assert.IsNotNull(results); + Assert.IsTrue(results.Count == 1); + Assert.IsTrue(results.First().Note.Contains(matchedDeliveryMethod.AccountNumber.ToString())); + Assert.AreEqual(matchedEStatementReferral.Id, results.First().ActivityId); + Assert.AreEqual(ActivityState.Accepted, results.First().State); + Assert.AreEqual(1, matchedEStatementReferral.MetaData.Where(x => x.MetaDataType == MetaDataType.Phone).Count()); // Added one new phone number + Assert.AreEqual(2, matchedEStatementReferral.MetaData.Where(x => x.MetaDataType == MetaDataType.Email).Count()); // Added one new email address + } + + [Test] + public async Task MatchOrExpireEstatementReferral_MultipleDeliveryMethodsMatch_OneMatchNoExpire() + { + // Delivery methods activity dates must precede referral source created date + var deliveryMethodDaysInPast = -2; + var latestDeliveryMethodDaysInPast = -1; + var referralDaysInPast = -5; + var referralMatchingPhoneNumber = "1234567890"; + + var matchedEStatementReferral = new Activity() + { + Product = Product.eStatement, + EmployeeId = "1", + SourceId = "2", + Type = ActivityType.Referral, + State = ActivityState.PendingReferral, + SourceCreated = DateTime.Now.AddDays(referralDaysInPast), + Detail = new ActivityDetail + { + AccountNumber = null, + SubjectPersonNbr = null, + SubjectOrgNbr = null + }, + MetaData = new List { + new ActivityMetaData { + MetaData = referralMatchingPhoneNumber, + MetaDataType = MetaDataType.Phone + } + } + }; + + matchedEStatementReferral.Id = await _activityRepository.SaveActivityAsync(matchedEStatementReferral); + await _unitOfWork.CompleteAsync(); + + var lastRun = new ProcessorHistoryRecord() { }; + _mockProcessorHistoryService.Setup(mock => mock.GetProcessorHistory(It.IsAny())).ReturnsAsync(lastRun); + + var matchedDeliveryMethod = new EStatementDeliveryMethod() + { + ActivityNumber = 1, + AccountNumber = 11111, + PersonNumber = "1234", + OrganizationNumber = "0", + ActivityDateTime = DateTime.Now.AddDays(latestDeliveryMethodDaysInPast), + PhoneNumbers = referralMatchingPhoneNumber, + EmailAddresses = "mail@mail.com" // This email address should be added to the referral activity + }; + + var methodWithNonMatchingMetadata = new EStatementDeliveryMethod() + { + ActivityNumber = 2, + AccountNumber = 22222, + PersonNumber = "5678", + OrganizationNumber = "0", + ActivityDateTime = DateTime.Now.AddDays(deliveryMethodDaysInPast), + PhoneNumbers = referralMatchingPhoneNumber, + EmailAddresses = "mail@mail.com, mail2@mail.com" + }; + + var methodWithoutMetadata = new EStatementDeliveryMethod() + { + ActivityNumber = 3, + AccountNumber = 33333, + PersonNumber = "9123", + OrganizationNumber = "0", + ActivityDateTime = DateTime.Now.AddDays(deliveryMethodDaysInPast), + PhoneNumbers = referralMatchingPhoneNumber, + EmailAddresses = null + }; + + var eStatmentActivities = new List() { matchedDeliveryMethod, methodWithNonMatchingMetadata, methodWithoutMetadata }; + _mockCoreApiWrapperService.Setup(mock => mock.GetEStatmentDeliveryMethods(It.IsAny())).ReturnsAsync(eStatmentActivities); + var results = await _referralService.UpdateOrExpireEStatementReferral(); + + _mockProcessorHistoryService.Verify(mock => mock.SaveProcessorHistory(It.IsAny()), Times.Once); + + Assert.IsNotNull(results); + Assert.IsTrue(results.Count == 1); + Assert.IsTrue(results.First().Note.Contains(matchedDeliveryMethod.AccountNumber.ToString())); + Assert.AreEqual(matchedEStatementReferral.Id, results.First().ActivityId); + Assert.AreEqual(ActivityState.Accepted, results.First().State); + Assert.AreEqual(1, matchedEStatementReferral.MetaData.Where(x => x.MetaDataType == MetaDataType.Phone).Count()); // Did not add any phone numbers + Assert.AreEqual(1, matchedEStatementReferral.MetaData.Where(x => x.MetaDataType == MetaDataType.Email).Count()); // Added one new email address + } + + [Test] + public async Task MatchOrExpireEstatementReferral_MatchMultipleOnPersNumberAndMetaData_OneMatchNoExpire() + { + // Delivery methods activity dates must precede referral source created date + var deliveryMethodDaysInPast = -2; + var firstMatchedDeliveryMethodDaysInPast = -2; + var latestDeliveryMethodDaysInPast = -1; // Method with this date will match because it is newest + var referralDaysInPast = -5; + + var referralMatchingPersNumber = "1111"; + var referralMatchingPhoneNumber = "1234567890"; + var referralMatchingEmail = "mail@mail.com"; + + var matchedEStatementReferral = new Activity() + { + Product = Product.eStatement, + EmployeeId = "1", + SourceId = "2", + Type = ActivityType.Referral, + State = ActivityState.PendingReferral, + SourceCreated = DateTime.Now.AddDays(referralDaysInPast), + Detail = new ActivityDetail + { + AccountNumber = null, + SubjectPersonNbr = referralMatchingPersNumber, + SubjectOrgNbr = null + }, + MetaData = new List { + new ActivityMetaData { + MetaData = referralMatchingEmail, + MetaDataType = MetaDataType.Email + } + } + }; + + matchedEStatementReferral.Id = await _activityRepository.SaveActivityAsync(matchedEStatementReferral); + await _unitOfWork.CompleteAsync(); + + var lastRun = new ProcessorHistoryRecord() { }; + _mockProcessorHistoryService.Setup(mock => mock.GetProcessorHistory(It.IsAny())).ReturnsAsync(lastRun); + + // Will match on email MetaData + var matchedOnEmailDeliveryMethod = new EStatementDeliveryMethod() + { + ActivityNumber = 1, + AccountNumber = 11111, + PersonNumber = "1234", + OrganizationNumber = "0", + ActivityDateTime = DateTime.Now.AddDays(latestDeliveryMethodDaysInPast), + PhoneNumbers = referralMatchingPhoneNumber, + EmailAddresses = referralMatchingEmail + }; + + // Will match on PersonNumber + var matchedOnPersonNumberButNotLatest = new EStatementDeliveryMethod() + { + ActivityNumber = 2, + AccountNumber = 22222, + PersonNumber = referralMatchingPersNumber, + OrganizationNumber = "0", + ActivityDateTime = DateTime.Now.AddDays(firstMatchedDeliveryMethodDaysInPast), + PhoneNumbers = referralMatchingPhoneNumber, + EmailAddresses = referralMatchingEmail + }; + + var nonMatchingMethod = new EStatementDeliveryMethod() + { + ActivityNumber = 3, + AccountNumber = 33333, + PersonNumber = "5678", + OrganizationNumber = "0", + ActivityDateTime = DateTime.Now.AddDays(deliveryMethodDaysInPast), + PhoneNumbers = null, + EmailAddresses = null + }; + + var eStatmentActivities = new List() { matchedOnEmailDeliveryMethod, matchedOnPersonNumberButNotLatest, nonMatchingMethod }; + _mockCoreApiWrapperService.Setup(mock => mock.GetEStatmentDeliveryMethods(It.IsAny())).ReturnsAsync(eStatmentActivities); + var results = await _referralService.UpdateOrExpireEStatementReferral(); + + _mockProcessorHistoryService.Verify(mock => mock.SaveProcessorHistory(It.IsAny()), Times.Once); + + Assert.IsNotNull(results); + Assert.IsTrue(results.Count == 1); + Assert.IsTrue(results.First().Note.Contains(matchedOnEmailDeliveryMethod.AccountNumber.ToString())); + Assert.AreEqual(matchedEStatementReferral.Id, results.First().ActivityId); + Assert.AreEqual(ActivityState.Accepted, results.First().State); + Assert.AreEqual(1, matchedEStatementReferral.MetaData.Where(x => x.MetaDataType == MetaDataType.Phone).Count()); // Did not add any phone numbers + Assert.AreEqual(1, matchedEStatementReferral.MetaData.Where(x => x.MetaDataType == MetaDataType.Email).Count()); // Added one new email address + } + + [Test] + public async Task MatchOrExpireEstatementReferral_MultipleReferralMatches_MatchOneReferralNoExpire() + { + // Delivery methods activity dates must precede referral source created date + var deliveryMethodDaysInPast = -2; + var referralDaysInPast = -5; + var latestReferral = -4; + + var duplicateReferral = new Activity() + { + Product = Product.eStatement, + EmployeeId = "1", + SourceId = "1", + Type = ActivityType.Referral, + State = ActivityState.PendingReferral, + SourceCreated = DateTime.Now.AddDays(latestReferral), + Detail = new ActivityDetail + { + AccountNumber = null, + SubjectPersonNbr = "1234", + SubjectOrgNbr = null + } + }; + + duplicateReferral.Id = await _activityRepository.SaveActivityAsync(duplicateReferral); + await _unitOfWork.CompleteAsync(); + + var matchingReferral = new Activity() + { + Product = Product.eStatement, + EmployeeId = "2", + SourceId = "2", + Type = ActivityType.Referral, + State = ActivityState.PendingReferral, + SourceCreated = DateTime.Now.AddDays(referralDaysInPast), + Detail = new ActivityDetail + { + AccountNumber = null, + SubjectPersonNbr = "1234", + SubjectOrgNbr = null + } + }; + // Saving this referral second makes it the "latest" (last updated) and match + matchingReferral.Id = await _activityRepository.SaveActivityAsync(matchingReferral); + await _unitOfWork.CompleteAsync(); + + var lastRun = new ProcessorHistoryRecord() { }; + _mockProcessorHistoryService.Setup(mock => mock.GetProcessorHistory(It.IsAny())).ReturnsAsync(lastRun); + + var matchedDeliveryMethod = new EStatementDeliveryMethod() + { + ActivityNumber = 1, + AccountNumber = 11111, + PersonNumber = "1234", + OrganizationNumber = "0", + ActivityDateTime = DateTime.Now.AddDays(deliveryMethodDaysInPast), + PhoneNumbers = null, + EmailAddresses = null + }; + + var nonMatchedDeliveryMethod = new EStatementDeliveryMethod() + { + ActivityNumber = 2, + AccountNumber = 22222, + PersonNumber = "4321", + OrganizationNumber = "0", + ActivityDateTime = DateTime.Now.AddDays(deliveryMethodDaysInPast), + PhoneNumbers = null, + EmailAddresses = null + }; + + var eStatmentActivities = new List() { matchedDeliveryMethod }; + _mockCoreApiWrapperService.Setup(mock => mock.GetEStatmentDeliveryMethods(It.IsAny())).ReturnsAsync(eStatmentActivities); + var results = await _referralService.UpdateOrExpireEStatementReferral(); + + _mockProcessorHistoryService.Verify(mock => mock.SaveProcessorHistory(It.IsAny()), Times.Once); + + Assert.IsNotNull(results); + Assert.IsTrue(results.Count == 1); + Assert.IsTrue(results.First().Note.Contains(matchedDeliveryMethod.AccountNumber.ToString())); + Assert.AreEqual(matchingReferral.Id, results.First().ActivityId); + Assert.AreEqual(ActivityState.Accepted, results.First().State); + } + + [Test] + public async Task MatchOrExpireEstatementReferral_NoMatch_Expire() + { + var referralDaysInPast = -61; // Expires after 60 days + + var matchedEStatementReferral = new Activity() + { + Product = Product.eStatement, + EmployeeId = "1", + SourceId = "2", + Type = ActivityType.Referral, + State = ActivityState.PendingReferral, + SourceCreated = DateTime.Now.AddDays(referralDaysInPast), + Detail = new ActivityDetail + { + AccountNumber = null, + SubjectPersonNbr = null, + SubjectOrgNbr = null + } + }; + + matchedEStatementReferral.Id = await _activityRepository.SaveActivityAsync(matchedEStatementReferral); + await _unitOfWork.CompleteAsync(); + + var lastRun = new ProcessorHistoryRecord() { }; + _mockProcessorHistoryService.Setup(mock => mock.GetProcessorHistory(It.IsAny())).ReturnsAsync(lastRun); + + var eStatmentActivities = new List() { }; // Matching fails because there are no estatements to match with + _mockCoreApiWrapperService.Setup(mock => mock.GetEStatmentDeliveryMethods(It.IsAny())).ReturnsAsync(eStatmentActivities); + var results = await _referralService.UpdateOrExpireEStatementReferral(); + + _mockProcessorHistoryService.Verify(mock => mock.SaveProcessorHistory(It.IsAny()), Times.Never); + + Assert.IsNotNull(results); + Assert.IsTrue(results.Count == 1); + Assert.IsTrue(results.First().Note.Contains("expired")); + Assert.AreEqual(0, matchedEStatementReferral.MetaData.Where(x => x.MetaDataType == MetaDataType.Phone).Count()); + Assert.AreEqual(0, matchedEStatementReferral.MetaData.Where(x => x.MetaDataType == MetaDataType.Email).Count()); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ExternalActivity/ExternalActivityServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ExternalActivity/ExternalActivityServiceTest.cs new file mode 100644 index 0000000..2a1ffe2 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ExternalActivity/ExternalActivityServiceTest.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApi.Extensions; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace Application.UnitTests.Services.ExternalActivity +{ + public class ExternalActivityServiceTest + { + private IExternalActivityService _externalActivityService; + private Mock _mockUserService; + private Mock _mockDepartmentRepository; + + [SetUp] + public void SetUp() + { + _mockUserService = new Mock(); + _mockDepartmentRepository = new Mock(); + var strategies = new List() + { + new NewCheckingAccountActivityStrategy(_mockUserService.Object, _mockDepartmentRepository.Object), + new NewIncentiveManagerActivityStrategy(_mockUserService.Object, _mockDepartmentRepository.Object), + new NewAtmCheckCardActivityStrategy(_mockUserService.Object, _mockDepartmentRepository.Object), + new NewMortgageActivityStrategy(_mockUserService.Object, _mockDepartmentRepository.Object) + }; + _externalActivityService = new ExternalActivityService(strategies); + } + + [Test] + public async Task NewAtmCheckCardActivityStrategy_ATM_OK() + { + var externalActivity = new NewAtmCheckCardActivity() + { + CardType = "ATM", + AccountNbr = 1, + ActivityDateTime = DateTime.Parse("2021-09-10"), + ResponsiblePersonNbr = 1234, + AgreementNbr = 10111, + OwnerPersonNbr = "5678", + AcctStateAtActivity = "ACT", + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + EmployeeEmail = "JohnnySmith@landmarkcu.com" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync(new AppUser() + { + AdEmail = "JohnnySmith@landmarkcu.com", + EmployeeId = "7624" + }); + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync( + new Department() + { + Id = 10 + } + ); + + var activity = await _externalActivityService.TryMapExternalActivity(externalActivity); + Assert.True(activity.Product == Product.ATMCard); + Assert.True(activity.Type == ActivityType.Other); + Assert.AreEqual(10, activity.DepartmentId); + Assert.AreEqual("7624", activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.IsAny()), Times.Exactly(1)); + } + + [Test] + public async Task NewCheckingAccountActivityStrategy_Ok() + { + var externalActivity = new NewCheckingAccountActivity() + { + SubjectAccountNbr = 1, + ResponsiblePersonNbr = 1234, + BranchOrgName = "West Bend-Main Branch", + BranchOrgNbr = 400, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + EmployeeEmail = "JohnnySmith@landmarkcu.com" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync(new AppUser() + { + AdEmail = "JohnnySmith@landmarkcu.com", + EmployeeId = "7624" + }); + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync( + new Department() + { + Id = 10 + } + ); + + var activity = await _externalActivityService.TryMapExternalActivity(externalActivity); + Assert.True(activity.Product == Product.NewChecking); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(10, activity.DepartmentId); + Assert.AreEqual("7624", activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.Is(x => x == "JohnnySmith@landmarkcu.com")), + Times.Once); + _mockUserService.Verify(x => x.GetAllUsersAsync(It.IsAny()), Times.Never); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.Is(x => x.EmployeeId == "7624")), Times.Once); + } + + [TestCase("Atm Card", Product.ATMCard)] + [TestCase("Check Card Referral", Product.CheckCard)] + [TestCase("Checking Referral", Product.NewChecking)] + [TestCase("Mortgage Referral", Product.Mortgage)] + [TestCase("Direct Deposit", Product.DirectDeposit)] + [TestCase("CC Balance Transfer", Product.CreditCardBalanceTransfer)] + public async Task NewIncentiveManagerActivityStrategy_Ok(string campaign, Product product) + { + var externalActivity = new NewActivityManagerReferral() + { + OriginatingBranchName = "MEQUON", + OriginatorPersNbr = 5, + OriginatorUserId = "JSMITHB", + ReferredPersonNbr = "1234", + ReferredPersOrgNbr = null, + ReferralStation= "Submitted", + StationDateTime = DateTime.Today.StartOfMonth(), + Campaign = campaign, + OriginationDateTime = DateTime.Parse("2021-03-10"), + OriginatorFirstName = "John", + OriginatorLastName = "Smith", + OriginatorMiddleInitial = "B", + EmployeeEmail = "JohnnySmith@landmarkcu.com" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync(new AppUser() + { + AdEmail = "JohnnySmith@landmarkcu.com", + EmployeeId = "7624" + }); + _mockDepartmentRepository.Setup(x => x.FindDepartmentByBranchName(It.IsAny())).ReturnsAsync( + new Department() + { + Id = 10 + } + ); + + var activity = await _externalActivityService.TryMapExternalActivity(externalActivity); + Assert.True(activity.Product == product); + Assert.True(activity.Type == ActivityType.Referral); + Assert.AreEqual(10, activity.DepartmentId); + Assert.AreEqual("7624", activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.Is(x => x == "JohnnySmith@landmarkcu.com")), Times.Once); + _mockDepartmentRepository.Verify(x => x.FindDepartmentByBranchName(It.Is(x => x == "MEQUON")), Times.Once); + } + + [Test] + public async Task NewMortgageActivityStrategy_Ok() + { + var externalActivity = new NewLoanSaleActivity() + { + SubjectAccountNbr = 1, + OriginatingPersonNbr = 1234, + BranchOrgName = "West Bend-Main Branch", + BranchOrgNbr = 400, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + EmployeeEmail = "JohnnySmith@landmarkcu.com", + MinorAccountType = "FE01" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync(new AppUser() + { + AdEmail = "JohnnySmith@landmarkcu.com", + EmployeeId = "7624" + }); + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync( + new Department() + { + Id = 10 + } + ); + + var activity = await _externalActivityService.TryMapExternalActivity(externalActivity); + Assert.True(activity.Product == Product.Mortgage); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(10, activity.DepartmentId); + Assert.AreEqual("7624", activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.Is(x => x == "JohnnySmith@landmarkcu.com")), Times.Once); + _mockUserService.Verify(x => x.GetAllUsersAsync(It.IsAny()), Times.Never); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.Is(x => x.EmployeeId == "7624")), Times.Once); + } + + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ExternalActivity/NewActivityManagerActivityStrategyTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ExternalActivity/NewActivityManagerActivityStrategyTest.cs new file mode 100644 index 0000000..b559902 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ExternalActivity/NewActivityManagerActivityStrategyTest.cs @@ -0,0 +1,229 @@ +using System; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Extensions; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity; +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; + +namespace Application.UnitTests.Services.ExternalActivity +{ + public class NewActivityManagerActivityStrategyTest + { + private Mock _mockUserService; + private Mock _mockDepartmentRepository; + private NewIncentiveManagerActivityStrategy _externalActivityService; + [SetUp] + public void Setup() + { + _mockUserService = new Mock(); + _mockDepartmentRepository = new Mock(); + _externalActivityService = new NewIncentiveManagerActivityStrategy(_mockUserService.Object, _mockDepartmentRepository.Object); + } + + [TestCase("Atm Card", Product.ATMCard)] + [TestCase("Check Card Referral", Product.CheckCard)] + [TestCase("Checking Referral", Product.NewChecking)] + [TestCase("Mortgage Referral", Product.Mortgage)] + [TestCase("Direct Deposit", Product.DirectDeposit)] + [TestCase("CC Balance Transfer", Product.CreditCardBalanceTransfer)] + [TestCase("LIC Referral", Product.LIC)] + public async Task NewIncentiveManagerActivityStrategy_Ok(string campaign, Product product) + { + var externalActivity = new NewActivityManagerReferral() + { + OriginatingBranchName = "MEQUON", + OriginatorPersNbr = 5, + OriginatorUserId = "JSMITHB", + ReferredPersonNbr = "1234", + ReferredPersOrgNbr = null, + ReferralStation= "Submitted", + StationDateTime = DateTime.Today.StartOfMonth(), + Campaign = campaign, + OriginationDateTime = DateTime.Parse("2021-03-10"), + OriginatorFirstName = "John", + OriginatorLastName = "Smith", + OriginatorMiddleInitial = "B", + EmployeeEmail = "JohnnySmith@landmarkcu.com" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync(new AppUser() + { + AdEmail = "JohnnySmith@landmarkcu.com", + EmployeeId = "7624" + }); + _mockDepartmentRepository.Setup(x => x.FindDepartmentByBranchName(It.IsAny())).ReturnsAsync( + new Department() + { + Id = 10 + } + ); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == product); + Assert.True(activity.Type == ActivityType.Referral); + Assert.AreEqual(10, activity.DepartmentId); + Assert.AreEqual("7624", activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.Is(x => x == "JohnnySmith@landmarkcu.com")), Times.Once); + _mockDepartmentRepository.Verify(x => x.FindDepartmentByBranchName(It.Is(x => x == "MEQUON")), Times.Once); + } + + [Test] + public async Task NewIncentiveManagerActivityStrategy_NoEmail_Ok() + { + var externalActivity = new NewActivityManagerReferral() + { + OriginatingBranchName = "WEST BEND MAIN", + OriginatorPersNbr = 5, + OriginatorUserId = "JSMITHB", + ReferredPersonNbr = "1234", + ReferredPersOrgNbr = null, + ReferralStation= "Submitted", + StationDateTime = DateTime.Today.StartOfMonth(), + Campaign = "ATM Card", + OriginationDateTime = DateTime.Parse("2021-03-10"), + OriginatorFirstName = "John", + OriginatorLastName = "Smith", + OriginatorMiddleInitial = "B", + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync((AppUser)null); + _mockDepartmentRepository.Setup(x => x.FindDepartmentByBranchName(It.IsAny())).ReturnsAsync( + new Department() + { + Id = 10 + } + ); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.ATMCard); + Assert.True(activity.Type == ActivityType.Referral); + Assert.AreEqual(10, activity.DepartmentId); + Assert.AreEqual(null, activity.EmployeeId); + + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Once); + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.IsAny()), Times.Never); + _mockDepartmentRepository.Verify(x => x.FindDepartmentByBranchName(It.Is(x => x == "WEST BEND MAIN")), Times.Once); + } + + [Test] + public async Task Throws_WhenUnknownProduct_OK() + { + var externalActivity = new NewActivityManagerReferral() + { + OriginatingBranchName = "WEST BEND MAIN", + OriginatorPersNbr = 5, + OriginatorUserId = "JSMITHB", + ReferredPersonNbr = "1234", + ReferredPersOrgNbr = null, + ReferralStation= "Submitted", + StationDateTime = DateTime.Today.StartOfMonth(), + Campaign = "THIS PRODUCT DOES NOT EXIST", + OriginationDateTime = DateTime.Parse("2021-03-10"), + OriginatorFirstName = "John", + OriginatorLastName = "Smith", + OriginatorMiddleInitial = "B", + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync(new AppUser() + { + AdEmail = "JohnnySmith@landmarkcu.com", + EmployeeId = "7624" + }); + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync( + new Department() + { + Id = 10 + } + ); + Assert.ThrowsAsync(async () => await _externalActivityService.MapToActivityManagerActivity(externalActivity)); + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.IsAny()), Times.Exactly(0)); + _mockDepartmentRepository.Verify(x => x.FindDepartmentByBranchName(It.Is(x => x == "WEST BEND MAIN")), Times.Once); + + } + + [Test] + public async Task NewIncentiveManagerActivityStrategy_NoDepartment_Ok() + { + var externalActivity = new NewActivityManagerReferral() + { + OriginatingBranchName = "WEST BEND MAIN", + OriginatorPersNbr = 5, + OriginatorUserId = "JSMITHB", + ReferredPersonNbr = "1234", + ReferredPersOrgNbr = null, + ReferralStation= "Submitted", + StationDateTime = DateTime.Today.StartOfMonth(), + Campaign = "ATM Card", + OriginationDateTime = DateTime.Parse("2021-03-10"), + OriginatorFirstName = "John", + OriginatorLastName = "Smith", + OriginatorMiddleInitial = "B", + EmployeeEmail = "JohnnySmith@landmarkcu.com" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync(new AppUser() + { + AdEmail = "JohnnySmith@landmarkcu.com", + EmployeeId = "7624" + }); + _mockDepartmentRepository.Setup(x => x.FindDepartmentByBranchName(It.IsAny())).ReturnsAsync((Department) null); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.ATMCard); + Assert.True(activity.Type == ActivityType.Referral); + Assert.AreEqual(-1, activity.DepartmentId); + Assert.AreEqual("7624", activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.Is(x => x == "JohnnySmith@landmarkcu.com")), Times.Once); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Never); + _mockDepartmentRepository.Verify(x => x.FindDepartmentByBranchName(It.Is(x => x == "WEST BEND MAIN")), Times.Once); + } + + [Test] + public async Task NewIncentiveManagerActivityStrategy_ExceptionReturnsBasic() + { + var externalActivity = new NewActivityManagerReferral() + { + OriginatingBranchName = "WEST BEND MAIN", + OriginatorPersNbr = 5, + OriginatorUserId = "JSMITHB", + ReferredPersonNbr = "1234", + ReferredPersOrgNbr = null, + ReferralStation= "Submitted", + StationDateTime = DateTime.Today.StartOfMonth(), + Campaign = "ATM Card", + OriginationDateTime = DateTime.Parse("2021-03-10"), + OriginatorFirstName = "John", + OriginatorLastName = "Smith", + OriginatorMiddleInitial = "B", + EmployeeEmail = "JohnnySmith@landmarkcu.com" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync(new AppUser() + { + AdEmail = "JohnnySmith@landmarkcu.com", + EmployeeId = "7624" + }); + _mockDepartmentRepository.Setup(x => x.FindDepartmentByBranchName(It.IsAny())) + .ThrowsAsync(new Exception("OOPS")); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.ATMCard); + Assert.True(activity.Type == ActivityType.Referral); + //we search for user before department, if department fails, we can still save user + Assert.AreEqual(-1, activity.DepartmentId); + Assert.AreEqual("7624", activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.Is(x => x == "JohnnySmith@landmarkcu.com")), Times.Once); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Never); + _mockDepartmentRepository.Verify(x => x.FindDepartmentByBranchName(It.Is(x => x == "WEST BEND MAIN")), Times.Once); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ExternalActivity/NewAtmCheckCardActivityStrategyTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ExternalActivity/NewAtmCheckCardActivityStrategyTest.cs new file mode 100644 index 0000000..d5ace61 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ExternalActivity/NewAtmCheckCardActivityStrategyTest.cs @@ -0,0 +1,260 @@ +using System; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity; +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; + +namespace Application.UnitTests.Services.ExternalActivity +{ + public class NewAtmCheckCardActivityStrategyTest + { + private Mock _mockUserService; + private Mock _mockDepartmentRepository; + private NewAtmCheckCardActivityStrategy _externalActivityService; + [SetUp] + public void Setup() + { + _mockUserService = new Mock(); + _mockDepartmentRepository = new Mock(); + _externalActivityService = new NewAtmCheckCardActivityStrategy(_mockUserService.Object, _mockDepartmentRepository.Object); + } + + [Test] + public async Task NewAtmCheckCardActivityStrategy_ATM_OK() + { + var externalActivity = new NewAtmCheckCardActivity() + { + CardType = "ATM", + AccountNbr = 1, + ActivityDateTime = DateTime.Parse("2021-09-10"), + ResponsiblePersonNbr = 1234, + AgreementNbr = 10111, + OwnerPersonNbr = "5678", + AcctStateAtActivity = "ACT", + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + EmployeeEmail = "JohnnySmith@landmarkcu.com" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync(new AppUser() + { + AdEmail = "JohnnySmith@landmarkcu.com", + EmployeeId = "7624" + }); + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync( + new Department() + { + Id = 10 + } + ); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.ATMCard); + Assert.True(activity.Type == ActivityType.Other); + Assert.AreEqual(10, activity.DepartmentId); + Assert.AreEqual("7624", activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.IsAny()), Times.Exactly(1)); + } + + [Test] + public async Task NewAtmCheckCardActivityStrategy_CheckCard_OK() + { + var externalActivity = new NewAtmCheckCardActivity() + { + CardType = "Check Card", + AccountNbr = 1, + ActivityDateTime = DateTime.Parse("2021-09-10"), + ResponsiblePersonNbr = 1234, + AgreementNbr = 10111, + OwnerPersonNbr = "5678", + AcctStateAtActivity = "ACT", + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + EmployeeEmail = "JohnnySmith@landmarkcu.com" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync(new AppUser() + { + AdEmail = "JohnnySmith@landmarkcu.com", + EmployeeId = "7624" + }); + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync( + new Department() + { + Id = 10 + } + ); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.CheckCard); + Assert.True(activity.Type == ActivityType.Other); + Assert.AreEqual(10, activity.DepartmentId); + Assert.AreEqual("7624", activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.IsAny()), Times.Exactly(1)); + } + + [Test] + public async Task NewAtmCheckCardActivityStrategy_NoEmail_OK() + { + var externalActivity = new NewAtmCheckCardActivity() + { + CardType = "ATM", + AccountNbr = 1, + ActivityDateTime = DateTime.Parse("2021-09-10"), + ResponsiblePersonNbr = 1234, + AgreementNbr = 10111, + OwnerPersonNbr = "5678", + AcctStateAtActivity = "ACT", + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync((AppUser)null); + _mockUserService + .Setup(x => x.TryFindSingleUserByName(It.IsAny())) + .ReturnsAsync( + new AppUser() + { + AdEmail = "JohnSmith@landmarkcu.com", + EmployeeId = "7624" + }); + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync( + new Department() + { + Id = 10 + } + ); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.ATMCard); + Assert.True(activity.Type == ActivityType.Other); + Assert.AreEqual(10, activity.DepartmentId); + Assert.AreEqual("7624", activity.EmployeeId); + + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.IsAny()), Times.Never); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Once); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.IsAny()), Times.Once); + } + + [Test] + public async Task NewAtmCheckCardActivityStrategy_ATM_NoDepartment_OK() + { + var externalActivity = new NewAtmCheckCardActivity() + { + CardType = "ATM", + AccountNbr = 1, + ActivityDateTime = DateTime.Parse("2021-09-10"), + ResponsiblePersonNbr = 1234, + AgreementNbr = 10111, + OwnerPersonNbr = "5678", + AcctStateAtActivity = "ACT", + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + EmployeeEmail = "JohnnySmith@landmarkcu.com" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync(new AppUser() + { + AdEmail = "JohnnySmith@landmarkcu.com", + EmployeeId = "7624" + }); + + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync((Department) null); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.ATMCard); + Assert.True(activity.Type == ActivityType.Other); + Assert.AreEqual(-1, activity.DepartmentId); + Assert.AreEqual("7624", activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.Is(x => x == "JohnnySmith@landmarkcu.com")), Times.Once); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Never); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.IsAny()), Times.Once); + } + + //NewAtmCheckCard uses Users Home Department to assign branch. + [Test] + public async Task NewAtmCheckCardActivityStrategy_ATM_NoUser_OK() + { + var externalActivity = new NewAtmCheckCardActivity() + { + CardType = "ATM", + AccountNbr = 1, + ActivityDateTime = DateTime.Parse("2021-09-10"), + ResponsiblePersonNbr = 1234, + AgreementNbr = 10111, + OwnerPersonNbr = "5678", + AcctStateAtActivity = "ACT", + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + EmployeeEmail = "JohnnySmith@landmarkcu.com" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync((AppUser)null); + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync(new Department() + { + Id = 10 + }); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.ATMCard); + Assert.True(activity.Type == ActivityType.Other); + Assert.AreEqual(-1, activity.DepartmentId); + Assert.AreEqual(null, activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.Is(x => x == "JohnnySmith@landmarkcu.com")), Times.Once); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Never); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.IsAny()), Times.Never); + } + + [Test] + public async Task NewAtmCheckCardActivityStrategy_ExceptionReturnsBasic() + { + var externalActivity = new NewAtmCheckCardActivity() + { + CardType = "ATM", + AccountNbr = 1, + ActivityDateTime = DateTime.Parse("2021-09-10"), + ResponsiblePersonNbr = 1234, + AgreementNbr = 10111, + OwnerPersonNbr = "5678", + AcctStateAtActivity = "ACT", + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + EmployeeEmail = "JohnnySmith@landmarkcu.com" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ThrowsAsync(new Exception("Oops")); + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync(new Department() + { + Id = 10 + }); + + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.ATMCard); + Assert.True(activity.Type == ActivityType.Other); + Assert.AreEqual(-1, activity.DepartmentId); + Assert.AreEqual(null, activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.Is(x => x == "JohnnySmith@landmarkcu.com")), Times.Once); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.IsAny()), Times.Never); + } + + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ExternalActivity/NewCheckingAccountActivityStrategyTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ExternalActivity/NewCheckingAccountActivityStrategyTest.cs new file mode 100644 index 0000000..fdcb750 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ExternalActivity/NewCheckingAccountActivityStrategyTest.cs @@ -0,0 +1,433 @@ +using System; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity; +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; + +namespace Application.UnitTests.Services.ExternalActivity +{ + public class NewCheckingAccountActivityStrategyTest + { + private Mock _mockUserService; + private Mock _mockDepartmentRepository; + private NewCheckingAccountActivityStrategy _externalActivityService; + + [SetUp] + public void Setup() + { + _mockUserService = new Mock(); + _mockDepartmentRepository = new Mock(); + _externalActivityService = new NewCheckingAccountActivityStrategy(_mockUserService.Object, _mockDepartmentRepository.Object); + } + + [Test] + public async Task NewCheckingAccountActivityStrategy_Ok() + { + var externalActivity = new NewCheckingAccountActivity() + { + SubjectAccountNbr = 1, + ResponsiblePersonNbr = 1234, + BranchOrgName = "West Bend-Main Branch", + BranchOrgNbr = 400, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + EmployeeEmail = "JohnnySmith@landmarkcu.com" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync(new AppUser() + { + AdEmail = "JohnnySmith@landmarkcu.com", + EmployeeId = "7624" + }); + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync( + new Department() + { + Id = 10 + } + ); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.NewChecking); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(10, activity.DepartmentId); + Assert.AreEqual("7624", activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.Is(x => x == "JohnnySmith@landmarkcu.com")), Times.Once); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Never); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.Is(x => x.EmployeeId == "7624")), Times.Once); + } + + [Test] + public async Task NewCheckingAccountActivityStrategy_NoEmail_Ok() + { + var externalActivity = new NewCheckingAccountActivity() + { + SubjectAccountNbr = 1, + ResponsiblePersonNbr = 1234, + BranchOrgName = "West Bend-Main Branch", + BranchOrgNbr = 400, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync((AppUser) null); + _mockUserService.Setup(x => x.TryFindSingleUserByName(It.IsAny())).ReturnsAsync( + + new AppUser() + { + AdEmail = "JohnSmith@landmarkcu.com", + EmployeeId = "7624" + }); + + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync( + new Department() + { + Id = 10 + } + ); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.NewChecking); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(10, activity.DepartmentId); + Assert.AreEqual("7624", activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.IsAny()), Times.Never); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Once); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.Is(x => x.EmployeeId == "7624")), Times.Once); + } + + [Test] + public async Task NewCheckingAccountActivityStrategy_NoUser() + { + var externalActivity = new NewCheckingAccountActivity() + { + SubjectAccountNbr = 1, + ResponsiblePersonNbr = 1234, + BranchOrgName = "West Bend-Main Branch", + BranchOrgNbr = 400, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync((AppUser) null); + _mockUserService + .Setup(x => x.TryFindSingleUserByName(It.IsAny())) + .ReturnsAsync((AppUser)null); + + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync( + new Department() + { + Id = 10 + }); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.NewChecking); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(-1, activity.DepartmentId); + Assert.AreEqual(null, activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.IsAny()), Times.Never); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Once); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.IsAny()), Times.Never()); + } + + [Test] + public async Task NewCheckingAccountActivityStrategy_NoDepartment_Ok() + { + var externalActivity = new NewCheckingAccountActivity() + { + SubjectAccountNbr = 1, + ResponsiblePersonNbr = 1234, + BranchOrgName = "West Bend-Main Branch", + BranchOrgNbr = 400, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + EmployeeEmail = "JohnnySmith@landmarkcu.com" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync(new AppUser() + { + AdEmail = "JohnnySmith@landmarkcu.com", + EmployeeId = "7624" + }); + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())) + .ReturnsAsync((Department)null); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.NewChecking); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(-1, activity.DepartmentId); + Assert.AreEqual("7624", activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.Is(x => x == "JohnnySmith@landmarkcu.com")), Times.Once); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Never); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.Is(x => x.EmployeeId == "7624")), Times.Once); + } + + [Test] + public async Task NewCheckingAccountActivityStrategy_ExceptionReturnsBasic() + { + var externalActivity = new NewCheckingAccountActivity() + { + SubjectAccountNbr = 1, + ResponsiblePersonNbr = 1234, + BranchOrgName = "West Bend-Main Branch", + BranchOrgNbr = 400, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ThrowsAsync(new Exception("OOPS")); + _mockUserService + .Setup(x => x.TryFindSingleUserByName(It.IsAny())) + .ThrowsAsync(new Exception("OOPS")); + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())) + .ReturnsAsync((Department)null); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.NewChecking); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(-1, activity.DepartmentId); + Assert.AreEqual(null, activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.Is(x => x == "JohnnySmith@landmarkcu.com")), Times.Never); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Once); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.IsAny()), Times.Never); + } + + [Test] + public async Task NewDigitalBankingCheckingAccountActivityStrategy_Ok() + { + var externalActivity = new NewCheckingAccountActivity() + { + SubjectAccountNbr = 1, + ResponsiblePersonNbr = 1234, + BranchOrgName = "Digital Banking", + BranchOrgNbr = 1210, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2022-12-10"), + AssociateFirst = "Digital", + AssociateLast = "Banking", + AssociateMiddleInit = "", + EmployeeEmail = "DigitalBanking@landmarkcu.com" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync(new AppUser() + { + AdEmail = "DigitalBanking@landmarkcu.com", + EmployeeId = "DigitalBankingAppUserEmployeeId" + }); + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync( + new Department() + { + Id = 43 + } + ); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.NewChecking); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(43, activity.DepartmentId); + Assert.AreEqual("DigitalBankingAppUserEmployeeId", activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.Is(x => x == "DigitalBanking@landmarkcu.com")), Times.Once); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Never); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.Is(x => x.EmployeeId == "DigitalBankingAppUserEmployeeId")), Times.Once); + } + + + [Test] + public async Task NewDigitalBankingCheckingAccountActivityStrategy_NoEmail_Ok() + { + var externalActivity = new NewCheckingAccountActivity() + { + SubjectAccountNbr = 1, + ResponsiblePersonNbr = 1234, + BranchOrgName = "Digital Banking", + BranchOrgNbr = 1210, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = "Digital", + AssociateLast = "Banking", + AssociateMiddleInit = "" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync((AppUser)null); + _mockUserService.Setup(x => x.TryFindSingleUserByName(It.IsAny())).ReturnsAsync( + + new AppUser() + { + AdEmail = "DigitalBanking@landmarkcu.com", + EmployeeId = "DigitalBankingAppUserEmployeeId" + }); + + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync( + new Department() + { + Id = 43 + } + ); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.NewChecking); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(43, activity.DepartmentId); + Assert.AreEqual("DigitalBankingAppUserEmployeeId", activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.IsAny()), Times.Never); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Once); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.Is(x => x.EmployeeId == "DigitalBankingAppUserEmployeeId")), Times.Once); + } + + [Test] + public async Task NewDigitalBankingCheckingAccountActivityStrategy_NoUser() + { + var externalActivity = new NewCheckingAccountActivity() + { + SubjectAccountNbr = 1, + ResponsiblePersonNbr = 1234, + BranchOrgName = "Digital Banking", + BranchOrgNbr = 1210, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = "Digital", + AssociateLast = "Banking", + AssociateMiddleInit = "" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync((AppUser)null); + _mockUserService + .Setup(x => x.TryFindSingleUserByName(It.IsAny())) + .ReturnsAsync((AppUser)null); + + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync( + new Department() + { + Id = 43 + }); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.NewChecking); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(-1, activity.DepartmentId); + Assert.AreEqual(null, activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.IsAny()), Times.Never); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Once); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.IsAny()), Times.Never()); + } + + [Test] + public async Task NewDigitalBankingCheckingAccountActivityStrategy_NoDepartment_Ok() + { + var externalActivity = new NewCheckingAccountActivity() + { + SubjectAccountNbr = 1, + ResponsiblePersonNbr = 1234, + BranchOrgName = "Digital Banking", + BranchOrgNbr = 1210, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = "Digital", + AssociateLast = "Banking", + AssociateMiddleInit = "", + EmployeeEmail = "DigitalBanking@landmarkcu.com" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync(new AppUser() + { + AdEmail = "DigitalBanking@landmarkcu.com", + EmployeeId = "DigitalBankingAppUserEmployeeId" + }); + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())) + .ReturnsAsync((Department)null); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.NewChecking); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(-1, activity.DepartmentId); + Assert.AreEqual("DigitalBankingAppUserEmployeeId", activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.Is(x => x == "DigitalBanking@landmarkcu.com")), Times.Once); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Never); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.Is(x => x.EmployeeId == "DigitalBankingAppUserEmployeeId")), Times.Once); + } + + [Test] + public async Task NewDigitalBankingCheckingAccountActivityStrategy_ExceptionReturnsBasic() + { + var externalActivity = new NewCheckingAccountActivity() + { + SubjectAccountNbr = 1, + ResponsiblePersonNbr = 1234, + BranchOrgName = "Digital Banking", + BranchOrgNbr = 1210, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = "Digital", + AssociateLast = "Banking", + AssociateMiddleInit = "" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ThrowsAsync(new Exception("OOPS")); + _mockUserService + .Setup(x => x.TryFindSingleUserByName(It.IsAny())) + .ThrowsAsync(new Exception("OOPS")); + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())) + .ReturnsAsync((Department)null); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.NewChecking); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(-1, activity.DepartmentId); + Assert.AreEqual(null, activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.Is(x => x == "DigitalBanking@landmarkcu.com")), Times.Never); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Once); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.IsAny()), Times.Never); + } + + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ExternalActivity/NewDebtProtectionEligibleLoanStrategyTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ExternalActivity/NewDebtProtectionEligibleLoanStrategyTest.cs new file mode 100644 index 0000000..b92b6bd --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ExternalActivity/NewDebtProtectionEligibleLoanStrategyTest.cs @@ -0,0 +1,231 @@ +using System; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity; +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; + +namespace Application.UnitTests.Services.ExternalActivity +{ + public class NewDebtProtectionEligibleLoanStrategyTest + { + private Mock _mockUserService; + private Mock _mockDepartmentRepository; + private NewDebtProtectionEligibleLoanStrategy _externalActivityService; + [SetUp] + public void Setup() + { + _mockUserService = new Mock(); + _mockDepartmentRepository = new Mock(); + _externalActivityService = new NewDebtProtectionEligibleLoanStrategy(_mockUserService.Object, _mockDepartmentRepository.Object); + } + + + [Test] + public async Task NewDebtProtectionEligibleLoan_Ok() + { + var externalActivity = new NewLoanSaleActivity() + { + SubjectAccountNbr = 1, + OriginatingPersonNbr = 1234, + BranchOrgName = "West Bend-Main Branch", + BranchOrgNbr = 400, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + EmployeeEmail = "JohnnySmith@landmarkcu.com", + MinorAccountType = "SG01" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync(new AppUser() + { + AdEmail = "JohnnySmith@landmarkcu.com", + EmployeeId = "7624" + }); + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync( + new Department() + { + Id = 10 + }); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.DebtProtectionEligibleLoan); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(10, activity.DepartmentId); + Assert.AreEqual("7624", activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.Is(x => x == "JohnnySmith@landmarkcu.com")), Times.Once); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Never); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.Is(x => x.EmployeeId == "7624")), Times.Once); + } + + [Test] + public async Task NewDebtProtectionEligibleLoan_NoEmail_Ok() + { + var externalActivity = new NewLoanSaleActivity() + { + SubjectAccountNbr = 1, + OriginatingPersonNbr = 1234, + BranchOrgName = "West Bend-Main Branch", + BranchOrgNbr = 400, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + MinorAccountType = "SG01" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync((AppUser) null); + _mockUserService.Setup(x => x.TryFindSingleUserByName(It.IsAny())).ReturnsAsync( + new AppUser() + { + AdEmail = "JohnSmith@landmarkcu.com", + EmployeeId = "7624" + } + ); + + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync( + new Department() + { + Id = 10 + }); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.DebtProtectionEligibleLoan); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(10, activity.DepartmentId); + Assert.AreEqual("7624", activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.IsAny()), Times.Never); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Once); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.Is(x => x.EmployeeId == "7624")), Times.Once); + } + + [Test] + public async Task NewDebtProtectionEligibleLoan_NoUser_Ok() + { + var externalActivity = new NewLoanSaleActivity() + { + SubjectAccountNbr = 1, + OriginatingPersonNbr = 1234, + BranchOrgName = "West Bend-Main Branch", + BranchOrgNbr = 400, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + MinorAccountType = "SG01" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync((AppUser) null); + _mockUserService + .Setup(x => x.TryFindSingleUserByName(It.IsAny())) + .ReturnsAsync((AppUser)null); + + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync( + new Department() + { + Id = 10 + }); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.DebtProtectionEligibleLoan); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(-1, activity.DepartmentId); + Assert.AreEqual(null, activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.IsAny()), Times.Never); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Exactly(1)); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.IsAny()), Times.Never()); + } + + [Test] + public async Task NewDebtProtectionEligibleLoan_NoDepartment_Ok() + { + var externalActivity = new NewLoanSaleActivity() + { + SubjectAccountNbr = 1, + OriginatingPersonNbr = 1234, + BranchOrgName = "West Bend-Main Branch", + BranchOrgNbr = 400, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + EmployeeEmail = "JohnnySmith@landmarkcu.com", + MinorAccountType = "SG01" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync(new AppUser() + { + AdEmail = "JohnnySmith@landmarkcu.com", + EmployeeId = "7624" + }); + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync((Department)null); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.DebtProtectionEligibleLoan); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(-1, activity.DepartmentId); + Assert.AreEqual("7624", activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.Is(x => x == "JohnnySmith@landmarkcu.com")), Times.Once); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Never); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.Is(x => x.EmployeeId == "7624")), Times.Once); + } + + [Test] + public async Task NewDebtProtectionEligibleLoan_ExceptionReturnsBasic() + { + var externalActivity = new NewLoanSaleActivity() + { + SubjectAccountNbr = 1, + OriginatingPersonNbr = 1234, + BranchOrgName = "West Bend-Main Branch", + BranchOrgNbr = 400, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + MinorAccountType = "SG01" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ThrowsAsync(new Exception("OOPS")); + _mockUserService + .Setup(x => x.TryFindSingleUserByName(It.IsAny())) + .ThrowsAsync(new Exception("OOPS")); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.AreEqual(activity.Product, Product.DebtProtectionEligibleLoan); + Assert.AreEqual(activity.Type, ActivityType.Sale); + Assert.AreEqual(-1, activity.DepartmentId); + Assert.AreEqual(null, activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.Is(x => x == "JohnnySmith@landmarkcu.com")), Times.Never); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Once); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.IsAny()), Times.Never); + + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ExternalActivity/NewMortgageActivityStrategyTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ExternalActivity/NewMortgageActivityStrategyTest.cs new file mode 100644 index 0000000..0c40663 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ExternalActivity/NewMortgageActivityStrategyTest.cs @@ -0,0 +1,280 @@ +using System; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity; +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; +using LCU.API.Internal.Operations.IncentiveApi.Models.Constants; + +namespace Application.UnitTests.Services.ExternalActivity +{ + public class NewMortgageActivityStrategyTest + { + private Mock _mockUserService; + private Mock _mockDepartmentRepository; + private NewMortgageActivityStrategy _externalActivityService; + [SetUp] + public void Setup() + { + _mockUserService = new Mock(); + _mockDepartmentRepository = new Mock(); + _externalActivityService = new NewMortgageActivityStrategy(_mockUserService.Object, _mockDepartmentRepository.Object); + } + + + [Test] + public async Task NewMortgageActivityStrategy_Ok() + { + var externalActivity = new NewLoanSaleActivity() + { + SubjectAccountNbr = 1, + OriginatingPersonNbr = 1234, + BranchOrgName = "West Bend-Main Branch", + BranchOrgNbr = 400, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + EmployeeEmail = "JohnnySmith@landmarkcu.com", + MinorAccountType = "FE01" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync(new AppUser() + { + AdEmail = "JohnnySmith@landmarkcu.com", + EmployeeId = "7624" + }); + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync( + new Department() + { + Id = 10 + } + ); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.Mortgage); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(10, activity.DepartmentId); + Assert.AreEqual("7624", activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.Is(x => x == "JohnnySmith@landmarkcu.com")), Times.Once); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Never); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.Is(x => x.EmployeeId == "7624")), Times.Once); + } + + [Test] + public async Task NewMortgageActivityStrategy_NoEmail_Ok() + { + var externalActivity = new NewLoanSaleActivity() + { + SubjectAccountNbr = 1, + OriginatingPersonNbr = 1234, + BranchOrgName = "West Bend-Main Branch", + BranchOrgNbr = 400, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + MinorAccountType = "FE01" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync((AppUser) null); + _mockUserService.Setup(x => x.TryFindSingleUserByName(It.IsAny())).ReturnsAsync( + new AppUser() + { + AdEmail = "JohnSmith@landmarkcu.com", + EmployeeId = "7624" + } + ); + + + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync( + new Department() + { + Id = 10 + } + ); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.Mortgage); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(10, activity.DepartmentId); + Assert.AreEqual("7624", activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.IsAny()), Times.Never); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Once); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.Is(x => x.EmployeeId == "7624")), Times.Once); + } + + [Test] + public async Task NewMortgageActivityStrategy_NoUser_Ok() + { + var externalActivity = new NewLoanSaleActivity() + { + SubjectAccountNbr = 1, + OriginatingPersonNbr = 1234, + BranchOrgName = "West Bend-Main Branch", + BranchOrgNbr = 400, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + MinorAccountType = "FE01" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync((AppUser) null); + _mockUserService + .Setup(x => x.TryFindSingleUserByName(It.IsAny())) + .ReturnsAsync((AppUser)null); + + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync( + new Department() + { + Id = 10 + }); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.Mortgage); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(-1, activity.DepartmentId); + Assert.AreEqual(null, activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.IsAny()), Times.Never); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Exactly(1)); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.IsAny()), Times.Never()); + } + + [Test] + public async Task NewMortgageActivityStrategy_NoDepartment_Ok() + { + var externalActivity = new NewLoanSaleActivity() + { + SubjectAccountNbr = 1, + OriginatingPersonNbr = 1234, + BranchOrgName = "West Bend-Main Branch", + BranchOrgNbr = 400, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + EmployeeEmail = "JohnnySmith@landmarkcu.com", + MinorAccountType = "FE01" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync(new AppUser() + { + AdEmail = "JohnnySmith@landmarkcu.com", + EmployeeId = "7624" + }); + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())) + .ReturnsAsync((Department)null); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.Mortgage); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(-1, activity.DepartmentId); + Assert.AreEqual("7624", activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.Is(x => x == "JohnnySmith@landmarkcu.com")), Times.Once); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Never); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.Is(x => x.EmployeeId == "7624")), Times.Once); + } + + [Test] + public async Task NewMortgageActivityStrategy_ExceptionReturnsBasic() + { + var externalActivity = new NewLoanSaleActivity() + { + SubjectAccountNbr = 1, + OriginatingPersonNbr = 1234, + BranchOrgName = "West Bend-Main Branch", + BranchOrgNbr = 400, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + MinorAccountType = "FE01" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ThrowsAsync(new Exception("OOPS")); + _mockUserService + .Setup(x => x.TryFindSingleUserByName(It.IsAny())) + .ThrowsAsync(new Exception("OOPS")); + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())) + .ReturnsAsync((Department)null); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.Mortgage); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(-1, activity.DepartmentId); + Assert.AreEqual(null, activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.Is(x => x == "JohnnySmith@landmarkcu.com")), Times.Never); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Once); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.IsAny()), Times.Never); + } + + [Test] + public async Task NewMortgageActivityStrategy_FirstMortgageSale_Ok() + { + var externalActivity = new NewLoanSaleActivity() + { + SubjectAccountNbr = 1, + OriginatingPersonNbr = 1234, + BranchOrgName = "West Bend-Main Branch", + BranchOrgNbr = 400, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = null, + AssociateLast = null, + AssociateMiddleInit = null, + EmployeeEmail = null, + MinorAccountType = "MA01" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync(new AppUser() + { + AdEmail = SystemAppUserConstants.FirstMortgageEmail, + EmployeeId = "1234" + }); + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync( + new Department() + { + Id = 39 + } + ); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.FirstMortgageReferral); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(39, activity.DepartmentId); + Assert.AreEqual("1234", activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.Is(x => x == SystemAppUserConstants.FirstMortgageEmail)), Times.Once); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Never); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.Is(x => x.EmployeeId == "1234")), Times.Once); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ExternalActivity/NewVehicleActivityStrategyTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ExternalActivity/NewVehicleActivityStrategyTest.cs new file mode 100644 index 0000000..e53bf68 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ExternalActivity/NewVehicleActivityStrategyTest.cs @@ -0,0 +1,235 @@ +using System; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity; +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; + +namespace Application.UnitTests.Services.ExternalActivity +{ + public class NewVehicleActivityStrategyTest + { + private Mock _mockUserService; + private Mock _mockDepartmentRepository; + private NewVehicleActivityStrategy _externalActivityService; + + [SetUp] + public void Setup() + { + _mockUserService = new Mock(); + _mockDepartmentRepository = new Mock(); + _externalActivityService = new NewVehicleActivityStrategy(_mockUserService.Object, _mockDepartmentRepository.Object); + } + + [Test] + public async Task NewVehicleActivityStrategy_Ok() + { + var externalActivity = new NewLoanSaleActivity() + { + SubjectAccountNbr = 1, + OriginatingPersonNbr = 1234, + BranchOrgName = "West Bend-Main Branch", + BranchOrgNbr = 400, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + EmployeeEmail = "JohnnySmith@landmarkcu.com", + MinorAccountType = "DV01" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync(new AppUser() + { + AdEmail = "JohnnySmith@landmarkcu.com", + EmployeeId = "7624" + }); + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync( + new Department() + { + Id = 10 + } + ); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.Vehicle); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(10, activity.DepartmentId); + Assert.AreEqual("7624", activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.Is(x => x == "JohnnySmith@landmarkcu.com")), Times.Once); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Never); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.Is(x => x.EmployeeId == "7624")), Times.Once); + } + + [Test] + public async Task NewVehicleActivityStrategy_NoEmail_Ok() + { + var externalActivity = new NewLoanSaleActivity() + { + SubjectAccountNbr = 1, + OriginatingPersonNbr = 1234, + BranchOrgName = "West Bend-Main Branch", + BranchOrgNbr = 400, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + MinorAccountType = "DV01" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync((AppUser) null); + _mockUserService.Setup(x => x.TryFindSingleUserByName(It.IsAny())).ReturnsAsync( + new AppUser() + { + AdEmail = "JohnSmith@landmarkcu.com", + EmployeeId = "7624" + }); + + + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync( + new Department() + { + Id = 10 + } + ); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.Vehicle); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(10, activity.DepartmentId); + Assert.AreEqual("7624", activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.IsAny()), Times.Never); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Once); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.Is(x => x.EmployeeId == "7624")), Times.Once); + } + + [Test] + public async Task NewVehicleActivityStrategy_NoUser_Ok() + { + var externalActivity = new NewLoanSaleActivity() + { + SubjectAccountNbr = 1, + OriginatingPersonNbr = 1234, + BranchOrgName = "West Bend-Main Branch", + BranchOrgNbr = 400, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + MinorAccountType = "DV01" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync((AppUser) null); + _mockUserService + .Setup(x => x.TryFindSingleUserByName(It.IsAny())) + .ReturnsAsync((AppUser)null); + + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())).ReturnsAsync( + new Department() + { + Id = 10 + }); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.Vehicle); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(-1, activity.DepartmentId); + Assert.AreEqual(null, activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.IsAny()), Times.Never); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Exactly(1)); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.IsAny()), Times.Never()); + } + + [Test] + public async Task NewVehicleActivityStrategy_NoDepartment_Ok() + { + var externalActivity = new NewLoanSaleActivity() + { + SubjectAccountNbr = 1, + OriginatingPersonNbr = 1234, + BranchOrgName = "West Bend-Main Branch", + BranchOrgNbr = 400, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + EmployeeEmail = "JohnnySmith@landmarkcu.com", + MinorAccountType = "DVO1" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ReturnsAsync(new AppUser() + { + AdEmail = "JohnnySmith@landmarkcu.com", + EmployeeId = "7624" + }); + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())) + .ReturnsAsync((Department)null); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.Vehicle); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(-1, activity.DepartmentId); + Assert.AreEqual("7624", activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.Is(x => x == "JohnnySmith@landmarkcu.com")), Times.Once); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Never); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.Is(x => x.EmployeeId == "7624")), Times.Once); + } + + [Test] + public async Task NewVehicleActivityStrategy_ExceptionReturnsBasic() + { + var externalActivity = new NewLoanSaleActivity() + { + SubjectAccountNbr = 1, + OriginatingPersonNbr = 1234, + BranchOrgName = "West Bend-Main Branch", + BranchOrgNbr = 400, + TaxOrgNbr = "300", + TaxPersonNbr = "200", + CurrentAccountStatus = "Active", + ActivityDateTime = DateTime.Parse("2021-09-10"), + AssociateFirst = "John", + AssociateLast = "Smith", + AssociateMiddleInit = "B", + MinorAccountType = "DV01" + }; + + _mockUserService.Setup(x => x.GetUserByAdEmailAsync(It.IsAny())).ThrowsAsync(new Exception("OOPS")); + _mockUserService + .Setup(x => x.TryFindSingleUserByName(It.IsAny())) + .ThrowsAsync(new Exception("OOPS")); + _mockDepartmentRepository.Setup(x => x.FindUserHomeDepartment(It.IsAny())) + .ReturnsAsync((Department)null); + + var activity = await _externalActivityService.MapToActivityManagerActivity(externalActivity); + Assert.True(activity.Product == Product.Vehicle); + Assert.True(activity.Type == ActivityType.Sale); + Assert.AreEqual(-1, activity.DepartmentId); + Assert.AreEqual(null, activity.EmployeeId); + + _mockUserService.Verify(x => x.GetUserByAdEmailAsync(It.Is(x => x == "JohnnySmith@landmarkcu.com")), Times.Never); + _mockUserService.Verify(x => x.TryFindSingleUserByName(It.IsAny()), Times.Once); + _mockDepartmentRepository.Verify(x => x.FindUserHomeDepartment(It.IsAny()), Times.Never); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/GapInsuranceActivityServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/GapInsuranceActivityServiceTest.cs new file mode 100644 index 0000000..419a3e8 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/GapInsuranceActivityServiceTest.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Microsoft.EntityFrameworkCore; +using Moq; +using NUnit.Framework; + +namespace Application.UnitTests.Services +{ + public class GapInsuranceActivityServiceTest + { + private GapInsuranceActivityService _gapInsuranceActivityService; + private IActivityRepository _activityRepository; + private IActivityDetailService _activityDetailService; + private IActivityService _activityService; + private IUnitOfWork _unitOfWork; + + [SetUp] + public void Setup() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .EnableSensitiveDataLogging(true) + .Options; + + var mockCurrentUserService = Mock.Of(); + + Mock.Get(mockCurrentUserService) + .Setup(service => service.GetCurrentUser()) + .Returns(new ApplicationUser() + { + Email = "JohnTest@test.com", + }); + + var context = new ApplicationDbContext(options, mockCurrentUserService, new DateTimeService()); + _activityRepository = new ActivityRepository(context); + _activityService = Mock.Of(); + _activityDetailService = Mock.Of(); + Mock.Get(_activityDetailService).Setup(x => x.SaveActivityDetailUpdate(It.IsAny())).ReturnsAsync(new ActivityDetailUpdate()); + Mock.Get(_activityService).Setup(x => x.ApplyStateChange(It.IsAny(), false)).ReturnsAsync(new Activity()); + _unitOfWork = new UnitOfWork(context); + _gapInsuranceActivityService = new GapInsuranceActivityService(_activityDetailService, _activityRepository, _activityService, _unitOfWork); + } + + [Test] + public async Task ExpectedRegularMatch() + { + var accepted = DateTime.UtcNow.AddDays(-1); + + var activities = new List() + { + new() + { + Accepted = accepted, + EmployeeId = CreateId(), + Product = Product.Vehicle, + SourceId = "12345", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "12345", + IsGapInsuranceEligible = true + } + }, + new() + { + Accepted = accepted, + EmployeeId = CreateId(), + Product = Product.GAPInsurance, + SourceId = "8675309", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "12345", + IsGapInsuranceEligible = false + } + } + }; + + var parentActivityId = await _activityRepository.SaveActivityAsync(activities[0]); + var accessoryActivityId = await _activityRepository.SaveActivityAsync(activities[1]); + await _unitOfWork.CompleteAsync(); + + var updated = await _gapInsuranceActivityService.MatchOrRemoveGapAccessories(); + Assert.AreEqual(2, updated); + var updateAccessoryActivity = await _activityRepository.GetActivityById(accessoryActivityId); + var updateParentActivity = await _activityRepository.GetActivityById(parentActivityId); + + Assert.AreEqual(2, updated); + Assert.AreEqual(updateAccessoryActivity.MatchingGapInsuranceActivityId, updateParentActivity.Id); + Assert.AreEqual(updateParentActivity.MatchingGapInsuranceActivityId, updateAccessoryActivity.Id); + Assert.IsNotNull(updateAccessoryActivity.Detail.GapInsuranceAddDate); + Assert.IsNotNull(updateParentActivity.Detail.GapInsuranceAddDate); + Assert.IsNull(updateAccessoryActivity.Detail.GapInsuranceRemoveDate); + Assert.IsNull(updateParentActivity.Detail.GapInsuranceRemoveDate); + } + + [TestCase(ActivityState.ManualReview, ActivityState.Accepted)] + [TestCase(ActivityState.Rejected, ActivityState.Accepted)] + [TestCase(ActivityState.Transferred, ActivityState.Accepted)] + [TestCase(ActivityState.Accepted, ActivityState.ManualReview)] + [TestCase(ActivityState.Accepted, ActivityState.Rejected)] + [TestCase(ActivityState.Accepted, ActivityState.Transferred)] + public async Task RegularMatchRemovedWhenEitherActivityStateBecomesNotAccepted(ActivityState accessoryActivityState, ActivityState parentActivityState) + { + var accepted = DateTime.UtcNow.AddDays(-1); + + var activities = new List() + { + new() + { + Accepted = accepted, + EmployeeId = CreateId(), + MatchingGapInsuranceActivityId = 2, + Product = Product.Vehicle, + SourceId = "Parent", + State = parentActivityState, + Detail = new ActivityDetail + { + AccountNumber = "12345", + IsGapInsuranceEligible = true, + GapInsuranceAddDate = accepted + } + }, + new() + { + Accepted = accepted, + EmployeeId = CreateId(), + MatchingGapInsuranceActivityId = 1, + Product = Product.GAPInsurance, + SourceId = "Accessory", + State = accessoryActivityState, + Detail = new ActivityDetail + { + AccountNumber = "12345", + IsGapInsuranceEligible = false, + GapInsuranceAddDate = accepted + } + } + }; + + var parentActivityId = await _activityRepository.SaveActivityAsync(activities[0]); + var accessoryActivityId = await _activityRepository.SaveActivityAsync(activities[1]); + await _unitOfWork.CompleteAsync(); + + var updated = await _gapInsuranceActivityService.MatchOrRemoveGapAccessories(); + + var removedAccessoryActivity = await _activityRepository.GetActivityById(accessoryActivityId); + var removedParentActivity = await _activityRepository.GetActivityById(parentActivityId); + + Assert.IsNotNull(removedAccessoryActivity.Detail.GapInsuranceRemoveDate); + Assert.IsNotNull(removedParentActivity.Detail.GapInsuranceRemoveDate); + + Assert.AreEqual(removedAccessoryActivity.MatchingGapInsuranceActivityId, 0); + Assert.AreEqual(removedParentActivity.MatchingGapInsuranceActivityId, 0); + } + + [Test] + public async Task RegularMatchRemovedWhenMatchedActivityCantBeFound() + { + var accepted = DateTime.UtcNow.AddDays(-1); + + var activities = new List() + { + new() + { + Accepted = accepted, + EmployeeId = CreateId(), + MatchingGapInsuranceActivityId = 2, + Product = Product.Vehicle, + SourceId = "Parent", + State = ActivityState.Accepted, + Detail = new ActivityDetail + { + AccountNumber = "12345", + IsGapInsuranceEligible = true, + GapInsuranceAddDate = accepted + } + } + }; + + var parentActivityId = await _activityRepository.SaveActivityAsync(activities[0]); + await _unitOfWork.CompleteAsync(); + + var updated = await _gapInsuranceActivityService.MatchOrRemoveGapAccessories(); + + var removedParentActivity = await _activityRepository.GetActivityById(parentActivityId); + + Assert.IsNotNull(removedParentActivity.Detail.GapInsuranceRemoveDate); + Assert.AreEqual(removedParentActivity.MatchingGapInsuranceActivityId, 0); + } + + private static string CreateId() => Guid.NewGuid().ToString("D"); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/GoalDispersalServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/GoalDispersalServiceTest.cs new file mode 100644 index 0000000..f381774 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/GoalDispersalServiceTest.cs @@ -0,0 +1,730 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApi.Services.DispersalStrategy; +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.API.Internal.Operations.IncentiveApi.Models.GoalDispersal; +using LCU.API.Internal.Operations.IncentiveApi.Extensions; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; + +namespace Application.UnitTests.Services +{ + public class GoalDispersalServiceTest + { + private Mock _mockGoalService; + private Mock _mockDepartmentRepository; + private Mock _mockNotificationService; + private Mock _mockUserService; + private Mock _mockGoalChangeRepository; + private Mock _mockDispersalStrategyService; + private IGoalDispersalService _goalDispersalService; + + [SetUp] + public void SetUp() + { + _mockGoalService = new Mock(); + _mockGoalChangeRepository = new Mock(); + _mockDepartmentRepository = new Mock(); + _mockUserService = new Mock(); + _mockDispersalStrategyService = new Mock(); + _mockNotificationService = new Mock(); + _goalDispersalService = new GoalDispersalService(_mockUserService.Object, _mockGoalService.Object, + _mockDepartmentRepository.Object, _mockNotificationService.Object, + _mockDispersalStrategyService.Object); + } + + [Test] + public async Task DisperseGoalsByExtrapolatingLastMonth_OK() + { + var goalList = new List() + { + new Goal() + { + DepartmentId = 10, + Product = Product.Mortgage, + Value = 200, + StartDate = DateTime.Parse("2021-1-1"), + EndDate = DateTime.Parse("2021-1-1").EndOfMonth() + }, + new Goal() + { + DepartmentId = 10, + Product = Product.Vehicle, + Value = 0, + StartDate = DateTime.Parse("2021-1-1"), + EndDate = DateTime.Parse("2021-1-1").EndOfMonth() + }, + new Goal() + { + DepartmentId = 10, + Product = Product.CheckCard, + Value = 0, + StartDate = DateTime.Parse("2021-1-1"), + EndDate = DateTime.Parse("2021-1-1").EndOfMonth() + }, + new Goal() + { + DepartmentId = 10, + Product = Product.DirectDeposit, + Value = 35, + StartDate = DateTime.Parse("2021-1-1"), + EndDate = DateTime.Parse("2021-1-1").EndOfMonth() + } + }; + + var departmentGoal = new DepartmentGoals(10, 2021); + departmentGoal.ApplyAll(goalList); + + var personnelGoals = new PersonnelGoals(10, 2021, 1, new List() + { + new AppUser() + { + PersonId = 5, + EmployeeId = "5E", + AdditionalPermissions = new List(), + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10 + } + }, + Role = Role.BM + } + }); + personnelGoals.ApplyAll(goalList.Select(x => + { + x.EmployeeId = "5E"; + return x; + }).ToList()); + + + _mockGoalService.Setup(x => x.GetAnnualDepartmentGoalsAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(departmentGoal); + _mockGoalService.Setup(x => x.GetMonthlyPersonnelGoalsAsync(It.IsAny())) + .ReturnsAsync(personnelGoals); + _mockDispersalStrategyService + .Setup(x => x.ExtrapolateFromExistingGoals(It.IsAny())) + .Returns(new GoalDispersalReceipt(10, GoalDispersalStrategy.ExtrapolateFromExistingGoals)); + _mockDispersalStrategyService + .Setup(x => x.DefaultWeightedDispersal(It.IsAny())) + .Returns(new GoalDispersalReceipt(10, GoalDispersalStrategy.DefaultDisperseWithWeights)); + + var results = await _goalDispersalService.DisperseGoalsByExtrapolatingLastMonth(10, 1, 2020); + Assert.AreEqual(GoalDispersalStrategy.ExtrapolateFromExistingGoals, results.Strategy); + _mockDispersalStrategyService.Verify(x => x.ExtrapolateFromExistingGoals(It.IsAny()), Times.Once); + _mockGoalService.Verify(x => x.SaveGoalRangeAsync(It.IsAny>()), Times.Once); + + } + + [TestCase(1, 2021, 12, 2020)] + [TestCase(2, 2021, 1, 2021)] + [TestCase(3, 2021, 2, 2021)] + [TestCase(4, 2021, 3, 2021)] + [TestCase(5, 2021, 4, 2021)] + [TestCase(6, 2021, 5, 2021)] + [TestCase(7, 2021, 6, 2021)] + [TestCase(8, 2021, 7, 2021)] + [TestCase(9, 2021, 8, 2021)] + [TestCase(10, 2021, 9, 2021)] + [TestCase(11, 2021, 10, 2021)] + [TestCase(12, 2021, 11, 2021)] + public async Task DisperseGoalsByExtrapolatingLastMonth_Calculates_Year_Month_OK(int month, int year, int lastMonth, int lastMonthYear) + { + var goalList = new List() + { + new Goal() + { + DepartmentId = 10, + Product = Product.Mortgage, + Value = 200, + StartDate = DateTime.Parse("2021-1-1"), + EndDate = DateTime.Parse("2021-1-1").EndOfMonth() + } + }; + + var departmentGoal = new DepartmentGoals(10, 2021); + departmentGoal.ApplyAll(goalList); + + var personnelGoals = new PersonnelGoals(10, 2021, 1, new List() + { + new AppUser() + { + PersonId = 5, + EmployeeId = "5E", + AdditionalPermissions = new List(), + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10 + } + }, + Role = Role.BM + } + }); + personnelGoals.ApplyAll(goalList.Select(x => + { + x.EmployeeId = "5E"; + return x; + }).ToList()); + + // The goals and personnel don't really matter for testing we just need some values + _mockGoalService.Setup(x => x.GetAnnualDepartmentGoalsAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(departmentGoal); + _mockGoalService.Setup(x => x.GetMonthlyPersonnelGoalsAsync(It.IsAny())) + .ReturnsAsync(personnelGoals); + _mockDispersalStrategyService + .Setup(x => x.ExtrapolateFromExistingGoals(It.IsAny())) + .Returns(new GoalDispersalReceipt(10, GoalDispersalStrategy.ExtrapolateFromExistingGoals)); + _mockDispersalStrategyService + .Setup(x => x.DefaultWeightedDispersal(It.IsAny())) + .Returns(new GoalDispersalReceipt(10, GoalDispersalStrategy.DefaultDisperseWithWeights)); + + var results = await _goalDispersalService.DisperseGoalsByExtrapolatingLastMonth(10, month, year); + Assert.NotNull(results); + _mockGoalService.Verify( + x => x.GetMonthlyPersonnelGoalsAsync( + It.Is(x => x.Month == month && x.Year == year)), Times.Once()); + _mockGoalService.Verify( + x => x.GetMonthlyPersonnelGoalsAsync( + It.Is(x => x.Month == lastMonth && x.Year == lastMonthYear)), + Times.Once()); + _mockGoalService.Verify( + X => X.GetAnnualDepartmentGoalsAsync(It.Is(x => x == 10), It.Is(x => x == year)), Times.AtLeastOnce); + _mockGoalService.Verify( + X => X.GetAnnualDepartmentGoalsAsync(It.Is(x => x == 10), It.Is(x => x == lastMonthYear)), + Times.AtLeastOnce); + _mockGoalService.Verify(x => x.SaveGoalRangeAsync(It.IsAny>()), Times.Once); + } + + [Test] + public async Task DisperseGoalsByExtrapolatingLastMonth_Defaults_OK() + { + var goalList = new List() + { + new Goal() + { + DepartmentId = 10, + Product = Product.Mortgage, + Value = 0, + StartDate = DateTime.Parse("2021-1-1"), + EndDate = DateTime.Parse("2021-1-1").EndOfMonth() + }, + new Goal() + { + DepartmentId = 10, + Product = Product.Vehicle, + Value = 0, + StartDate = DateTime.Parse("2021-1-1"), + EndDate = DateTime.Parse("2021-1-1").EndOfMonth() + }, + new Goal() + { + DepartmentId = 10, + Product = Product.CheckCard, + Value = 0, + StartDate = DateTime.Parse("2021-1-1"), + EndDate = DateTime.Parse("2021-1-1").EndOfMonth() + }, + new Goal() + { + DepartmentId = 10, + Product = Product.DirectDeposit, + Value = 0, + StartDate = DateTime.Parse("2021-1-1"), + EndDate = DateTime.Parse("2021-1-1").EndOfMonth() + } + }; + + var departmentGoal = new DepartmentGoals(10, 2021); + departmentGoal.ApplyAll(goalList); + + var personnelGoals = new PersonnelGoals(10, 2021, 1, new List() + { + new AppUser() + { + PersonId = 5, + EmployeeId = "5E", + AdditionalPermissions = new List(), + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10 + } + }, + Role = Role.BM + } + }); + personnelGoals.ApplyAll(goalList.Select(x => + { + x.EmployeeId = "5E"; + return x; + }).ToList()); + + // The goals and personnel don't really matter for testing we just need some values + _mockGoalService.Setup(x => x.GetAnnualDepartmentGoalsAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(departmentGoal); + _mockGoalService.Setup(x => x.GetMonthlyPersonnelGoalsAsync(It.IsAny())) + .ReturnsAsync(personnelGoals); + + _mockDispersalStrategyService + .Setup(x => x.ExtrapolateFromExistingGoals(It.IsAny())) + .Returns(new GoalDispersalReceipt(10, GoalDispersalStrategy.ExtrapolateFromExistingGoals)); + _mockDispersalStrategyService + .Setup(x => x.DefaultWeightedDispersal(It.IsAny())) + .Returns(new GoalDispersalReceipt(10, GoalDispersalStrategy.DefaultDisperseWithWeights)); + + var results = await _goalDispersalService.DisperseGoalsByExtrapolatingLastMonth(10, 1, 2020); + Assert.AreEqual(GoalDispersalStrategy.DefaultDisperseWithWeights, results.Strategy); + _mockDispersalStrategyService.Verify(x => x.DefaultWeightedDispersal(It.IsAny()), Times.Once); + _mockGoalService.Verify(x => x.SaveGoalRangeAsync(It.IsAny>()), Times.Once); + } + + [Test] + public async Task DisperseAllDepartmentGoalsForMonth_OK() + { + var goalList = new List() + { + new Goal() + { + DepartmentId = 10, + Product = Product.Mortgage, + Value = 200, + StartDate = DateTime.Parse("2021-1-1"), + EndDate = DateTime.Parse("2021-1-1").EndOfMonth() + } + }; + + var departmentGoal = new DepartmentGoals(10, 2021); + departmentGoal.ApplyAll(goalList); + + var personnelGoals = new PersonnelGoals(10, 2021, 1, new List() + { + new AppUser() + { + PersonId = 5, + EmployeeId = "5E", + AdditionalPermissions = new List(), + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10 + } + }, + Role = Role.BM + } + }); + personnelGoals.ApplyAll(goalList.Select(x => + { + x.EmployeeId = "5E"; + return x; + }).ToList()); + + + // The goals and personnel don't really matter for testing we just need some values + _mockGoalService.Setup(x => x.GetAnnualDepartmentGoalsAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(departmentGoal); + _mockGoalService.Setup(x => x.GetMonthlyPersonnelGoalsAsync(It.IsAny())) + .ReturnsAsync(personnelGoals); + + _mockDispersalStrategyService + .Setup(x => x.ExtrapolateFromExistingGoals(It.Is(x => x.DepartmentId == 0))) + .Returns(new GoalDispersalReceipt(0, GoalDispersalStrategy.ExtrapolateFromExistingGoals)); + _mockDispersalStrategyService + .Setup(x => x.ExtrapolateFromExistingGoals(It.Is(x => x.DepartmentId == 1))) + .Returns(new GoalDispersalReceipt(1, GoalDispersalStrategy.DefaultDisperseWithWeights)); + _mockDispersalStrategyService + .Setup(x => x.ExtrapolateFromExistingGoals(It.Is(x => x.DepartmentId == 2))) + .Returns(new GoalDispersalReceipt(2, GoalDispersalStrategy.ExtrapolateFromExistingGoals)); + _mockDispersalStrategyService + .Setup(x => x.ExtrapolateFromExistingGoals( + It.Is(x => x.DepartmentId == 3))) + .Throws(new Exception("Ooops")); + + _mockDepartmentRepository.Setup(x => x.GetAllDepartmentsAsync()).ReturnsAsync(new List() + { + new Department() + { + Id = 0, + DepartmentName = "Name0" + }, + new Department() + { + Id = 1, + DepartmentName = "Name1" + }, + new Department() + { + Id = 2, + }, + new Department() + { + Id = 3, + DepartmentName = "Name3" + }, + + }); + + var results = await _goalDispersalService.DisperseAllDepartmentGoalsForMonth(1, 2020); + Assert.Contains("0 - Name0",results.Keys); + Assert.Contains("1 - Name1", results.Keys); + Assert.Contains("2 - ", results.Keys); + Assert.Contains("3 - Name3", results.Keys); + + Assert.NotNull(results["0 - Name0"]); + Assert.NotNull(results["1 - Name1"]); + Assert.NotNull(results["2 - "]); + Assert.Null(results["3 - Name3"]); + + Assert.AreEqual(GoalDispersalStrategy.ExtrapolateFromExistingGoals, results["0 - Name0"].Strategy); + Assert.AreEqual(GoalDispersalStrategy.DefaultDisperseWithWeights,results["1 - Name1"].Strategy); + Assert.AreEqual(GoalDispersalStrategy.ExtrapolateFromExistingGoals,results["2 - "].Strategy); + } + + [Test] + public async Task CustomWeightedDispersal_OK() + { + var customWeightedDispersalRequest = new CustomWeightedDispersalRequest() + { + DepartmentId = 10, + month = 01, + year = 2021, + DispersalProduct = Product.Mortgage, + DispersalWeightByRole = new Dictionary(){ { "BM", .10 }, {"PFO", .25} } + }; + + var users = new List() + { + new AppUser() + { + EmployeeId = "ABM1", + PersonId = 1, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.ABM + } + } + }, + new AppUser() + { + EmployeeId = "BM1", + PersonId = 2, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.BM + } + } + }, + new AppUser() + { + EmployeeId = "BM2", + PersonId = 3, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.BM + } + } + }, + new AppUser() + { + EmployeeId = "PFO1", + PersonId = 4, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.PFO + } + } + }, + new AppUser() + { + EmployeeId = "PFO2", + PersonId = 5, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.PFO + } + } + }, + new AppUser() + { + EmployeeId = "PFO3", + PersonId = 6, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10, + Role = Role.PFO + } + } + } + }; + + var deptGoals = new DepartmentGoals(10, 2021); + deptGoals.ApplyAll(new List() + { + new Goal(){ + DepartmentId = 10, + Product = Product.Mortgage, + Value = 200, + StartDate = DateTime.Parse("2021-1-1"), + EndDate = DateTime.Parse("2021-1-1").EndOfMonth() + } + }); + + _mockGoalService.Setup(x => x.GetAnnualDepartmentGoalsAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(deptGoals); + + _mockUserService.Setup(x => x.FindDepartmentAssignments(It.IsAny())) + .ReturnsAsync(users); + + _mockDispersalStrategyService + .Setup(x => x.CustomWeightedDispersal(It.IsAny(), + It.IsAny>(), It.IsAny())) + .Returns(new GoalDispersalReceipt(10, GoalDispersalStrategy.CustomWeightedDispersal)); + + var receipt = await _goalDispersalService.CustomWeightedDispersal(customWeightedDispersalRequest); + Assert.AreEqual(10, receipt.DepartmentId); + Assert.AreEqual(GoalDispersalStrategy.CustomWeightedDispersal, receipt.Strategy); + + _mockDispersalStrategyService + .Verify(x => x.CustomWeightedDispersal( + It.Is(x => x.Equals(customWeightedDispersalRequest)), + It.Is>(x => x.Count == 6), It.Is(x => x == 200))); + + _mockGoalService.Verify(x => + x.GetAnnualDepartmentGoalsAsync(It.Is(x => x == 10), It.Is(x => x == 2021))); + + _mockUserService.Verify(x => x.FindDepartmentAssignments(It.Is(x => + x.Month == 1 && x.Year == 2021 && x.IncludeAllPersonnel == true && x.DepartmentId == 10))); + + _mockGoalService.Verify(x => x.SaveGoalRangeAsync(It.IsAny>()), Times.Once); + + } + + [Test] + public async Task ExecuteMonthStartDispersal_OK() + { + var request = new GoalDispersalRequest() + { + MonthsBack = 0, + GoalDispersalNotification = new List { "test1@landmarkcu.com", "test2@landmarkcu.com" } + }; + + _mockDepartmentRepository.Setup(x => x.GetAllDepartmentsAsync()).ReturnsAsync(new List() + { + new Department() + { + Id = 0, + DepartmentName = "Department0" + }, + new Department() + { + Id = 10, + Assignments = new List() + { + new UserDepartment() + { + Id = 100, + AppUserId = new Guid("00000000-1111-1111-0000-000000000000"), + DepartmentId = 10 + } + }, + DepartmentName = "Department10" + }, + new Department() + { + Id = 2, + Assignments = new List() + { + new UserDepartment() + { + Id = 22, + AppUserId = new Guid("00000000-2222-0000-0000-000000000000"), + DepartmentId = 2 + } + }, + DepartmentName = "Department2" + }, + new Department() + { + Id = 3, + Assignments = new List() + { + new UserDepartment() + { + Id = 33, + AppUserId = new Guid("00000000-3333-0000-0000-000000000000"), + DepartmentId = 3 + } + }, + DepartmentName = "Department3" + }, + }); + + var goalUpdate = new GoalChange() + { + Product = Product.Mortgage, + DepartmentId = 10, + Value = 300, + StartDate = DateTime.Parse("2021-01-01T20:00:00.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-01-31T23:59:00.000") + }; + + var goal2Update = new GoalChange() + { + Product = Product.Mortgage, + DepartmentId = 10, + Value = 301, + StartDate = DateTime.Parse("2021-01-01T10:00:00.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-01-31T21:59:00.000") + }; + + var goal3Update = new GoalChange() + { + Product = Product.Mortgage, + DepartmentId = 10, + Value = 302, + StartDate = DateTime.Parse("2021-01-01T11:30:00.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-01-31T03:59:00.000") + }; + + var goalChangeList = new List() { goalUpdate, goal2Update, goal3Update }; + + var goal1 = new Goal() + { + Product = Product.Mortgage, + DepartmentId = 10, + Value = 303, + StartDate = DateTime.Parse("2021-01-01T11:30:00.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-01-31T03:59:00.000") + }; + + var goalDeptList = new List() { goal1 }; + + _mockGoalService.Setup(x => x.SaveGoalRangeAsync(goalChangeList)).ReturnsAsync(goalDeptList); + + var deptGoals = new DepartmentGoals(10, 2021); + + deptGoals.ApplyAll(new List() + { + new Goal(){ + DepartmentId = 10, + Product = Product.Mortgage, + Value = 299, + StartDate = DateTime.Parse("2021-1-1"), + EndDate = DateTime.Parse("2021-1-31").EndOfMonth() + }, + new Goal(){ + DepartmentId = 10, + Product = Product.Mortgage, + Value = 500, + StartDate = DateTime.Parse("2021-2-1"), + EndDate = DateTime.Parse("2021-2-28").EndOfMonth() + } + }); + + _mockGoalService.Setup(x => x.GetAnnualDepartmentGoalsAsync(It.IsAny(), It.IsAny())).ReturnsAsync(deptGoals); + + var personnelGoals = new PersonnelGoals(10, 2021, 1, new List() + { + new AppUser() + { + PersonId = 5, + Id = new Guid("00000000-1111-1111-0000-000000000000"), + EmployeeId = "11111", + AdditionalPermissions = new List(), + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10 + } + }, + Role = Role.BM + }, + + new AppUser() + { + PersonId = 7, + Id = new Guid("00000000-7777-7777-0000-000000000000"), + EmployeeId = "77777", + AdditionalPermissions = new List(), + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 10 + } + }, + Role = Role.Teller + } + }); + + personnelGoals.ApplyAll(goalDeptList.Select(x => + { + x.EmployeeId = "11111"; + return x; + + }).ToList()); + + _mockGoalService.Setup(x => x.GetMonthlyPersonnelGoalsAsync(It.IsAny())).ReturnsAsync(personnelGoals); + + var receipt = new GoalDispersalReceipt(10, GoalDispersalStrategy.ExtrapolateFromExistingGoals) + { + GoalChanges = goalChangeList, + Strategy = GoalDispersalStrategy.ExtrapolateFromExistingGoals, + DepartmentId = 10, + GoalDispersalErrors = new List() + { + new GoalDispersalReceiptItem(GoalDispersalError.NewUserRequiresManualEntry, "77777", "New user required manual entry.") + { + EmployeeId = "77777", + ErrorType = GoalDispersalError.NewUserRequiresManualEntry + } + }, + }; + + _mockDispersalStrategyService.Setup(x => x.ExtrapolateFromExistingGoals(It.IsAny())).Returns(receipt); + + var results = await _goalDispersalService.DisperseAllDepartmentGoalsForMonth(2, 2021); + await _goalDispersalService.ExecuteMonthStartDispersal(request); + + _mockNotificationService.Verify(mock => mock.SendMonthStartGoalDispersalReceipt(results, request.GoalDispersalNotification), Times.Once); + + Assert.NotNull(results["0 - Department0"]); + Assert.NotNull(results["10 - Department10"]); + Assert.AreEqual(3, results["10 - Department10"].GoalChanges.Count()); + Assert.AreEqual(1, results["10 - Department10"].GoalDispersalErrors.Count()); + Assert.NotNull(results["2 - Department2"]); + Assert.NotNull(results["3 - Department3"]); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/GoalServiceRepositoryTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/GoalServiceRepositoryTest.cs new file mode 100644 index 0000000..9db3430 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/GoalServiceRepositoryTest.cs @@ -0,0 +1,736 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.API.Internal.Operations.IncentiveApi.Extensions; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Microsoft.EntityFrameworkCore; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApi.Services.DispersalStrategy; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.API.Internal.Operations.IncentiveApi.Models.GoalDispersal; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.API.Internal.Operations.IncentiveApi.Models; + +namespace Application.UnitTests.Services +{ + public class GoalServiceRepositoryTest + { + private GoalService _goalService; + private IGoalRepository _goalRepository; + private IGoalChangeRepository _goalChangeRepository; + private ICurrentUserService _currentUserService; + private Mock _mockDispersalStrategyService; + private IUserService _userService; + private IDepartmentRepository _departmentRepository; + + [SetUp] + public void Setup() + { + var mockDateTime = new DateTimeService(); + _currentUserService = Mock.Of(); + _userService = Mock.Of(); + + Mock.Get(_currentUserService).Setup(s => s.GetCurrentUser()).Returns(new ApplicationUser() + { + Email = "JohnTest@test.com" + }); + Mock.Get(_userService).Setup(x => x.GetUserByEmployeeIdAsync(It.IsAny())).ReturnsAsync(new AppUser { Id = Guid.NewGuid() }); + + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .EnableSensitiveDataLogging(true) + .Options; + var context = new ApplicationDbContext(options, _currentUserService, mockDateTime); + _goalRepository = new GoalRepository(context); + _goalChangeRepository = new GoalChangeRepository(context); + _mockDispersalStrategyService = new Mock(); + _departmentRepository = Mock.Of(); + + _goalService = new GoalService(_goalRepository, _goalChangeRepository, _currentUserService, _mockDispersalStrategyService.Object, + new UnitOfWork(context), _userService, _departmentRepository); + } + + [Test] + public async Task Save_And_Get_Goal() + { + var saved = await _goalService.GetGoalsAsync(new GoalSearchRequest()); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + var goal = new GoalChange() + { + Product = Product.CheckCard, + DepartmentId = 1, + PersonId = "C00002", + Value = 99, + StartDate = DateTime.Parse("2021-07-01T00:00:00.000"), + EndDate = DateTime.Parse("2021-07-31T23:59:00.000") + }; + var result = await _goalService.SaveGoalAsync(goal); + Assert.NotNull(result); + + saved = await _goalService.GetGoalsAsync(new GoalSearchRequest()); + Assert.NotNull(saved); + Assert.IsNotEmpty(saved); + } + + [Test] + public async Task Save_And_Get_Goal_Strips_Timestamp_Preserves_Kind() + { + var saved = await _goalService.GetGoalsAsync(new GoalSearchRequest()); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + var goal = new GoalChange() + { + Product = Product.CheckCard, + DepartmentId = 1, + PersonId = "C00002", + Value = 99, + StartDate = DateTime.SpecifyKind(DateTime.Parse("2021-07-01T10:37:00.000"), DateTimeKind.Local), + EndDate = DateTime.SpecifyKind(DateTime.Parse("2021-07-31T22:04:30.000"), DateTimeKind.Utc) + }; + var result = await _goalService.SaveGoalAsync(goal); + Assert.NotNull(result); + + saved = await _goalService.GetGoalsAsync(new GoalSearchRequest()); + Assert.NotNull(saved); + Assert.IsNotEmpty(saved); + Assert.AreEqual(DateTime.Parse("2021-07-01T00:00:00.000"), saved.First().StartDate); + Assert.AreEqual(DateTime.Parse("2021-07-31T23:59:59.000"), saved.First().EndDate); + Assert.AreEqual(goal.StartDate.Kind, saved.First().StartDate.Kind); + Assert.AreEqual(goal.EndDate.Kind, saved.First().EndDate.Kind); + } + + [Test] + public async Task GetDeptGoalsForRange() + { + var goal = new GoalChange() + { + Product = Product.CheckCard, + DepartmentId = 55, + Value = 98, + StartDate = DateTime.Parse("2021-07-01T00:00:00.000"), + EndDate = DateTime.Parse("2021-07-31T23:59:00.000") + }; + + await _goalService.SaveGoalAsync(goal); + + var user = new AppUser() + { + Role = Role.Admin, + AdEmail = "tester@test.com", + BaseRole = "Tester", + EmployeeId = "11771", + FullName = "Test McTester", + HomeDepartment = "HOME", + PersonId = 11771, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 55, + Department = new Department() + { + Id = 55, + State = DepartmentState.Active, + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code + }, + StartDate = DateTime.Parse("2021-07-01T00:00:00.000") + } + } + }; + + var range = new MonthRange(2021, 7, 2021, 8); + var found = await _goalService.GetEmployeeDepartmentsGoalsForMonthRangeAsync(user, range); + Assert.NotNull(found); + Assert.IsNotEmpty(found.First().Goals.ProductGoals); + Assert.IsNotEmpty(found.First().GoalAudit); + Assert.AreEqual(1, found.First().GoalAudit.Count()); + } + + [Test] + public async Task GetDeptGoalsForRange_NoAssignmentInMonthRange_ReturnsEmpty() + { + var user = new AppUser() + { + Role = Role.Admin, + AdEmail = "tester@test.com", + BaseRole = "Tester", + EmployeeId = "11771", + FullName = "Test McTester", + HomeDepartment = "HOME", + PersonId = 11771, + Departments = new List() + { + new UserDepartment() + { + DepartmentId = 55, + Department = new Department() + { + Id = 55, + State = DepartmentState.Active, + DepartmentName = "Test", + BusinessLineCode = BusinessLine.Operations.Code + }, + StartDate = DateTime.Parse("2021-07-01T00:00:00.000"), + EndDate = DateTime.Parse("2021-08-01T00:00:00.000") + } + } + }; + + var range = new MonthRange(2021, 5, 2021, 6); + var found = await _goalService.GetEmployeeDepartmentsGoalsForMonthRangeAsync(user, range); + Assert.IsEmpty(found); + } + + [Test] + public async Task SaveGoalRange_FirstDepartmentChanges_OK() + { + //Stub this so there is no exception, extrapolate generates no new Goals + _mockDispersalStrategyService + .Setup(x => x.ExtrapolateSingleDepartmentProduct(It.IsAny(), It.IsAny())) + .Returns(new GoalDispersalReceipt(1, GoalDispersalStrategy.ExtrapolateSingleDepartmentProduct)); + + Mock.Get(_userService).Setup(x => x.FindDepartmentAssignments(It.IsAny())) + .ReturnsAsync(new List() + { + new AppUser() + { + EmployeeId = "C4000" + }, + new AppUser() + { + EmployeeId = "C5000" + } + }); + + var saved = await _goalService.GetGoalsAsync(new GoalSearchRequest()); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + var goal = new GoalChange() + { + Product = Product.CheckCard, + DepartmentId = 1, + Value = 98, + StartDate = DateTime.Parse("2021-07-01T20:00:00.000"), + EndDate = DateTime.Parse("2021-07-31T23:59:00.000") + }; + var goal2 = new GoalChange() + { + Product = Product.CheckCard, + DepartmentId = 1, + Value = 99, + StartDate = DateTime.Parse("2021-08-01T10:00:00.000"), + EndDate = DateTime.Parse("2021-08-31T21:59:00.000") + }; + var goal3 = new GoalChange() + { + Product = Product.CheckCard, + DepartmentId = 1, + Value = 100, + StartDate = DateTime.Parse("2020-09-01T11:30:00.000"), + EndDate = DateTime.Parse("2020-09-30T03:59:00.000") + }; + var goalDiffDept = new GoalChange() + { + Product = Product.CheckCard, + DepartmentId = 2, + Value = 101, + StartDate = DateTime.Parse("2021-09-01T12:30:00.000"), + EndDate = DateTime.Parse("2021-09-30T20:59:00.000") + }; + var goalPersonnel = new GoalChange() + { + Product = Product.CheckCard, + DepartmentId = 1, + PersonId = "C00002", + Value = 102, + StartDate = DateTime.Parse("2021-07-01T13:00:13.000"), + EndDate = DateTime.Parse("2021-07-31T20:59:00.000") + }; + var result = await _goalService.SaveGoalRangeAsync(new List() + { + goal, goal2, goal3, goalDiffDept, goalPersonnel + }); + Assert.NotNull(result); + Assert.AreEqual(5, result.Count); + + //Only Re-disperse if goal was updated not added for the first time + _mockDispersalStrategyService + .Verify(x => x.ExtrapolateSingleDepartmentProduct(It.Is(x => x.DepartmentId == 1), It.IsAny()), Times.Exactly(0)); + + + Assert.True(result.TrueForAll(x => x.StartDate.TimeOfDay.ToString().Equals("00:00:00"))); + Assert.True(result.TrueForAll(x => x.EndDate.TimeOfDay.ToString().Equals("23:59:59"))); + + var startDate = goal.StartDate; + + var goals = await _goalService.GetAnnualDepartmentGoalsAsync(goal.DepartmentId, startDate.Year); + Assert.NotNull(goals); + var checkCardGoals = goals.MonthlyGoals[goal.StartDate.Month - 1].ProductGoals[Product.CheckCard]; + Assert.AreEqual(goal.Value, checkCardGoals); + } + + [Test] + public async Task SaveGoalRange_ReDisperseDepartmentGoals_OK() + { + var julyGoalDispersalReceipt = + new GoalDispersalReceipt(1, GoalDispersalStrategy.ExtrapolateSingleDepartmentProduct); + + Mock.Get(_userService).Setup(x => x.FindDepartmentAssignments(It.IsAny())).ReturnsAsync( + new List(){ + new AppUser() + { + EmployeeId = "C00002" + }, + new AppUser() + { + EmployeeId = "C00003" + }, + new AppUser() + { + EmployeeId = "C00004" + } + }); + julyGoalDispersalReceipt.GoalChanges = new List() + { + new GoalChange() + { + Product = Product.Mortgage, + DepartmentId = 1, + PersonId = "C00002", + Value = 110, + StartDate = DateTime.Parse("2021-07-01T13:00:13.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-07-31T20:59:00.000") + }, + new GoalChange() + { + Product = Product.Mortgage, + DepartmentId = 1, + PersonId = "C00003", + Value = 111, + StartDate = DateTime.Parse("2021-07-01T13:00:13.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-07-31T20:59:00.000") + }, + new GoalChange() + { + Product = Product.Mortgage, + DepartmentId = 1, + PersonId = "C00004", + Value = 112, + StartDate = DateTime.Parse("2021-07-01T13:00:13.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-07-31T20:59:00.000") + } + }; + + _mockDispersalStrategyService + .Setup(x => x.ExtrapolateSingleDepartmentProduct(It.Is(request => request.DepartmentId == 1 && request.DepartmentGoalStartDate.Month == 7), It.IsAny())) + .Returns(julyGoalDispersalReceipt); + + //For August + var augGoalDispersalReceipt = + new GoalDispersalReceipt(1, GoalDispersalStrategy.ExtrapolateSingleDepartmentProduct); + augGoalDispersalReceipt.GoalChanges = julyGoalDispersalReceipt.GoalChanges.Select(x => new GoalChange() + { + Product = x.Product, + DepartmentId = x.DepartmentId, + PersonId = x.PersonId, + Value = x.Value * 2, + StartDate = DateTime.Parse("2021-08-01T13:00:13.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-08-31T20:59:00.000") + }).ToList(); + + _mockDispersalStrategyService + .Setup(x => x.ExtrapolateSingleDepartmentProduct(It.Is(x => x.DepartmentId == 1 && x.DepartmentGoalStartDate.Month == 8), It.IsAny())) + .Returns(augGoalDispersalReceipt); + + //For September + var sepGoalDispersalReceipt = + new GoalDispersalReceipt(1, GoalDispersalStrategy.ExtrapolateSingleDepartmentProduct); + sepGoalDispersalReceipt.GoalChanges = julyGoalDispersalReceipt.GoalChanges.Select(x => new GoalChange() + { + Product = x.Product, + DepartmentId = x.DepartmentId, + PersonId = x.PersonId, + Value = x.Value * 3, + StartDate = DateTime.Parse("2021-09-01T13:00:13.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-09-30T20:59:00.000") + }).ToList(); + + _mockDispersalStrategyService + .Setup(x => x.ExtrapolateSingleDepartmentProduct(It.Is(x => x.DepartmentId == 1 && x.DepartmentGoalStartDate.Month == 9), It.IsAny())) + .Returns(sepGoalDispersalReceipt); + + _mockDispersalStrategyService + .Setup(x => x.ExtrapolateSingleDepartmentProduct(It.Is(x => x.DepartmentId == 2), It.IsAny())) + .Returns(new GoalDispersalReceipt(2, GoalDispersalStrategy.ExtrapolateSingleDepartmentProduct)); + + var saved = await _goalService.GetGoalsAsync(new GoalSearchRequest()); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + var goal = new GoalChange() + { + Product = Product.Mortgage, + DepartmentId = 1, + Value = 98, + StartDate = DateTime.Parse("2021-07-01T20:00:00.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-07-31T23:59:00.000") + }; + var goal2 = new GoalChange() + { + Product = Product.Mortgage, + DepartmentId = 1, + Value = 99, + StartDate = DateTime.Parse("2021-08-01T10:00:00.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-08-31T21:59:00.000") + }; + var goal3 = new GoalChange() + { + Product = Product.Mortgage, + DepartmentId = 1, + Value = 100, + StartDate = DateTime.Parse("2021-09-01T11:30:00.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-09-30T03:59:00.000") + }; + var goalDiffDept = new GoalChange() + { + Product = Product.CheckCard, + DepartmentId = 2, + Value = 101, + StartDate = DateTime.Parse("2021-09-01T12:30:00.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-09-30T20:59:00.000") + }; + var goalPersonnel = new GoalChange() + { + Product = Product.CheckCard, + DepartmentId = 1, + PersonId = "C00002", + Value = 102, + StartDate = DateTime.Parse("2021-07-01T13:00:13.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-07-31T20:59:00.000") + }; + var result = await _goalService.SaveGoalRangeAsync(new List() + { + goal, goal2, goal3, goalDiffDept, goalPersonnel + }); + + var goalUpdate = new GoalChange() + { + Product = Product.Mortgage, + DepartmentId = 1, + Value = 300, + StartDate = DateTime.Parse("2021-07-01T20:00:00.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-07-31T23:59:00.000") + }; + var goal2Update = new GoalChange() + { + Product = Product.Mortgage, + DepartmentId = 1, + Value = 301, + StartDate = DateTime.Parse("2021-08-01T10:00:00.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-08-31T21:59:00.000") + }; + var goal3Update = new GoalChange() + { + Product = Product.Mortgage, + DepartmentId = 1, + Value = 302, + StartDate = DateTime.Parse("2021-09-01T11:30:00.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-09-30T03:59:00.000") + }; + + var resultRedispersal = await _goalService.SaveGoalRangeAsync(new List() + { + goalUpdate, goal2Update, goal3Update + }); + + Assert.NotNull(result); + Assert.AreEqual(5, result.Count); + + Assert.NotNull(resultRedispersal); + Assert.AreEqual(12, resultRedispersal.Count); + + var goalChanges = await _goalChangeRepository.GetGoalChangesAsync(); + Assert.AreEqual(17, goalChanges.Count()); + Assert.True(result.TrueForAll(x => x.StartDate.TimeOfDay.ToString().Equals("00:00:00"))); + Assert.True(result.TrueForAll(x => x.EndDate.TimeOfDay.ToString().Equals("23:59:59"))); + + var startDate = goal.StartDate; + + _mockDispersalStrategyService + .Verify(x => x.ExtrapolateSingleDepartmentProduct(It.Is(x => x.DepartmentId == 1), It.IsAny()), Times.Exactly(3)); + + _mockDispersalStrategyService + .Verify(x => x.ExtrapolateSingleDepartmentProduct(It.Is(x => x.DepartmentId == 2), It.IsAny()), Times.Exactly(0)); + + var goals = await _goalService.GetAnnualDepartmentGoalsAsync(1, 2021); + Assert.NotNull(goals); + + var mortgageGoals = goals.MonthlyGoals[6].ProductGoals[Product.Mortgage]; + Assert.AreEqual(300, mortgageGoals); + + mortgageGoals = goals.MonthlyGoals[7].ProductGoals[Product.Mortgage]; + Assert.AreEqual(301, mortgageGoals); + + mortgageGoals = goals.MonthlyGoals[8].ProductGoals[Product.Mortgage]; + Assert.AreEqual(302, mortgageGoals); + + + var test = await _goalRepository.GetGoalsAsync(null); + /*These were not calculated as we mocked the dispersal service. Just verifying that the receipt goal + change were applied. */ + var julyPersonnelGoals = await _goalService.GetMonthlyPersonnelGoalsAsync( + new DepartmentPersonnelSearchRequest() + { + DepartmentId = 1, + Month = 7, + Year = 2021 + }); + Assert.AreEqual(110, julyPersonnelGoals.Goals["C00002"].ProductGoals[Product.Mortgage]); + Assert.AreEqual(111, julyPersonnelGoals.Goals["C00003"].ProductGoals[Product.Mortgage]); + Assert.AreEqual(112, julyPersonnelGoals.Goals["C00004"].ProductGoals[Product.Mortgage]); + + var augPersonnelGoals = await _goalService.GetMonthlyPersonnelGoalsAsync( + new DepartmentPersonnelSearchRequest() + { + DepartmentId = 1, + Month = 8, + Year = 2021 + }); + Assert.AreEqual(220, augPersonnelGoals.Goals["C00002"].ProductGoals[Product.Mortgage]); + Assert.AreEqual(222, augPersonnelGoals.Goals["C00003"].ProductGoals[Product.Mortgage]); + Assert.AreEqual(224, augPersonnelGoals.Goals["C00004"].ProductGoals[Product.Mortgage]); + + var sepPersonnelGoals = await _goalService.GetMonthlyPersonnelGoalsAsync( + new DepartmentPersonnelSearchRequest() + { + DepartmentId = 1, + Month = 9, + Year = 2021 + }); + Assert.AreEqual(330, sepPersonnelGoals.Goals["C00002"].ProductGoals[Product.Mortgage]); + Assert.AreEqual(333, sepPersonnelGoals.Goals["C00003"].ProductGoals[Product.Mortgage]); + Assert.AreEqual(336, sepPersonnelGoals.Goals["C00004"].ProductGoals[Product.Mortgage]); + } + + [Test] + public async Task SaveGoalRange_ReDisperseDepartmentGoals_OnlyIfDeptGoalChanges_OK() + { + var julygoalDispersalReceipt = + new GoalDispersalReceipt(1, GoalDispersalStrategy.ExtrapolateSingleDepartmentProduct); + + Mock.Get(_userService).Setup(x => x.FindDepartmentAssignments(It.IsAny())) + .ReturnsAsync( + new List() + { + new AppUser() + { + EmployeeId = "C00002" + }, + new AppUser() + { + EmployeeId = "C00003" + }, + new AppUser() + { + EmployeeId = "C00004" + } + }); + julygoalDispersalReceipt.GoalChanges = new List() + { + new GoalChange() + { + Product = Product.Mortgage, + DepartmentId = 1, + PersonId = "C00002", + Value = 110, + StartDate = DateTime.Parse("2021-07-01T13:00:13.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-07-31T20:59:00.000") + }, + new GoalChange() + { + Product = Product.Mortgage, + DepartmentId = 1, + PersonId = "C00003", + Value = 111, + StartDate = DateTime.Parse("2021-07-01T13:00:13.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-07-31T20:59:00.000") + }, + new GoalChange() + { + Product = Product.Mortgage, + DepartmentId = 1, + PersonId = "C00004", + Value = 112, + StartDate = DateTime.Parse("2021-07-01T13:00:13.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-07-31T20:59:00.000") + } + }; + + _mockDispersalStrategyService + .Setup(x => x.ExtrapolateSingleDepartmentProduct( + It.Is(request => + request.DepartmentId == 1 && request.DepartmentGoalStartDate.Month == 7), + It.IsAny())) + .Returns(julygoalDispersalReceipt); + + //For August + var augGoalDispersalReceipt = + new GoalDispersalReceipt(1, GoalDispersalStrategy.ExtrapolateSingleDepartmentProduct); + augGoalDispersalReceipt.GoalChanges = julygoalDispersalReceipt.GoalChanges.Select(x => new GoalChange() + { + Product = x.Product, + DepartmentId = x.DepartmentId, + PersonId = x.PersonId, + Value = x.Value * 2, + StartDate = DateTime.Parse("2021-08-01T13:00:13.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-08-31T20:59:00.000") + }).ToList(); + + _mockDispersalStrategyService + .Setup(x => x.ExtrapolateSingleDepartmentProduct( + It.Is(x => + x.DepartmentId == 1 && x.DepartmentGoalStartDate.Month == 8), It.IsAny())) + .Returns(augGoalDispersalReceipt); + + //For September + var sepGoalDispersalReceipt = + new GoalDispersalReceipt(1, GoalDispersalStrategy.ExtrapolateSingleDepartmentProduct); + sepGoalDispersalReceipt.GoalChanges = julygoalDispersalReceipt.GoalChanges.Select(x => new GoalChange() + { + Product = x.Product, + DepartmentId = x.DepartmentId, + PersonId = x.PersonId, + Value = x.Value * 3, + StartDate = DateTime.Parse("2021-09-01T13:00:13.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-09-30T20:59:00.000") + }).ToList(); + + _mockDispersalStrategyService + .Setup(x => x.ExtrapolateSingleDepartmentProduct( + It.Is(x => + x.DepartmentId == 1 && x.DepartmentGoalStartDate.Month == 9), It.IsAny())) + .Returns(sepGoalDispersalReceipt); + + _mockDispersalStrategyService + .Setup(x => x.ExtrapolateSingleDepartmentProduct( + It.Is(x => x.DepartmentId == 2), + It.IsAny())) + .Returns(new GoalDispersalReceipt(2, GoalDispersalStrategy.ExtrapolateSingleDepartmentProduct)); + + var saved = await _goalService.GetGoalsAsync(new GoalSearchRequest()); + Assert.NotNull(saved); + Assert.IsEmpty(saved); + + var goal = new GoalChange() + { + Product = Product.Mortgage, + DepartmentId = 1, + Value = 98, + StartDate = DateTime.Parse("2021-07-01T20:00:00.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-07-31T23:59:00.000") + }; + var goal2 = new GoalChange() + { + Product = Product.Mortgage, + DepartmentId = 1, + Value = 99, + StartDate = DateTime.Parse("2021-08-01T10:00:00.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-08-31T21:59:00.000") + }; + var goal3 = new GoalChange() + { + Product = Product.Mortgage, + DepartmentId = 1, + Value = 100, + StartDate = DateTime.Parse("2021-09-01T11:30:00.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-09-30T03:59:00.000") + }; + var goalDiffDept = new GoalChange() + { + Product = Product.CheckCard, + DepartmentId = 2, + Value = 101, + StartDate = DateTime.Parse("2021-09-01T12:30:00.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-09-30T20:59:00.000") + }; + var goalPersonnel = new GoalChange() + { + Product = Product.CheckCard, + DepartmentId = 1, + PersonId = "C00002", + Value = 102, + StartDate = DateTime.Parse("2021-07-01T13:00:13.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-07-31T20:59:00.000") + }; + var result = await _goalService.SaveGoalRangeAsync(new List() + { + goal, goal2, goal3, goalDiffDept, goalPersonnel + }); + + var goalUpdate = new GoalChange() + { + Product = Product.Mortgage, + DepartmentId = 1, + Value = 98, + StartDate = DateTime.Parse("2021-07-01T20:00:00.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-07-31T23:59:00.000") + }; + var goal2Update = new GoalChange() + { + Product = Product.Mortgage, + DepartmentId = 1, + Value = 99, + StartDate = DateTime.Parse("2021-08-01T10:00:00.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-08-31T21:59:00.000") + }; + var goal3Update = new GoalChange() + { + Product = Product.Mortgage, + DepartmentId = 1, + Value = 100, + StartDate = DateTime.Parse("2021-09-01T11:30:00.000").StartOfMonth(), + EndDate = DateTime.Parse("2021-09-30T03:59:00.000") + }; + + var resultRedispersal = await _goalService.SaveGoalRangeAsync(new List() + { + goalUpdate, goal2Update, goal3Update + }); + + Assert.NotNull(result); + Assert.AreEqual(5, result.Count); + + //Only duplicate goal changes are saved, and issue no new personnel changes + Assert.NotNull(resultRedispersal); + Assert.AreEqual(3, resultRedispersal.Count); + + var goalChanges = await _goalChangeRepository.GetGoalChangesAsync(); + Assert.AreEqual(8, goalChanges.Count()); + + var startDate = goal.StartDate; + + //Verify that we did not Redisperse + _mockDispersalStrategyService.Verify( + x => x.ExtrapolateSingleDepartmentProduct( + It.Is(x => x.DepartmentId == 1), + It.IsAny()), Times.Exactly(0)); + + _mockDispersalStrategyService.Verify( + x => x.ExtrapolateSingleDepartmentProduct( + It.Is(x => x.DepartmentId == 2), + It.IsAny()), Times.Exactly(0)); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/GoalServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/GoalServiceTest.cs new file mode 100644 index 0000000..3d8de83 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/GoalServiceTest.cs @@ -0,0 +1,361 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApi.Services.DispersalStrategy; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace Application.UnitTests.Services +{ + public class GoalServiceTest + { + private GoalService _goalService; + private IGoalRepository _goalRepository; + private IGoalChangeRepository _goalChangeRepository; + private ICurrentUserService _currentUserService; + private IDepartmentRepository _departmentRepository; + private Mock _mockDispersalStrategyService; + private IUserService _userService; + private IUnitOfWork _unitOfWork; + + [SetUp] + public void Setup() + { + _currentUserService = Mock.Of(); + _userService = Mock.Of(); + + Mock.Get(_currentUserService).Setup(s => s.GetCurrentUser()).Returns(new ApplicationUser() + { + Email = "JohnTest@test.com" + }); + + _goalRepository = Mock.Of(); + _goalChangeRepository = Mock.Of(); + _unitOfWork = Mock.Of(); + _mockDispersalStrategyService = new Mock(); + _departmentRepository = Mock.Of(); + + _goalService = new GoalService(_goalRepository, _goalChangeRepository, _currentUserService, _mockDispersalStrategyService.Object, + _unitOfWork, _userService, _departmentRepository); + } + [Test] + public async Task GetMonthlyPersonnelGoals_Basic() + { + var searchRequest = new DepartmentPersonnelSearchRequest() + { + DepartmentId = 55, + Year = 2020, + Month = 3 + }; + var users = new List() + { + new AppUser() + { + FullName = "Tester Doe", + EmployeeId = "11771" + } + }; + var goals = new List() + { + new Goal() + { + Product = Product.Mortgage, + Value = 4, + EmployeeId = "11771", + DepartmentId = 55 + }, + new Goal() + { + Product = Product.Vehicle, + Value = 4, + EmployeeId = "11771", + DepartmentId = 55 + } + }; + + Mock.Get(_userService).Setup(s => s.FindDepartmentAssignments(searchRequest)) + .ReturnsAsync(users); + + Mock.Get(_goalRepository).Setup(s => s.FindMonthlyPersonnelGoals(searchRequest.DepartmentId, users, + searchRequest.Year, searchRequest.Month)) + .ReturnsAsync(goals); + + var result = await _goalService.GetMonthlyPersonnelGoalsAsync(searchRequest); + Assert.NotNull(result); + Assert.NotNull(result.GoalAudit); + Assert.IsNotEmpty(result.GoalAudit); + } + + [Test] + public async Task GetEmployeeDepartmentsGoalsForMonth_Basic() + { + var range = new MonthRange(2021, 1); + + var user = new AppUser() + { + FullName = "Tester Doe", + EmployeeId = "11771", + Departments = new List() + }; + var userDept = new UserDepartment() + { + Role = Role.Teller, + DepartmentId = 55, + Employee = user, + IsHome = true, + SlidingPayout = 100, + Department = new Department() + { + Id = 55, + DepartmentName = "Dept1", + State = DepartmentState.Active + } + }; + user.Departments.Add(userDept); + + var goals = new List() + { + new Goal() + { + Product = Product.Mortgage, + Value = 4, + EmployeeId = "11771", + DepartmentId = 55 + }, + new Goal() + { + Product = Product.Vehicle, + Value = 4, + EmployeeId = "11771", + DepartmentId = 55 + } + }; + + Mock.Get(_goalRepository).Setup(s => + s.FindDepartmentGoalsByMonthRange(userDept.DepartmentId, It.IsAny())) + .ReturnsAsync(goals); + + var result = await _goalService.GetEmployeeDepartmentsGoalsForMonthRangeAsync(user, range); + + Assert.NotNull(result); + Assert.NotNull(result); + } + + [Test] + public async Task GetEmployeeGoalsForMonth_Basic() + { + var searchRequest = new DepartmentPersonnelSearchRequest() + { + DepartmentId = 55, + Year = 2020, + Month = 3 + }; + var range = new MonthRange(2021, 1); + + var user = new AppUser() + { + FullName = "Tester Doe", + EmployeeId = "11771", + Departments = new List() + }; + var userDept = new UserDepartment() + { + Role = Role.Teller, + DepartmentId = 55, + Employee = user, + IsHome = true, + SlidingPayout = 100, + Department = new Department() + { + Id = 55, + DepartmentName = "Dept1", + State = DepartmentState.Active + } + }; + user.Departments.Add(userDept); + + var goals = new List() + { + new Goal() + { + Product = Product.Mortgage, + Value = 4, + EmployeeId = "11771", + DepartmentId = 55 + }, + new Goal() + { + Product = Product.Vehicle, + Value = 4, + EmployeeId = "11771", + DepartmentId = 55 + } + }; + + Mock.Get(_goalRepository).Setup(s => + s.FindPersonnelGoalsByMonthRange(userDept.DepartmentId, It.IsAny>(), range)) + .ReturnsAsync(goals); + + var result = await _goalService.GetEmployeeGoalsForMonthRangeAsync(user, range); + Assert.NotNull(result); + Assert.NotNull(result.GoalAudit); + Assert.IsNotEmpty(result.GoalAudit); + } + + [Test] + public async Task GetDeptGoals() + { + var goal = new Goal() + { + Product = Product.CheckCard, + DepartmentId = 1, + Value = 98, + StartDate = DateTime.Parse("2021-07-01T00:00:00.000"), + EndDate = DateTime.Parse("2021-07-31T23:59:00.000") + }; + var goal2 = new Goal() + { + Product = Product.CheckCard, + DepartmentId = 1, + Value = 99, + StartDate = DateTime.Parse("2021-08-01T00:00:00.000"), + EndDate = DateTime.Parse("2021-08-31T23:59:00.000") + }; + var goal3 = new Goal() + { + Product = Product.CheckCard, + DepartmentId = 1, + Value = 100, + StartDate = DateTime.Parse("2020-09-01T00:00:00.000"), + EndDate = DateTime.Parse("2020-09-30T23:59:00.000") + }; + var goalDiffDept = new Goal() + { + Product = Product.CheckCard, + DepartmentId = 2, + Value = 101, + StartDate = DateTime.Parse("2021-09-01T00:00:00.000"), + EndDate = DateTime.Parse("2021-09-30T23:59:00.000") + }; + var inputGoals = new List() + { + goal, goal2, goal3, goalDiffDept + }; + + var startDate = goal.StartDate; + Mock.Get(_goalRepository).Setup(mock => mock.FindAnnualDepartmentGoals(It.IsAny(), It.IsAny())) + .ReturnsAsync(inputGoals); + + var goals = await _goalService.GetAnnualDepartmentGoalsAsync(goal.DepartmentId, startDate.Year); + Assert.NotNull(goals); + var checkCardGoals = goals.MonthlyGoals[goal.StartDate.Month - 1].ProductGoals[Product.CheckCard]; + Assert.AreEqual(goal.Value, checkCardGoals); + } + + [Test] + public async Task GetAllDeptGoals() + { + var goal = new Goal() + { + Product = Product.CheckCard, + DepartmentId = 1, + Value = 98, + StartDate = DateTime.Parse("2021-07-01T00:00:00.000"), + EndDate = DateTime.Parse("2021-07-31T23:59:00.000") + }; + var goal2 = new Goal() + { + Product = Product.CheckCard, + DepartmentId = 1, + Value = 99, + StartDate = DateTime.Parse("2021-08-01T00:00:00.000"), + EndDate = DateTime.Parse("2021-08-31T23:59:00.000") + }; + var goalDiffDept = new Goal() + { + Product = Product.CheckCard, + DepartmentId = 2, + Value = 100, + StartDate = DateTime.Parse("2020-09-01T00:00:00.000"), + EndDate = DateTime.Parse("2020-09-30T23:59:00.000") + }; + var goalDiffDept2 = new Goal() + { + Product = Product.CheckCard, + DepartmentId = 2, + Value = 101, + StartDate = DateTime.Parse("2021-09-01T00:00:00.000"), + EndDate = DateTime.Parse("2021-09-30T23:59:00.000") + }; + var inputGoals = new List() + { + goal, goal2 + }; + var inputGoals2 = new List() + { + goalDiffDept, goalDiffDept2 + }; + var departments = new List() + { + new Department() + { + Id = 1, + DepartmentName = "dept1" + }, + new Department() + { + Id = 2, + DepartmentName = "dept2" + } + }; + + var startDate = goal.StartDate; + Mock.Get(_goalRepository).Setup(mock => mock.FindAnnualDepartmentGoals(1, It.IsAny())) + .ReturnsAsync(inputGoals); + Mock.Get(_goalRepository).Setup(mock => mock.FindAnnualDepartmentGoals(2, It.IsAny())) + .ReturnsAsync(inputGoals2); + Mock.Get(_departmentRepository).Setup(mock => mock.GetAllDepartmentsAsync()).ReturnsAsync(departments); + + var goals = await _goalService.GetAllAnnualDepartmentGoalsAsync(startDate.Year); + Assert.NotNull(goals); + var checkCardGoals = goals.First().MonthlyGoals[goal.StartDate.Month - 1].ProductGoals[Product.CheckCard]; + Assert.AreEqual(goal.Value, checkCardGoals); + } + + [Test] + public async Task GetGoalChanges_Basic() + { + var goals = new List() + { + new GoalChange() + { + Product = Product.Mortgage, + Value = 4, + DepartmentId = 55 + }, + new GoalChange() + { + Product = Product.Vehicle, + Value = 4, + DepartmentId = 55 + } + }; + + Mock.Get(_goalChangeRepository).Setup(s => s.GetGoalChangesAsync()) + .ReturnsAsync(goals); + + var result = await _goalService.GetGoalChangesAsync(); + Assert.NotNull(result); + Assert.IsNotEmpty(result); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/MicroApiServices/FileOperationsApiServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/MicroApiServices/FileOperationsApiServiceTest.cs new file mode 100644 index 0000000..fa9e4fe --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/MicroApiServices/FileOperationsApiServiceTest.cs @@ -0,0 +1,44 @@ +using System; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApi.DataAccess; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.Library.Internal.Exceptions; +using Moq; +using NUnit.Framework; + +namespace Application.UnitTests.Services.MicroApiServices +{ + public class FileOperationsApiServiceTest + { + private FileOperationsApiService _fileOperationsApiService; + private Mock _mockFileOperationsDao; + + [SetUp] + public void Setup() + { + _mockFileOperationsDao = new Mock(); + _fileOperationsApiService = new FileOperationsApiService(_mockFileOperationsDao.Object); + } + + [Test] + public async Task GetFileContent_ValidRequest_Success() + { + _mockFileOperationsDao.Setup(s => s.GetFileContent("filePath")).ReturnsAsync("fileContent"); + + var fileContent = await _fileOperationsApiService.GetFileContent("filePath"); + + _mockFileOperationsDao.Verify(s => s.GetFileContent("filePath"), Times.Once()); + Assert.AreEqual("fileContent", fileContent); + } + + [Test] + public void GetFileContent_DaoFails_ExceptionThrown() + { + _mockFileOperationsDao.Setup(s => s.GetFileContent("filePath")).ThrowsAsync(new Exception("Failure")); + + Assert.ThrowsAsync(async () => await _fileOperationsApiService.GetFileContent("filePath")); + + _mockFileOperationsDao.Verify(s => s.GetFileContent("filePath"), Times.Once()); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/NotificationServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/NotificationServiceTest.cs new file mode 100644 index 0000000..67251e3 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/NotificationServiceTest.cs @@ -0,0 +1,226 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.API.Internal.NotificationsApiLib.Models; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.API.Internal.Operations.IncentiveApi.DataAccess; +using LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; +using LCU.API.Internal.Operations.IncentiveApi.Models.GoalDispersal; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Moq; +using NUnit.Framework; + +namespace Application.UnitTests.Services +{ + public class NotificationServiceTest + { + private INotificationApiService _notificationService; + private Mock _mockNotificationsDao; + private EmailRecipientsConfiguration _emailConfig; + private Mock _incentiveApiConfig; + private Mock _mockUserService; + + + [SetUp] + public void SetUp() + { + _mockNotificationsDao = new Mock(); + _mockUserService = new Mock(); + _incentiveApiConfig = new Mock(); + _emailConfig = new EmailRecipientsConfiguration() + { + TransferRequestInitiatedNotification = new List { "test@test.com" } + }; + _notificationService = new NotificationApiService(_emailConfig, _incentiveApiConfig.Object, _mockNotificationsDao.Object, + _mockUserService.Object); + } + + [Test] + public async Task SendMonthStartGoalDispersalReceipt_Ok() + { + var noErrorsReceipt = new GoalDispersalReceipt(1, GoalDispersalStrategy.ExtrapolateFromExistingGoals); + var withErrorsReceipt = new GoalDispersalReceipt(2, GoalDispersalStrategy.ExtrapolateFromExistingGoals); + withErrorsReceipt.AddNewDispersalError(GoalDispersalError.ErrorsDuringDisperse, "1234", "Error1", "Batmam"); + withErrorsReceipt.AddNewDispersalError(GoalDispersalError.DispersedToUserWithoutPermission, "4567", + "Error2", "Ironman"); + var defaultReceipt = new GoalDispersalReceipt(3, GoalDispersalStrategy.DefaultDisperseWithWeights); + var defaultReceiptWithErrors = + new GoalDispersalReceipt(4, GoalDispersalStrategy.DefaultDisperseWithWeights); + defaultReceiptWithErrors.AddNewDispersalError(GoalDispersalError.ErrorsDuringDisperse, "8910", "Error3", + "Superman"); + + var reportData = new Dictionary() + { + { "DeptA - 1", noErrorsReceipt }, + { "DeptB - 2", withErrorsReceipt }, + { "DeptC - 3", defaultReceipt }, + { "DeptD - 4", defaultReceiptWithErrors }, + { "DeptE - 6", null } + }; + + _mockNotificationsDao.Setup(x => + x.SendMessageAsync(It.IsAny())); + await _notificationService.SendMonthStartGoalDispersalReceipt(reportData, new List()); + + _mockNotificationsDao.Verify(x => x.SendMessageAsync(It.IsAny())); + } + + [Test] + public void SendMonthStartGoalDispersalReceipt_Does_Not_Except() + { + _mockNotificationsDao.Setup(x => x.SendMessageAsync(It.IsAny())) + .ThrowsAsync(new Exception("Ahhh")); + + var result = _notificationService.SendMonthStartGoalDispersalReceipt(new Dictionary() + { + { "Test", new GoalDispersalReceipt(10, GoalDispersalStrategy.DefaultDisperseWithWeights) } + }, new List()); + } + + [Test] + public async Task SendMissingRoleNotification_Ok() + { + var users = new List() + { + new AppUser() + { + FullName = "Test One", + EmployeeId = "11771", + BaseRole = "Base Role", + HomeDepartment = "Home" + }, + new AppUser() + { + FullName = "Test Two", + EmployeeId = "C00002", + BaseRole = "Base Role", + HomeDepartment = "Home" + } + }; + + _mockNotificationsDao.Setup(x => + x.SendMessageAsync(It.IsAny())); + await _notificationService.SendMissingRoleNotification(users, new List()); + + _mockNotificationsDao.Verify(x => x.SendMessageAsync(It.IsAny())); + } + + [Test] + public async Task SendTerminatedEmployeeNotification_Ok() + { + var employee = new AppUser() + { + FullName = "Test One", + EmployeeId = "11771", + BaseRole = "Base Role", + HomeDepartment = "Home", + Terminated = new DateTime(2020, 3, 1) + }; + var manager = new AppUser() + { + FullName = "Test Two", + EmployeeId = "C00002", + BaseRole = "Base Role", + HomeDepartment = "Home" + }; + var lastMonth = new PayoutEntry() + { + Code = "ABC", + EmployeeId = "11771", + EmployeeName = "Test One", + PayoutTotal = 500 + }; + var thisMonth = new PayoutEntry() + { + Code = "ABC", + EmployeeId = "11771", + EmployeeName = "Test One", + PayoutTotal = 200 + }; + + var receipts = new List() + { + new TerminatedEmployeeReceipt(employee,lastMonth,thisMonth,manager) + }; + + _mockNotificationsDao.Setup(x => + x.SendMessageAsync(It.IsAny())); + await _notificationService.SendTerminatedEmployeeNotification(receipts, new List()); + + _mockNotificationsDao.Verify(x => x.SendMessageAsync(It.IsAny())); + } + + [Test] + public async Task SendTransferRequestInitiatedNotification_Ok() + { + var transferAction = new ActivityTransferAction + { + RequestedActivityId = 1, + RequestedActivity = new Activity + { + DepartmentId = 1, + Product = Product.CheckCard, + Type = ActivityType.Referral + }, + CreatedBy = "Employee One", + NewActivityEmployeeId = "123", + NewActivityDepartmentId = 2 + }; + var recipients = new List { "user1@landmarkcu.com", "user2@landmarkcu.com" }; + + _mockNotificationsDao.Setup(x => + x.SendMessageAsync(It.IsAny())); + + await _notificationService.SendTransferRequestInitiatedNotification(transferAction, recipients); + + _mockNotificationsDao.Verify(x => x.SendMessageAsync(It.IsAny())); + } + + [Test] + public async Task SendTransferRequestInitiatedNotification_OverrideRecipients() + { + var transferAction = new ActivityTransferAction + { + RequestedActivityId = 1, + RequestedActivity = new Activity + { + DepartmentId = 1, + Product = Product.CheckCard, + Type = ActivityType.Referral + }, + CreatedBy = "Employee One", + NewActivityEmployeeId = "123", + NewActivityDepartmentId = 2 + }; + var originalRecipients = new List { "user1@landmarkcu.com", "user2@landmarkcu.com" }; + var actualRecipients = new List { "developer@landmarkcu.com" }; + + _emailConfig.OverrideRecipients = true; + _emailConfig.TransferRequestInitiatedNotification = actualRecipients; + + var emailRequest = new EmailRequest + { + Recipient = originalRecipients, + Body = "test", + Subject = "Test" + }; + + _mockNotificationsDao.Setup(x => x.SendMessageAsync(emailRequest)); + + await _notificationService.SendTransferRequestInitiatedNotification(transferAction, actualRecipients); + + emailRequest = new EmailRequest + { + Recipient = actualRecipients, + Body = "test", + Subject = "Test" + }; + + _mockNotificationsDao.Verify(notification => notification.SendMessageAsync(It.IsAny()), Times.Once); + } + } +} + diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Payout/PayoutCalculateServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Payout/PayoutCalculateServiceTest.cs new file mode 100644 index 0000000..21dd862 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Payout/PayoutCalculateServiceTest.cs @@ -0,0 +1,1440 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApi.Services.Payout; +using LCU.API.Internal.Operations.IncentiveApi.Services.Rules; +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace Application.UnitTests.Services.Payout +{ + public class PayoutCalculateServiceTest + { + private IPayoutCalculateService _payoutCalculateService; + private Mock _mockActivityRepository; + private Mock _mockGoalRepository; + private Mock _mockCommissionRepository; + private Mock _mockBusinessRuleService; + + private readonly List _departmentRules = new List + { + new NewCheckingReferralRule(56) // this simulates looking up 200. + }; + private readonly List _personalRules = new List(); + + private readonly DateTime _nonOverriddenDateTime = DateTime.ParseExact("2021-12-01", "yyyy-MM-dd", CultureInfo.InvariantCulture); + + [SetUp] + public void Setup() + { + _mockActivityRepository = new Mock(); + _mockGoalRepository = new Mock(); + _mockCommissionRepository = new Mock(); + _mockBusinessRuleService = new Mock(); + IEnumerable payoutStrategies = new List() + { + new DefaultPayoutStrategy(), + new GapPayoutStrategy() + }; + _mockBusinessRuleService.Setup(x => x.GetRules(PayoutType.Department)).Returns(_departmentRules); + _mockBusinessRuleService.Setup(x => x.GetRules(PayoutType.Personal)).Returns(_personalRules); + _payoutCalculateService = new PayoutCalculateService(_mockActivityRepository.Object, + _mockGoalRepository.Object, + _mockCommissionRepository.Object, payoutStrategies, + _mockBusinessRuleService.Object); + } + + [Test] + public async Task DepartmentPayoutView_Empty() + { + var theTestDate = DateTime.Today; + AppUser employee = GetBasicTestEmployee(Role.BM); + var userDepartment = GetBasicDepartmentAssignment(employee, theTestDate); + employee.Departments.Add(userDepartment); + + _mockActivityRepository + .Setup(activity => activity.FindEmployeeActivityByMonthRange(employee, It.IsAny())) + .ReturnsAsync(new List()); + _mockActivityRepository + .Setup(activity => + activity.FindDepartmentActivityByMonthRange(userDepartment.DepartmentId, It.IsAny())) + .ReturnsAsync(new List()); + _mockCommissionRepository.Setup(commission => commission.FindProductCommissions(employee.Role)) + .ReturnsAsync(new List()); + _mockGoalRepository.Setup(goal => + goal.FindPersonnelGoalsByMonthRange(userDepartment.DepartmentId, It.IsAny>(), + It.IsAny())).ReturnsAsync(new List()); + _mockGoalRepository.Setup(goal => + goal.FindDepartmentGoalsByMonthRange(userDepartment.DepartmentId, It.IsAny())) + .ReturnsAsync(new List()); + + var deptPayoutView = await _payoutCalculateService.BuildDepartmentPayoutView(employee,userDepartment, new MonthRange(theTestDate.Year, theTestDate.Month)); + Assert.NotNull(deptPayoutView); + Assert.NotNull(deptPayoutView.CommissionPayouts); + Assert.IsEmpty(deptPayoutView.CommissionPayouts); + Assert.NotNull(deptPayoutView.GoalPayouts); + Assert.IsEmpty(deptPayoutView.GoalPayouts); + } + + /** + * Create a user with a department, activity, goals, commissions and a role that + * has a matching personal payout plan. + * Here we test a BM, as they can earn commission and DEPARTMENT payouts, but not personal + * #27202, make sure sales/referrals work correctly. + */ + [Test] + public async Task PayoutPlan_Commission_Payout_Department() + { + var theTestDate = DateTime.Today; + AppUser employee = GetBasicTestEmployee(Role.BM); + var userDept = GetBasicDepartmentAssignment(employee, theTestDate); + employee.Departments.Add(userDept); + userDept.Role = Role.BM; + + var activity = new List() + { + new Activity() + { + Product = Product.Mortgage, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Type = ActivityType.Sale, + Detail = new ActivityDetail() + { + AccountNumber = "123456789", + AgreementNumber = "123456789", + SubjectPersonNbr = "IGuess1", + SubjectOrgNbr = "IGuess1", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456789", + StationName = "PlayStation", + AccountBalance = 1000 + }, + State = ActivityState.ManualReview + }, + new Activity() + { + Product = Product.Mortgage, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Type = ActivityType.Other, + Detail = new ActivityDetail() + { + AccountNumber = "223456789", + AgreementNumber = "223456789", + SubjectPersonNbr = "IGuessOther1", + SubjectOrgNbr = "IGuessOther1", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "223456789", + StationName = "PlayStation", + AccountBalance = 1000 + }, + State = ActivityState.ManualReview + }, + new Activity() + { + Product = Product.Mortgage, + EmployeeId = employee.EmployeeId, + Type = ActivityType.Sale, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456788", + AgreementNumber = "123456788", + SubjectPersonNbr = "IGuess2", + SubjectOrgNbr = "IGuess2", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456789", + StationName = "PlayStation", + AccountBalance = 1000 + }, + State = ActivityState.ManualReview + }, + new Activity() + { + Product = Product.NewChecking, + EmployeeId = employee.EmployeeId, + Type = ActivityType.Sale, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456787", + AgreementNumber = "123456787", + SubjectPersonNbr = "IGuess3", + SubjectOrgNbr = "IGuess3", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456789", + StationName = "PlayStation" + }, + State = ActivityState.ManualReview + }, + new Activity() + { + Product = Product.Mortgage, + EmployeeId = employee.EmployeeId, + Type = ActivityType.Sale, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456786", + AgreementNumber = "123456786", + SubjectPersonNbr = "IGuess4", + SubjectOrgNbr = "IGuess4", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456789", + StationName = "PlayStation", + AccountBalance = 1000 + }, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.Mortgage, + EmployeeId = employee.EmployeeId, + Type = ActivityType.Other, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "223456786", + AgreementNumber = "223456786", + SubjectPersonNbr = "IGuessOther4", + SubjectOrgNbr = "IGuessOther4", + MemberName = "NotNeededOther?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456789", + StationName = "PlayStation", + AccountBalance = 1000 + }, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.Mortgage, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456785", + AgreementNumber = "123456785", + SubjectPersonNbr = "IGuess5", + SubjectOrgNbr = "IGuess5", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456789", + StationName = "PlayStation", + AccountBalance = 1000 + }, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.Mortgage, + Type = ActivityType.Referral, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "1234567850", + AgreementNumber = "1234567850", + SubjectPersonNbr = "IGuess55", + SubjectOrgNbr = "IGuess55", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "1234567890", + StationName = "PlayStation", + // ,AccountBalance = 5000 TODO #27202, this shouldn't count? Is it all referrals, or mortgage referrals? + }, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.NewChecking, + EmployeeId = employee.EmployeeId, + Type = ActivityType.Sale, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456784", + AgreementNumber = "123456784", + SubjectPersonNbr = "IGuess6", + SubjectOrgNbr = "IGuess6", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456789", + StationName = "PlayStation" + }, + State = ActivityState.Accepted + } + }; + + var commissions = new List() + { + new Commission() + { + Product = Product.Mortgage, + RatePerProduct = 5, + Role = userDept.Role + }, + new Commission() + { + Product = Product.NewChecking, + RatePerProduct = 25, + Role = userDept.Role + }, + new Commission() + { + Product = Product.Vehicle, + RatePerProduct = 100, + Role = userDept.Role + } + }; + + var personalGoals = new List() + { + new Goal() + { + Product = Product.Mortgage, + Value = 2000, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId + } + }; + + var departmentGoals = new List() + { + new Goal() + { + Product = Product.Mortgage, + Value = 2000, + DepartmentId = userDept.DepartmentId + } + }; + + _mockActivityRepository + .Setup(activity => activity.FindEmployeeActivityByMonthRange(employee, It.IsAny())) + .ReturnsAsync(activity); + _mockActivityRepository + .Setup(activity => + activity.FindDepartmentActivityByMonthRange(userDept.DepartmentId, It.IsAny())) + .ReturnsAsync(activity); + _mockCommissionRepository.Setup(commission => commission.FindProductCommissions(userDept.Role)) + .ReturnsAsync(commissions); + _mockGoalRepository.Setup(goal => + goal.FindPersonnelGoalsByMonthRange(userDept.DepartmentId, It.IsAny>(), + It.IsAny())).ReturnsAsync(personalGoals); + _mockGoalRepository.Setup(goal => + goal.FindDepartmentGoalsByMonthRange(userDept.DepartmentId, It.IsAny())) + .ReturnsAsync(departmentGoals); + + var payout = await _payoutCalculateService.BuildDepartmentPayoutView(employee, userDept, new MonthRange(theTestDate.Year, theTestDate.Month)); + Assert.NotNull(payout); + Assert.NotNull(payout.CommissionPayouts); + Assert.NotNull(payout.CommissionPayouts); + Assert.IsNotEmpty(payout.CommissionPayouts); + Assert.NotNull(payout.GoalPayouts); + Assert.IsNotEmpty(payout.GoalPayouts); + + var newCheckingComm = payout.CommissionPayouts + .FirstOrDefault(x => x.Product == Product.NewChecking); + var mortgageComm = payout.CommissionPayouts + .FirstOrDefault(x => x.Product == Product.Mortgage); + Assert.AreEqual(15, mortgageComm.PayoutTotal); + Assert.AreEqual(25, newCheckingComm.PayoutTotal); + var payoutRange = PayoutProfile.BMMortgage.Payouts.First().Value; + Assert.AreEqual(payoutRange[0].Payout, payout.GoalPayouts.First().PayoutTotal); + } + + + /** + * Create a user with a department, activity, goals, commissions and a role that + * has a matching personal payout plan. + * Here we test a BM, as they can earn commission and DEPARTMENT payouts, but not personal + * We test this BM in two places - Member Services, and Not. For new checking. #26912 + */ + [Test] + public async Task PayoutPlan_Commission_Goal_Payout_Department_NewChecking_Exception() + { + var theTestDate = DateTime.Today; + AppUser employee = GetBasicTestEmployee(Role.BM); + var userDept = GetBasicDepartmentAssignment(employee, theTestDate); + employee.Departments.Add(userDept); + userDept.Role = Role.BM; + + var activity = new List() + { + new Activity() + { + Product = Product.NewChecking, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Type = ActivityType.Referral, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.NewChecking, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Type = ActivityType.Sale, + State = ActivityState.ManualReview + }, + new Activity() + { + Product = Product.NewChecking, + EmployeeId = employee.EmployeeId, + Type = ActivityType.Sale, + DepartmentId = userDept.DepartmentId, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.NewChecking, + EmployeeId = employee.EmployeeId, + Type = ActivityType.Referral, + DepartmentId = userDept.DepartmentId, + State = ActivityState.Rejected + } + }; + + var commissions = new List() + { + new Commission() + { + Product = Product.NewChecking, + RatePerProduct = 25, + Role = userDept.Role + } + }; + + var personalGoals = new List() + { + new Goal() + { + Product = Product.NewChecking, + Value = 2, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId + } + }; + + var departmentGoals = new List() + { + new Goal() + { + Product = Product.NewChecking, + Value = 1, + DepartmentId = userDept.DepartmentId + } + }; + + var departmentRules = new List() + { + new NewCheckingReferralRule(56) // this simulates looking up 200. + }; + var personalRules = new List(); + + _mockActivityRepository + .Setup(activity => activity.FindEmployeeActivityByMonthRange(employee, It.IsAny())) + .ReturnsAsync(activity); + _mockActivityRepository + .Setup(activity => + activity.FindDepartmentActivityByMonthRange(userDept.DepartmentId, It.IsAny())) + .ReturnsAsync(activity); + _mockCommissionRepository.Setup(commission => commission.FindProductCommissions(userDept.Role)) + .ReturnsAsync(commissions); + _mockGoalRepository.Setup(goal => + goal.FindPersonnelGoalsByMonthRange(userDept.DepartmentId, It.IsAny>(), + It.IsAny())).ReturnsAsync(personalGoals); + _mockGoalRepository.Setup(goal => + goal.FindDepartmentGoalsByMonthRange(userDept.DepartmentId, It.IsAny())) + .ReturnsAsync(departmentGoals); + + var payout = await _payoutCalculateService.BuildDepartmentPayoutView(employee, userDept, new MonthRange(theTestDate.Year, theTestDate.Month)); + Assert.NotNull(payout); + Assert.NotNull(payout.CommissionPayouts); + Assert.NotNull(payout.CommissionPayouts); + Assert.IsNotEmpty(payout.CommissionPayouts); + Assert.NotNull(payout.GoalPayouts); + Assert.IsNotEmpty(payout.GoalPayouts); + + var newCheckingComm = payout.CommissionPayouts + .FirstOrDefault(x => x.Product == Product.NewChecking); + Assert.AreEqual(50, newCheckingComm.PayoutTotal); + var payoutRange = PayoutProfile.BMNewChecking.Payouts.First().Value; + Assert.AreEqual(payoutRange[0].Payout, payout.GoalPayouts.First().PayoutTotal); + } + + /** + * Create a user with a department, activity, goals, and a role that has a GAP payout plan. + * Here we test a BM getting GAP payouts. + */ + [Test] + public async Task PayoutPlan_Payout_GAP_Simple() + { + var theTestDate = DateTime.Today; + AppUser employee = GetBasicTestEmployee(Role.BM); + var userDept = GetBasicDepartmentAssignment(employee, theTestDate); + employee.Departments.Add(userDept); + userDept.Role = Role.BM; + + // We need multiple activity types here.... + // Let's lay it out: + // 3 GAP, two approved. + // 5 that are eligible, 4 are approved. + // 3/4 that have debt protection => 3/5 + var activity = new List() + { + new Activity() + { + Product = Product.GAPInsurance, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456700", + AgreementNumber = "123456700", + SubjectPersonNbr = "GAP0", + SubjectOrgNbr = "DGAP0", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456780", + StationName = "PlayStation" + }, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.GAPInsurance, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456789", + AgreementNumber = "123456789", + SubjectPersonNbr = "GAP1", + SubjectOrgNbr = "DGAP1", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456789", + StationName = "PlayStation" + }, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.GAPInsurance, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456789", + AgreementNumber = "123456789", + SubjectPersonNbr = "GAP2", + SubjectOrgNbr = "DGAP2", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456789", + StationName = "PlayStation" + }, + State = ActivityState.Rejected + }, + new Activity() + { + Product = Product.Mortgage, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456788", + AgreementNumber = "123456788", + SubjectPersonNbr = "DEBTPROTELIG1", + SubjectOrgNbr = "DEPTDEBTPROTELIG1", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456788", + StationName = "PlayStation", + IsDebtProtectionEligible = true, + DebtProtectionAddDate = DateTime.UtcNow + }, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.Vehicle, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456777", + AgreementNumber = "123456777", + SubjectPersonNbr = "DEBTPROTELIG2", + SubjectOrgNbr = "DEPTDEBTPROTELIG2", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456787", + StationName = "PlayStation", + IsDebtProtectionEligible = true, + DebtProtectionAddDate = DateTime.UtcNow + }, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.Mortgage, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456766", + AgreementNumber = "123456766", + SubjectPersonNbr = "DEBTPROTELIG3", + SubjectOrgNbr = "DEPTDEBTPROTELIG3", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456786", + StationName = "PlayStation", + IsDebtProtectionEligible = true, + DebtProtectionAddDate = DateTime.UtcNow + }, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.DebtProtectionEligibleLoan, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456755", + AgreementNumber = "123456755", + SubjectPersonNbr = "DEBTPROTELIG4", + SubjectOrgNbr = "DEPTDEBTPROTELIG4", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456785", + StationName = "PlayStation", + IsDebtProtectionEligible = true + }, + State = ActivityState.Rejected + }, + new Activity() + { + Product = Product.Mortgage, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456744", + AgreementNumber = "123456744", + SubjectPersonNbr = "DEBTPROTELIG5", + SubjectOrgNbr = "DEPTDEBTPROTELIG5", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456784", + StationName = "PlayStation", + IsDebtProtectionEligible = true + }, + State = ActivityState.Accepted + } + }; + + var commissions = new List() + { + new Commission + { + Id = 1, + Role = userDept.Role, + RatePerProduct = 20, + Product = Product.GAPInsurance + } + }; + var personalGoals = new List(); + var departmentGoals = new List(); + + _mockActivityRepository + .Setup(activity => activity.FindEmployeeActivityByMonthRange(employee, It.IsAny())) + .ReturnsAsync(activity); + _mockActivityRepository + .Setup(activity => + activity.FindDepartmentActivityByMonthRange(userDept.DepartmentId, It.IsAny())) + .ReturnsAsync(activity); + _mockCommissionRepository.Setup(commission => commission.FindProductCommissions(userDept.Role)) + .ReturnsAsync(commissions); + _mockGoalRepository.Setup(goal => + goal.FindPersonnelGoalsByMonthRange(userDept.DepartmentId, It.IsAny>(), + It.IsAny())).ReturnsAsync(personalGoals); + _mockGoalRepository.Setup(goal => + goal.FindDepartmentGoalsByMonthRange(userDept.DepartmentId, It.IsAny())) + .ReturnsAsync(departmentGoals); + + var payout = await _payoutCalculateService.BuildDepartmentPayoutView(employee, userDept, new MonthRange(theTestDate.Year, theTestDate.Month)); + Assert.NotNull(payout); + Assert.NotNull(payout.CommissionPayouts); + Assert.IsNotEmpty(payout.CommissionPayouts); + Assert.NotNull(payout.GoalPayouts); + Assert.IsEmpty(payout.GoalPayouts); + var gapPayout = payout.CommissionPayouts.First(); + Assert.NotNull(gapPayout); + Assert.AreEqual(2, gapPayout.ActivityTotal); + Assert.AreEqual(2 * gapPayout.CommissionValue, gapPayout.PayoutTotal); + + } + + + /** + * Create a user with a department, activity, goals, and a role that has a GAP payout plan. + * Here we test a BM getting GAP payouts. + */ + [Test] + public async Task PayoutPlan_Payout_GAP_Complex() + { + var theTestDate = DateTime.Today; + AppUser employee = GetBasicTestEmployee(Role.BM); + var userDept = GetBasicDepartmentAssignment(employee, theTestDate); + employee.Departments.Add(userDept); + userDept.Role = Role.BM; + + // We need multiple activity types here.... + // Let's lay it out: + // 4 GAP, three approved. + // 1 that is ineligible. + // 8 that are eligible: 1 is rejected. 3 have prot. 4 are eligible, no prot. + // 7/3 that have debt protection => 3/7 (42%) + // 13 total... + // Will have -10- associated activities (Eligible + Gap), and pay out 3 x $40.00 ($120), for the 42%. + var activity = new List() + { + // GAP, 3 approved + new Activity() + { + Product = Product.GAPInsurance, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456001", + AgreementNumber = "123456001", + SubjectPersonNbr = "GAP01", + SubjectOrgNbr = "DGAP01", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456700", + StationName = "PlayStation" + }, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.GAPInsurance, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456700", + AgreementNumber = "123456700", + SubjectPersonNbr = "GAP0", + SubjectOrgNbr = "DGAP0", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456780", + StationName = "PlayStation" + }, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.GAPInsurance, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456789", + AgreementNumber = "123456789", + SubjectPersonNbr = "GAP1", + SubjectOrgNbr = "DGAP1", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456789", + StationName = "PlayStation" + }, + State = ActivityState.Accepted + }, + // Gap, 1 Rejected. + new Activity() + { + Product = Product.GAPInsurance, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456789", + AgreementNumber = "123456789", + SubjectPersonNbr = "GAP2", + SubjectOrgNbr = "DGAP2", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456789", + StationName = "PlayStation" + }, + State = ActivityState.Rejected + }, + // Eligible 3 with prot, accepted + new Activity() + { + Product = Product.Mortgage, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456788", + AgreementNumber = "123456788", + SubjectPersonNbr = "DEBTPROTELIG1", + SubjectOrgNbr = "DEPTDEBTPROTELIG1", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456788", + StationName = "PlayStation", + IsDebtProtectionEligible = true, + DebtProtectionAddDate = DateTime.UtcNow + }, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.Vehicle, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456777", + AgreementNumber = "123456777", + SubjectPersonNbr = "DEBTPROTELIG2", + SubjectOrgNbr = "DEPTDEBTPROTELIG2", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456787", + StationName = "PlayStation", + IsDebtProtectionEligible = true, + DebtProtectionAddDate = DateTime.UtcNow + }, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.Mortgage, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456766", + AgreementNumber = "123456766", + SubjectPersonNbr = "DEBTPROTELIG3", + SubjectOrgNbr = "DEPTDEBTPROTELIG3", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456786", + StationName = "PlayStation", + IsDebtProtectionEligible = true, + DebtProtectionAddDate = DateTime.UtcNow + }, + State = ActivityState.Accepted + }, + // 1 Ineligible, accepted, + new Activity() + { + Product = Product.DebtProtectionEligibleLoan, + EmployeeId = employee.EmployeeId, + Type = ActivityType.Sale, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456755", + AgreementNumber = "123456755", + SubjectPersonNbr = "DEBTPROTINELIG1", + SubjectOrgNbr = "DEPTDEBTPROTINELIG1", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456785", + StationName = "PlayStation", + IsDebtProtectionEligible = false + }, + State = ActivityState.Accepted + }, + // 1 eligible, but rejected, no prot. + new Activity() + { + Product = Product.Mortgage, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456744", + AgreementNumber = "123456744", + SubjectPersonNbr = "DEBTPROTELIG5", + SubjectOrgNbr = "DEPTDEBTPROTELIG5", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456784", + StationName = "PlayStation", + IsDebtProtectionEligible = true + }, + State = ActivityState.Rejected + }, + // 4 eligible, accepted, no prot. + new Activity() + { + Product = Product.Mortgage, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456744", + AgreementNumber = "123456744", + SubjectPersonNbr = "DEBTPROTELIG61", + SubjectOrgNbr = "DEPTDEBTPROTELIG61", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456784", + StationName = "PlayStation", + IsDebtProtectionEligible = true + }, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.Mortgage, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456744", + AgreementNumber = "123456744", + SubjectPersonNbr = "DEBTPROTELIG62", + SubjectOrgNbr = "DEPTDEBTPROTELIG62", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456784", + StationName = "PlayStation", + IsDebtProtectionEligible = true + }, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.Mortgage, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456744", + AgreementNumber = "123456744", + SubjectPersonNbr = "DEBTPROTELIG63", + SubjectOrgNbr = "DEPTDEBTPROTELIG64", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456784", + StationName = "PlayStation", + IsDebtProtectionEligible = true + }, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.Mortgage, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456744", + AgreementNumber = "123456744", + SubjectPersonNbr = "DEBTPROTELIG65", + SubjectOrgNbr = "DEPTDEBTPROTELIG65", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456784", + StationName = "PlayStation", + IsDebtProtectionEligible = true + }, + State = ActivityState.Accepted + } + }; + + var commissions = new List() + { + new Commission + { + Id = 1, + Role = userDept.Role, + RatePerProduct = 20, + Product = Product.GAPInsurance + } + }; + var personalGoals = new List(); + var departmentGoals = new List(); + + _mockActivityRepository + .Setup(activity => activity.FindEmployeeActivityByMonthRange(employee, It.IsAny())) + .ReturnsAsync(activity); + _mockActivityRepository + .Setup(activity => + activity.FindDepartmentActivityByMonthRange(userDept.DepartmentId, It.IsAny())) + .ReturnsAsync(activity); + _mockCommissionRepository.Setup(commission => commission.FindProductCommissions(userDept.Role)) + .ReturnsAsync(commissions); + _mockGoalRepository.Setup(goal => + goal.FindPersonnelGoalsByMonthRange(userDept.DepartmentId, It.IsAny>(), + It.IsAny())).ReturnsAsync(personalGoals); + _mockGoalRepository.Setup(goal => + goal.FindDepartmentGoalsByMonthRange(userDept.DepartmentId, It.IsAny())) + .ReturnsAsync(departmentGoals); + + var payout = await _payoutCalculateService.BuildDepartmentPayoutView(employee, userDept, new MonthRange(theTestDate.Year, theTestDate.Month)); + Assert.NotNull(payout); + Assert.NotNull(payout.CommissionPayouts); + Assert.IsNotEmpty(payout.CommissionPayouts); + Assert.NotNull(payout.GoalPayouts); + Assert.IsEmpty(payout.GoalPayouts); + var gapPayout = payout.CommissionPayouts.First(); + Assert.NotNull(gapPayout); + Assert.AreEqual(3, gapPayout.ActivityTotal); + Assert.AreEqual(3, gapPayout.ActivityPayouts.Count); + var commissionProfile = commissions.First(); + Assert.AreEqual(commissionProfile.RatePerProduct * gapPayout.ActivityPayouts.Count, 60); + } + + /** + * Create a user with a department, activity, goals, commissions and a role that + * has a matching personal payout plan. + * Here we test a BM again, but the department is only paying 75%. + */ + [Test] + public async Task PayoutPlan_Commission_Payout_Department_Sliding() + { + var theTestDate = DateTime.Today; + AppUser employee = GetBasicTestEmployee(Role.BM); + var userDept = GetBasicDepartmentAssignment(employee, theTestDate); + employee.Departments.Add(userDept); + userDept.Role = Role.BM; + userDept.SlidingPayout = 75; + + var activity = new List() + { + new Activity() + { + Product = Product.Mortgage, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456789", + AgreementNumber = "123456789", + SubjectPersonNbr = "IGuess1", + SubjectOrgNbr = "IGuess1", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456789", + StationName = "PlayStation", + AccountBalance = 1000 + }, + State = ActivityState.ManualReview + }, + new Activity() + { + Product = Product.Mortgage, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456788", + AgreementNumber = "123456788", + SubjectPersonNbr = "IGuess2", + SubjectOrgNbr = "IGuess2", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456789", + StationName = "PlayStation", + AccountBalance = 1000 + }, + State = ActivityState.ManualReview + }, + new Activity() + { + Product = Product.NewChecking, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456787", + AgreementNumber = "123456787", + SubjectPersonNbr = "IGuess3", + SubjectOrgNbr = "IGuess3", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456789", + StationName = "PlayStation" + }, + State = ActivityState.ManualReview + }, + new Activity() + { + Product = Product.Mortgage, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456786", + AgreementNumber = "123456786", + SubjectPersonNbr = "IGuess4", + SubjectOrgNbr = "IGuess4", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456789", + StationName = "PlayStation", + AccountBalance = 1000 + }, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.Mortgage, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456785", + AgreementNumber = "123456785", + SubjectPersonNbr = "IGuess5", + SubjectOrgNbr = "IGuess5", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456789", + StationName = "PlayStation", + AccountBalance = 1000 + }, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.NewChecking, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + Detail = new ActivityDetail() + { + AccountNumber = "123456784", + AgreementNumber = "123456784", + SubjectPersonNbr = "IGuess6", + SubjectOrgNbr = "IGuess6", + MemberName = "NotNeeded?", + BranchOrgName = "MoreFunFields", + BranchOrgNbr = "MoreFunFields", + DnaPersonNbr = "123456789", + StationName = "PlayStation" + }, + State = ActivityState.Accepted + } + }; + + var commissions = new List() + { + new Commission() + { + Product = Product.Mortgage, + RatePerProduct = 5, + Role = userDept.Role + }, + new Commission() + { + Product = Product.NewChecking, + RatePerProduct = 25, + Role = userDept.Role + }, + new Commission() + { + Product = Product.Vehicle, + RatePerProduct = 100, + Role = userDept.Role + } + }; + + var personalGoals = new List() + { + new Goal() + { + Product = Product.Mortgage, + Value = 2000, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId + } + }; + + var departmentGoals = new List() + { + new Goal() + { + Product = Product.Mortgage, + Value = 2000, + DepartmentId = userDept.DepartmentId + } + }; + + _mockActivityRepository + .Setup(activity => activity.FindEmployeeActivityByMonthRange(employee, It.IsAny())) + .ReturnsAsync(activity); + _mockActivityRepository + .Setup(activity => + activity.FindDepartmentActivityByMonthRange(userDept.DepartmentId, It.IsAny())) + .ReturnsAsync(activity); + _mockCommissionRepository.Setup(commission => commission.FindProductCommissions(userDept.Role)) + .ReturnsAsync(commissions); + _mockGoalRepository.Setup(goal => + goal.FindPersonnelGoalsByMonthRange(userDept.DepartmentId, It.IsAny>(), + It.IsAny())).ReturnsAsync(personalGoals); + _mockGoalRepository.Setup(goal => + goal.FindDepartmentGoalsByMonthRange(userDept.DepartmentId, It.IsAny())) + .ReturnsAsync(departmentGoals); + + var payout = await _payoutCalculateService.BuildDepartmentPayoutView(employee, userDept, new MonthRange(theTestDate.Year, theTestDate.Month)); + Assert.NotNull(payout); + Assert.NotNull(payout.CommissionPayouts); + Assert.NotNull(payout.CommissionPayouts); + Assert.IsNotEmpty(payout.CommissionPayouts); + Assert.NotNull(payout.GoalPayouts); + Assert.IsNotEmpty(payout.GoalPayouts); + + var newCheckingComm = payout.CommissionPayouts + .FirstOrDefault(x => x.Product == Product.NewChecking); + var mortgageComm = payout.CommissionPayouts + .FirstOrDefault(x => x.Product == Product.Mortgage); + Assert.AreEqual(10, mortgageComm.PayoutTotal); + Assert.AreEqual(25, newCheckingComm.PayoutTotal); + var payoutRange = PayoutProfile.BMMortgage.Payouts.First().Value; + Assert.AreEqual( payoutRange[0].Payout * .75, payout.GoalPayouts.First().PayoutTotal); + Assert.AreEqual(150, payout.GoalPayouts.First().PayoutTotal); + } + + /** + * Create a user with a department, activity, goals, commissions and a role that + * has no matching payout plan; should still see commissions. + */ + [Test] + public async Task PayoutPlan_NoPayoutProfile() + { + var theTestDate = DateTime.Today; + AppUser employee = GetBasicTestEmployee(Role.Teller); + var userDept = GetBasicDepartmentAssignment(employee, theTestDate); + employee.Departments.Add(userDept); + userDept.Role = Role.Teller; + + var activity = new List() + { + new Activity() + { + Product = Product.Mortgage, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.Mortgage, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.NewChecking, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.Mortgage, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + State = ActivityState.Rejected + }, + new Activity() + { + Product = Product.Mortgage, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + State = ActivityState.PendingTransfer + }, + new Activity() + { + Product = Product.NewChecking, + Type = ActivityType.Sale, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + State = ActivityState.ManualReview + } + }; + + var commissions = new List() + { + new Commission() + { + Product = Product.Mortgage, + RatePerProduct = 5, + Role = userDept.Role + } + }; + + var goals = new List() + { + new Goal() + { + Product = Product.Mortgage, + Value = 2, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId + } + }; + + _mockActivityRepository + .Setup(activity => activity.FindEmployeeActivityByMonthRange(employee, It.IsAny())) + .ReturnsAsync(activity); + _mockCommissionRepository.Setup(commission => commission.FindProductCommissions(userDept.Role)) + .ReturnsAsync(commissions); + _mockGoalRepository.Setup(goal => + goal.FindPersonnelGoalsByMonthRange(userDept.DepartmentId, It.IsAny>(), + It.IsAny())).ReturnsAsync(goals); + + var payout = await _payoutCalculateService.BuildDepartmentPayoutView(employee, userDept, new MonthRange(theTestDate)); + Assert.NotNull(payout); + Assert.NotNull(payout.CommissionPayouts); + Assert.NotNull(payout.CommissionPayouts); + Assert.IsNotEmpty(payout.CommissionPayouts); + Assert.NotNull(payout.GoalPayouts); + Assert.IsEmpty(payout.GoalPayouts); + + var newCheckingComm = payout.CommissionPayouts + .FirstOrDefault(x => x.Product == Product.NewChecking); + var mortgageComm = payout.CommissionPayouts + .FirstOrDefault(x => x.Product == Product.Mortgage); + Assert.AreEqual(10, mortgageComm.PayoutTotal); + Assert.IsNull(newCheckingComm); + } + + + private AppUser GetBasicTestEmployee(Role role) + { + AppUser employee = new AppUser() + { + Departments = new List(), + AdEmail = "testpayout@test.com", + BaseRole = "BM", + EmployeeId = "11771", + FullName = "Jane Test", + HomeDepartment = "Home", + PersonId = 11234 + }; + return employee; + } + + private UserDepartment GetBasicDepartmentAssignment(AppUser employee, DateTime theTestDate) + { + var department = new Department() + { + State = DepartmentState.Active, + DepartmentName = "Test1", + BusinessLineCode = BusinessLine.Operations.Code, + Id = 99 + }; + + var assignment1 = new UserDepartment() + { + DepartmentId = 99, + Department = department, + Employee = employee, + Note = "Default!", + IsHome = true, + StartDate = new DateTime(theTestDate.Year, theTestDate.Month, 1), + SlidingPayout = 100 + }; + return assignment1; + } + + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Payout/PayoutServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Payout/PayoutServiceTest.cs new file mode 100644 index 0000000..54f9309 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Payout/PayoutServiceTest.cs @@ -0,0 +1,1105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApi.Services.Payout; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Moq; +using NUnit.Framework; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.API.Internal.Operations.IncentiveApi.Models.Payout; +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.API.Internal.Operations.IncentiveApi.Services.Rules; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace Application.UnitTests.Services.Payout +{ + public class PayoutServiceTest + { + private IPayoutService _payoutService; + private Mock _mockActivityRepository; + private Mock _mockGoalRepository; + private Mock _mockCommissionRepository; + private Mock _mockUserRepository; + private Mock _mockPayoutRepository; + private Mock _mockUnitOfWork; + private IPayoutCalculateService _payoutCalculateService; + private Mock _mockPayoutCalculateService; + private Mock _mockBusinessRuleService; + private Mock _mockAccountingRepository; + private Mock _mockActivityService; + + private readonly List departmentRules = new List + { + new NewCheckingReferralRule(56) + }; + private readonly List personalRules = new List() + { + }; + + [SetUp] + public void Setup() + { + _mockActivityRepository = new Mock(); + _mockGoalRepository = new Mock(); + _mockCommissionRepository = new Mock(); + _mockUserRepository = new Mock(); + _mockPayoutRepository = new Mock(); + _mockUnitOfWork = new Mock(); + _mockPayoutCalculateService = new Mock(); + _mockActivityService = new Mock(); + _mockBusinessRuleService = new Mock(); + _mockAccountingRepository = new Mock(); + _mockBusinessRuleService.Setup(x => x.GetRules(PayoutType.Department)).Returns(departmentRules); + _mockBusinessRuleService.Setup(x => x.GetRules(PayoutType.Personal)).Returns(personalRules); + + IEnumerable payoutStrategies = new List() + { + new DefaultPayoutStrategy(), + new GapPayoutStrategy() + }; + _payoutCalculateService = new PayoutCalculateService(_mockActivityRepository.Object, + _mockGoalRepository.Object, + _mockCommissionRepository.Object, payoutStrategies, _mockBusinessRuleService.Object); + } + + /** + * If a user has no departments, then they should get no payouts. + */ + [Test] + public async Task PayoutPlans_No_Departments() + { + _payoutService = new PayoutService(_mockUserRepository.Object, _mockPayoutRepository.Object, + _mockUnitOfWork.Object, _mockPayoutCalculateService.Object, _mockAccountingRepository.Object, _mockActivityService.Object); + + _mockPayoutCalculateService.Setup(x => x.BuildDepartmentPayoutView(It.IsAny(), + It.IsAny(), + It.IsAny())).Verifiable(); + + AppUser employee = GetBasicTestEmployee(Role.BM); + var payout = + await _payoutService.BuildUserDepartmentsPayoutView(employee, + new MonthRange(DateTime.Today.Year, DateTime.Today.Month)); + Assert.NotNull(payout); + Assert.IsFalse(payout.Any()); + + _mockPayoutCalculateService.Verify(x => x.BuildDepartmentPayoutView(It.IsAny(), + It.IsAny(), + It.IsAny()), Times.Never); + } + + [Test] + public async Task PayoutPlans_NonEmpty() + { + _payoutService = new PayoutService(_mockUserRepository.Object, _mockPayoutRepository.Object, + _mockUnitOfWork.Object, _mockPayoutCalculateService.Object, _mockAccountingRepository.Object, _mockActivityService.Object); + var theTestDate = DateTime.Today; + var range = new MonthRange(theTestDate); + AppUser employee = GetBasicTestEmployee(Role.BM); + var userDepartment = GetBasicDepartmentAssignment(employee, theTestDate,Role.BM); + employee.Departments.Add(userDepartment); + + var deptPayoutView = new DepartmentPayout(employee, userDepartment, range, new List(), new List()); + + _mockPayoutCalculateService.Setup(x => x.BuildDepartmentPayoutView(employee, userDepartment, + range)).ReturnsAsync(deptPayoutView); + + var payout = await _payoutService.BuildUserDepartmentsPayoutView(employee, range); + Assert.NotNull(payout); + Assert.IsNotEmpty(payout); + Assert.AreEqual(1, payout.Count()); + Assert.NotNull(payout.First().CommissionPayouts); + Assert.IsEmpty(payout.First().CommissionPayouts); + Assert.NotNull(payout.First().GoalPayouts); + Assert.IsEmpty(payout.First().GoalPayouts); + + _mockPayoutCalculateService.Verify(x => x.BuildDepartmentPayoutView(It.IsAny(), + It.IsAny(), + It.IsAny()), Times.Once); + } + + + /** + * Create a user with a department, activity, goals, no commissions and a role that + * has a matching payout plan; should still see goal payout. + */ + [Test] + public async Task PayoutPlan_No_Commissions() + { + _payoutService = new PayoutService(_mockUserRepository.Object, _mockPayoutRepository.Object, + _mockUnitOfWork.Object, _payoutCalculateService, _mockAccountingRepository.Object, _mockActivityService.Object); + var theTestDate = DateTime.Today; + AppUser employee = GetBasicTestEmployee(Role.BM); + var userDept = GetBasicDepartmentAssignment(employee, theTestDate, Role.BM); + employee.Departments.Add(userDept); + + var activity = new List() + { + new Activity() + { + Product = Product.Mortgage, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + State = ActivityState.Accepted, + Detail = new ActivityDetail() + { + AccountBalance = 1000 + } + }, + new Activity() + { + Product = Product.Mortgage, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + State = ActivityState.Accepted, + Detail = new ActivityDetail() + { + AccountBalance = 1000 + } + }, + new Activity() + { + Product = Product.NewChecking, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.Mortgage, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + State = ActivityState.Rejected, + Detail = new ActivityDetail() + { + AccountBalance = 1000 + } + }, + new Activity() + { + Product = Product.Mortgage, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + State = ActivityState.Rejected, + Detail = new ActivityDetail() + { + AccountBalance = 1000 + } + }, + new Activity() + { + Product = Product.NewChecking, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + State = ActivityState.ManualReview + } + }; + + var commissions = new List(); + + var personalGoals = new List() + { + new Goal() + { + Product = Product.Mortgage, + Value = 2000, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId + } + }; + + var departmentGoals = new List() + { + new Goal() + { + Product = Product.Mortgage, + Value = 2000, + DepartmentId = userDept.DepartmentId + } + }; + + _mockActivityRepository + .Setup(activity => activity.FindEmployeeActivityByMonthRange(employee, It.IsAny())) + .ReturnsAsync(activity); + _mockActivityRepository + .Setup(activity => + activity.FindDepartmentActivityByMonthRange(userDept.DepartmentId, It.IsAny())) + .ReturnsAsync(activity); + _mockCommissionRepository.Setup(commission => commission.FindProductCommissions(employee.Role)) + .ReturnsAsync(commissions); + _mockGoalRepository.Setup(goal => + goal.FindPersonnelGoalsByMonthRange(userDept.DepartmentId, It.IsAny>(), + It.IsAny())).ReturnsAsync(personalGoals); + _mockGoalRepository.Setup(goal => + goal.FindDepartmentGoalsByMonthRange(userDept.DepartmentId, It.IsAny())) + .ReturnsAsync(departmentGoals); + + var payout = await _payoutService.BuildUserDepartmentsPayoutView(employee, new MonthRange(theTestDate)); + Assert.NotNull(payout); + Assert.NotNull(payout.First().CommissionPayouts); + Assert.NotNull(payout.First().CommissionPayouts); + Assert.IsEmpty(payout.First().CommissionPayouts); + Assert.NotNull(payout.First().GoalPayouts); + Assert.IsNotEmpty(payout.First().GoalPayouts); + var payoutRange = PayoutProfile.BMMortgage.Payouts.First().Value; + + Assert.AreEqual(payoutRange[0].Payout, payout.First().GoalPayouts.First().PayoutTotal); + } + + /** + * Create a user with a department, activity, no goals, commissions and a role that + * has a matching payout plan; should still see commissions, but no goal payouts since there are no goals. + */ + [Test] + public async Task PayoutPlan_No_Goals() + { + _payoutService = new PayoutService(_mockUserRepository.Object, _mockPayoutRepository.Object, + _mockUnitOfWork.Object, _payoutCalculateService, _mockAccountingRepository.Object, _mockActivityService.Object); + var theTestDate = DateTime.Today; + AppUser employee = GetBasicTestEmployee(Role.BM); + var userDept = GetBasicDepartmentAssignment(employee, theTestDate, Role.BM); + employee.Departments.Add(userDept); + + var activity = new List() + { + new Activity() + { + Product = Product.Mortgage, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.Mortgage, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.NewChecking, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.Mortgage, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + State = ActivityState.Rejected + }, + new Activity() + { + Product = Product.Mortgage, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + State = ActivityState.PendingTransfer + }, + new Activity() + { + Product = Product.NewChecking, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept.DepartmentId, + State = ActivityState.ManualReview + } + }; + + var commissions = new List() + { + new Commission() + { + Product = Product.Mortgage, + RatePerProduct = 5, + Role = userDept.Role + }, + new Commission() + { + Product = Product.NewChecking, + RatePerProduct = 25, + Role = userDept.Role + }, + new Commission() + { + Product = Product.Vehicle, + RatePerProduct = 100, + Role = userDept.Role + } + }; + + var goals = new List(); + + _mockActivityRepository + .Setup(activity => activity.FindEmployeeActivityByMonthRange(employee, It.IsAny())) + .ReturnsAsync(activity); + _mockActivityRepository + .Setup(activity => + activity.FindDepartmentActivityByMonthRange(userDept.DepartmentId, It.IsAny())) + .ReturnsAsync(activity); + _mockCommissionRepository.Setup(commission => commission.FindProductCommissions(userDept.Role)) + .ReturnsAsync(commissions); + _mockGoalRepository.Setup(goal => + goal.FindPersonnelGoalsByMonthRange(userDept.DepartmentId, It.IsAny>(), + It.IsAny())).ReturnsAsync(goals); + _mockGoalRepository.Setup(goal => + goal.FindDepartmentGoalsByMonthRange(userDept.DepartmentId, It.IsAny())) + .ReturnsAsync(goals); + + var payout = + await _payoutService.BuildUserDepartmentsPayoutView(employee, + new MonthRange(theTestDate.Year, theTestDate.Month)); + Assert.NotNull(payout); + Assert.NotNull(payout.First().CommissionPayouts); + Assert.NotNull(payout.First().CommissionPayouts); + Assert.IsNotEmpty(payout.First().CommissionPayouts); + Assert.NotNull(payout.First().GoalPayouts); + Assert.IsEmpty(payout.First().GoalPayouts); + + var newCheckingComm = payout.First().CommissionPayouts + .FirstOrDefault(x => x.Product == Product.NewChecking); + var mortgageComm = payout.First().CommissionPayouts + .FirstOrDefault(x => x.Product == Product.Mortgage); + Assert.AreEqual(10, mortgageComm.PayoutTotal); + Assert.AreEqual(25, newCheckingComm.PayoutTotal); + } + + + /** + * Create a user with two departments, activity, no goals, commissions and a role that + * has a matching payout plan; should see commissions for both departments. + */ + [Test] + public async Task Commission_Only_User_Multi_Dept() + { + _payoutService = new PayoutService(_mockUserRepository.Object, _mockPayoutRepository.Object, + _mockUnitOfWork.Object, _payoutCalculateService, _mockAccountingRepository.Object, _mockActivityService.Object); + var theTestDate = DateTime.Today; + AppUser employee = GetBasicTestEmployee(Role.Teller); + var userDept1 = GetBasicDepartmentAssignment(employee, theTestDate, Role.Teller); + var userDept2 = GetOtherDepartmentAssignment(651, employee, theTestDate); + userDept2.Role = Role.Teller; + + employee.Departments.Add(userDept1); + employee.Departments.Add(userDept2); + + var activity = new List() + { + new Activity() + { + Product = Product.Mortgage, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept1.DepartmentId, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.Mortgage, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept1.DepartmentId, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.NewChecking, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept1.DepartmentId, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.Vehicle, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept2.DepartmentId, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.Mortgage, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept2.DepartmentId, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.NewChecking, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept2.DepartmentId, + State = ActivityState.Accepted + }, + // Won't count. + new Activity() + { + Product = Product.Mortgage, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept2.DepartmentId, + State = ActivityState.Rejected + }, + new Activity() + { + Product = Product.NewChecking, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept2.DepartmentId, + State = ActivityState.ManualReview + } + }; + + var commissions = new List() + { + new Commission() + { + Product = Product.Mortgage, + RatePerProduct = 5, + Role = userDept1.Role + }, + new Commission() + { + Product = Product.NewChecking, + RatePerProduct = 25, + Role = userDept1.Role + }, + new Commission() + { + Product = Product.Vehicle, + RatePerProduct = 100, + Role = userDept1.Role + } + }; + + var goals = new List(); + + _mockActivityRepository + .Setup(activity => activity.FindEmployeeActivityByMonthRange(employee, It.IsAny())) + .ReturnsAsync(activity); + _mockActivityRepository + .Setup(activity => + activity.FindDepartmentActivityByMonthRange(userDept1.DepartmentId, It.IsAny())) + .ReturnsAsync(activity.Where(x => x.DepartmentId == userDept1.DepartmentId).ToList()); + _mockActivityRepository + .Setup(activity => + activity.FindDepartmentActivityByMonthRange(userDept2.DepartmentId, It.IsAny())) + .ReturnsAsync(activity.Where(x => x.DepartmentId == userDept2.DepartmentId).ToList()); + _mockCommissionRepository.Setup(commission => commission.FindProductCommissions(userDept1.Role)) + .ReturnsAsync(commissions); + _mockGoalRepository.Setup(goal => + goal.FindPersonnelGoalsByMonthRange(It.IsAny(), It.IsAny>(), + It.IsAny())).ReturnsAsync(goals); + _mockGoalRepository.Setup(goal => + goal.FindDepartmentGoalsByMonthRange(It.IsAny(), It.IsAny())) + .ReturnsAsync(goals); + + var payout = + await _payoutService.BuildUserDepartmentsPayoutView(employee, + new MonthRange(theTestDate.Year, theTestDate.Month)); + Assert.NotNull(payout); + Assert.NotNull(payout.ElementAt(0).CommissionPayouts); + Assert.NotNull(payout.ElementAt(0).CommissionPayouts); + Assert.IsNotEmpty(payout.ElementAt(0).CommissionPayouts); + Assert.NotNull(payout.ElementAt(1).CommissionPayouts); + Assert.NotNull(payout.ElementAt(1).CommissionPayouts); + Assert.IsNotEmpty(payout.ElementAt(1).CommissionPayouts); + Assert.NotNull(payout.First().GoalPayouts); + Assert.IsEmpty(payout.First().GoalPayouts); + + var userDept1Payouts = payout.FirstOrDefault(x => x.Department == userDept1); + var userDept2Payouts = payout.FirstOrDefault(x => x.Department == userDept2); + + var dept1Mortgage = userDept1Payouts.CommissionPayouts.FirstOrDefault(x => x.Product == Product.Mortgage); + Assert.AreEqual(10, dept1Mortgage.PayoutTotal); + var dept1Checking = + userDept1Payouts.CommissionPayouts.FirstOrDefault(x => x.Product == Product.NewChecking); + Assert.AreEqual(25, dept1Checking.PayoutTotal); + + var dept2Mortgage = userDept2Payouts.CommissionPayouts.FirstOrDefault(x => x.Product == Product.Mortgage); + Assert.AreEqual(5, dept2Mortgage.PayoutTotal); + var dept2Checking = + userDept2Payouts.CommissionPayouts.FirstOrDefault(x => x.Product == Product.NewChecking); + Assert.AreEqual(25, dept2Checking.PayoutTotal); + var dept2Vehicle = userDept2Payouts.CommissionPayouts.FirstOrDefault(x => x.Product == Product.Vehicle); + Assert.AreEqual(100, dept2Vehicle.PayoutTotal); + } + + /** + * Create a user with two departments, activity, no goals, commissions and a role that + * has a matching payout plan; should see commissions for both departments. + * Have them be different roles! + */ + [Test] + public async Task Commission_Only_User_Multi_Dept_MultiRole() + { + _payoutService = new PayoutService(_mockUserRepository.Object, _mockPayoutRepository.Object, + _mockUnitOfWork.Object, _payoutCalculateService, _mockAccountingRepository.Object, _mockActivityService.Object); + var theTestDate = DateTime.Today; + AppUser employee = GetBasicTestEmployee(Role.Teller); + var userDept1 = GetBasicDepartmentAssignment(employee, theTestDate, Role.Teller); + var userDept2 = GetOtherDepartmentAssignment(651, employee, theTestDate); + userDept2.Role = Role.ABM; + + employee.Departments.Add(userDept1); + employee.Departments.Add(userDept2); + + var activity = new List() + { + new Activity() + { + Product = Product.Mortgage, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept1.DepartmentId, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.Mortgage, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept1.DepartmentId, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.NewChecking, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept1.DepartmentId, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.Vehicle, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept2.DepartmentId, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.Mortgage, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept2.DepartmentId, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.NewChecking, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept2.DepartmentId, + State = ActivityState.Accepted + } + }; + + var commissions = new List() + { + new Commission() + { + Product = Product.Mortgage, + RatePerProduct = 5, + Role = userDept1.Role + }, new Commission() + { + Product = Product.Mortgage, + RatePerProduct = 5, + Role = userDept2.Role + }, + new Commission() + { + Product = Product.NewChecking, + RatePerProduct = 25, + Role = userDept1.Role + }, + new Commission() + { + Product = Product.NewChecking, + RatePerProduct = 25, + Role = userDept2.Role + }, + new Commission() + { + Product = Product.Vehicle, + RatePerProduct = 100, + Role = userDept2.Role + } + }; + + var goals = new List(); + + _mockActivityRepository + .Setup(activity => activity.FindEmployeeActivityByMonthRange(employee, It.IsAny())) + .ReturnsAsync(activity); + _mockActivityRepository + .Setup(activity => + activity.FindDepartmentActivityByMonthRange(userDept1.DepartmentId, It.IsAny())) + .ReturnsAsync(activity.Where(x => x.DepartmentId == userDept1.DepartmentId).ToList()); + _mockActivityRepository + .Setup(activity => + activity.FindDepartmentActivityByMonthRange(userDept2.DepartmentId, It.IsAny())) + .ReturnsAsync(activity.Where(x => x.DepartmentId == userDept2.DepartmentId).ToList()); + _mockCommissionRepository.Setup(commission => commission.FindProductCommissions(userDept1.Role)) + .ReturnsAsync(commissions.Where(x => x.Role == userDept1.Role).ToList()); + _mockCommissionRepository.Setup(commission => commission.FindProductCommissions(userDept2.Role)) + .ReturnsAsync(commissions.Where(x => x.Role == userDept2.Role).ToList()); + _mockGoalRepository.Setup(goal => + goal.FindPersonnelGoalsByMonthRange(It.IsAny(), It.IsAny>(), + It.IsAny())).ReturnsAsync(goals); + _mockGoalRepository.Setup(goal => + goal.FindDepartmentGoalsByMonthRange(It.IsAny(), It.IsAny())) + .ReturnsAsync(goals); + + var payout = + await _payoutService.BuildUserDepartmentsPayoutView(employee, + new MonthRange(theTestDate.Year, theTestDate.Month)); + Assert.NotNull(payout); + Assert.NotNull(payout.ElementAt(0).CommissionPayouts); + Assert.NotNull(payout.ElementAt(0).CommissionPayouts); + Assert.IsNotEmpty(payout.ElementAt(0).CommissionPayouts); + Assert.NotNull(payout.ElementAt(1).CommissionPayouts); + Assert.NotNull(payout.ElementAt(1).CommissionPayouts); + Assert.IsNotEmpty(payout.ElementAt(1).CommissionPayouts); + Assert.NotNull(payout.First().GoalPayouts); + Assert.IsEmpty(payout.First().GoalPayouts); + + var userDept1Payouts = payout.FirstOrDefault(x => x.Department == userDept1); + var userDept2Payouts = payout.FirstOrDefault(x => x.Department == userDept2); + + var dept1Mortgage = userDept1Payouts.CommissionPayouts.FirstOrDefault(x => x.Product == Product.Mortgage); + Assert.AreEqual(10, dept1Mortgage.PayoutTotal); + var dept1Checking = + userDept1Payouts.CommissionPayouts.FirstOrDefault(x => x.Product == Product.NewChecking); + Assert.AreEqual(25, dept1Checking.PayoutTotal); + + var dept2Mortgage = userDept2Payouts.CommissionPayouts.FirstOrDefault(x => x.Product == Product.Mortgage); + Assert.AreEqual(5, dept2Mortgage.PayoutTotal); + var dept2Checking = + userDept2Payouts.CommissionPayouts.FirstOrDefault(x => x.Product == Product.NewChecking); + Assert.AreEqual(25, dept2Checking.PayoutTotal); + var dept2Vehicle = userDept2Payouts.CommissionPayouts.FirstOrDefault(x => x.Product == Product.Vehicle); + Assert.AreEqual(100, dept2Vehicle.PayoutTotal); + } + + /** + * MSC Team lead is a great test scenario. They get paid for both personal and department goals, which this + * test demonstrates. + */ + [Test] + public async Task Only_PayoutPlan_Personal_And_Department() + { + _payoutService = new PayoutService(_mockUserRepository.Object, _mockPayoutRepository.Object, + _mockUnitOfWork.Object, _payoutCalculateService, _mockAccountingRepository.Object, _mockActivityService.Object); + var theTestDate = DateTime.Today; + AppUser employee = GetBasicTestEmployee(Role.MSCTeamLead); + var userDept1 = GetBasicDepartmentAssignment(employee, theTestDate, Role.MSCTeamLead); + employee.Departments.Add(userDept1); + + var activity = new List() + { + // Not this person, but dept 1 mortgage + new Activity() + { + Product = Product.Mortgage, + EmployeeId = "9898", + DepartmentId = userDept1.DepartmentId, + State = ActivityState.Accepted, + Detail = new ActivityDetail() + { + AccountBalance = 1000 + } + }, + new Activity() + { + Product = Product.Mortgage, + EmployeeId = "9898", + DepartmentId = userDept1.DepartmentId, + State = ActivityState.Rejected, + Detail = new ActivityDetail() + { + AccountBalance = 1000 + } + }, + new Activity() + { + // Not this person, but dept 1 mortgage + Product = Product.Mortgage, + EmployeeId = "9897", + DepartmentId = userDept1.DepartmentId, + State = ActivityState.Accepted, + Detail = new ActivityDetail() + { + AccountBalance = 1000 + } + }, + new Activity() + { + // Not this person, but dept 1 checking + Product = Product.NewChecking, + EmployeeId = "9896", + DepartmentId = userDept1.DepartmentId, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.Mortgage, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept1.DepartmentId, + State = ActivityState.Accepted, + Detail = new ActivityDetail() + { + AccountBalance = 1000 + } + }, + new Activity() + { + Product = Product.Mortgage, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept1.DepartmentId, + State = ActivityState.Accepted, + Detail = new ActivityDetail() + { + AccountBalance = 1000 + } + }, + new Activity() + { + Product = Product.NewChecking, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept1.DepartmentId, + State = ActivityState.Accepted + }, + new Activity() + { + Product = Product.NewChecking, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept1.DepartmentId, + State = ActivityState.ManualReview + } + }; + + var commissions = new List(); + + var personalGoals = new List() + { + new Goal() + { + Product = Product.Mortgage, + Value = 2000, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept1.DepartmentId + }, + // Employee will miss this goal + new Goal() + { + Product = Product.NewChecking, + Value = 2, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept1.DepartmentId + } + }; + + var departmentGoals = new List() + { + new Goal() + { + Product = Product.Mortgage, + Value = 4000, + DepartmentId = userDept1.DepartmentId + }, + new Goal() + { + Product = Product.NewChecking, + Value = 2, + DepartmentId = userDept1.DepartmentId + } + }; + + _mockActivityRepository + .Setup(activity => activity.FindEmployeeActivityByMonthRange(employee, It.IsAny())) + .ReturnsAsync(activity.Where(x => x.EmployeeId == employee.EmployeeId).ToList()); + _mockActivityRepository + .Setup(activity => + activity.FindDepartmentActivityByMonthRange(userDept1.DepartmentId, It.IsAny())) + .ReturnsAsync(activity); + _mockCommissionRepository.Setup(commission => commission.FindProductCommissions(userDept1.Role)) + .ReturnsAsync(commissions); + _mockGoalRepository.Setup(goal => + goal.FindPersonnelGoalsByMonthRange(userDept1.DepartmentId, It.IsAny>(), + It.IsAny())).ReturnsAsync(personalGoals); + _mockGoalRepository.Setup(goal => + goal.FindDepartmentGoalsByMonthRange(userDept1.DepartmentId, It.IsAny())) + .ReturnsAsync(departmentGoals); + + var payouts = + await _payoutService.BuildUserDepartmentsPayoutView(employee, + new MonthRange(theTestDate.Year, theTestDate.Month)); + Assert.NotNull(payouts); + Assert.IsNotEmpty(payouts); + + var payout1 = payouts.First(); + Assert.NotNull(payout1.CommissionPayouts); + Assert.IsEmpty(payout1.CommissionPayouts); + Assert.NotNull(payout1.GoalPayouts); + Assert.IsNotEmpty(payout1.GoalPayouts); + Assert.AreEqual(3, payout1.GoalPayouts.Count()); + + // employee should have 2 department level, and a personal level payout. + var dept1Mortgage = payout1.GoalPayouts.FirstOrDefault(x => + x.Product == Product.Mortgage && + PayoutProfile.FindById(x.PayoutProfileId).Type == PayoutType.Department); + Assert.AreEqual(50, dept1Mortgage.PayoutTotal); + + var dept1Checking = payout1.GoalPayouts.FirstOrDefault(x => + x.Product == Product.NewChecking && + PayoutProfile.FindById(x.PayoutProfileId).Type == PayoutType.Department); + Assert.AreEqual(50, dept1Checking.PayoutTotal); + + var persMortgage = payout1.GoalPayouts.FirstOrDefault(x => + x.Product == Product.Mortgage && PayoutProfile.FindById(x.PayoutProfileId).Type == PayoutType.Personal); + Assert.AreEqual(100, persMortgage.PayoutTotal); + } + + [Test] + public async Task Payout_Terminated_Basic() + { + _payoutService = new PayoutService(_mockUserRepository.Object, _mockPayoutRepository.Object, + _mockUnitOfWork.Object, _mockPayoutCalculateService.Object, _mockAccountingRepository.Object, _mockActivityService.Object); + var theTestDate = DateTime.Today; + var range = new MonthRange(theTestDate); + var manager = GetBasicTestEmployee(Role.Manager); + var employee = GetBasicTestEmployee(Role.Teller); + var userDept1 = GetBasicDepartmentAssignment(employee, theTestDate, Role.Teller); + employee.Departments.Add(userDept1); + + // We need the user to have been in the department last month.... + userDept1.StartDate= userDept1.StartDate.AddMonths(-1); + + _mockPayoutRepository + .Setup(activity => activity.GetEmployeePayoutsForPeriod(employee, It.IsAny())) + .ReturnsAsync(new List()); + + var deptPayout = new DepartmentPayout(employee, userDept1, range, new List(), new List()) + { + CommissionPayouts = new List() + { + new PayoutLineItem() + { + Product = Product.Mortgage, + ActivityTotal = 500, + CommissionId = 19, + CommissionValue = 10, + PayoutTotal = 5000 + } + } + }; + _mockPayoutCalculateService + .Setup(x => x.BuildDepartmentPayoutView(employee, userDept1, It.IsAny())) + .ReturnsAsync(deptPayout); + + _mockPayoutRepository + .Setup(activity => activity.SavePayoutLineItemRangeAsync(It.IsAny>())) + .Verifiable(); + + _mockUnitOfWork.Setup(x => x.CompleteAsync()).Verifiable(); + + var receipt = await _payoutService.CreateTerminatedEmployeePayouts(employee, manager); + Assert.NotNull(receipt); + Assert.NotNull(receipt.LastMonthPayout); + Assert.NotNull(receipt.ThisMonthPayout); + + _mockPayoutRepository + .Verify(activity => activity.GetEmployeePayoutsForPeriod(employee, It.IsAny()), + Times.Exactly(4)); + _mockPayoutCalculateService + .Verify(x => x.BuildDepartmentPayoutView(employee, userDept1, It.IsAny()), + Times.Exactly(2)); + _mockPayoutRepository + .Verify(activity => activity.SavePayoutLineItemRangeAsync(It.IsAny>()), + Times.Exactly(2)); + _mockUnitOfWork.Verify(x => x.CompleteAsync(), Times.Exactly(1)); + } + + [Test] + public async Task RetrieveMonthlyIncentivePayouts_Basic() + { + _payoutService = new PayoutService(_mockUserRepository.Object, _mockPayoutRepository.Object, + _mockUnitOfWork.Object, _mockPayoutCalculateService.Object, _mockAccountingRepository.Object, _mockActivityService.Object); + var theTestDate = DateTime.Today; + var employee = GetBasicTestEmployee(Role.Teller); + var userDept1 = GetBasicDepartmentAssignment(employee, theTestDate, Role.Teller); + employee.Departments.Add(userDept1); + + _mockPayoutRepository + .Setup(activity => activity.GetPayoutLineItemsForPeriodAsync( It.IsAny())) + .ReturnsAsync(new List() + { + new PayoutLineItem() + { + Product = Product.Mortgage, + Role = Role.Teller, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept1.DepartmentId, + PayoutTotal = 50 + }, + new PayoutLineItem() + { + Product = Product.Vehicle, + Role = Role.Teller, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept1.DepartmentId, + PayoutTotal = 60 + } + }); + + _mockUserRepository.Setup(x => x.FindUserByEmployeeIdAsync(It.IsAny())).ReturnsAsync(employee); + + var results = await _payoutService.RetrieveMonthlyIncentivePayouts(theTestDate); + Assert.NotNull(results); + Assert.IsNotEmpty(results); + + _mockPayoutRepository + .Verify(activity => activity.GetPayoutLineItemsForPeriodAsync(It.IsAny()), Times.Once); + + _mockUserRepository.Verify(x => x.FindUserByEmployeeIdAsync(It.IsAny()), Times.Once); + } + + [Test] + public async Task CreateMonthlyIncentivePayouts_Basic() + { + _payoutService = new PayoutService(_mockUserRepository.Object, _mockPayoutRepository.Object, _mockUnitOfWork.Object, _mockPayoutCalculateService.Object, + _mockAccountingRepository.Object, _mockActivityService.Object); + + var theTestDate = DateTime.Today; + var range = new MonthRange(theTestDate); + var employee = GetBasicTestEmployee(Role.Teller); + var userDept1 = GetBasicDepartmentAssignment(employee, theTestDate, Role.Teller); + employee.Departments.Add(userDept1); + var employees = new List() + { + employee + }; + + _mockPayoutRepository + .Setup(activity => activity.GetPayoutLineItemsForPeriodAsync(It.IsAny())) + .ReturnsAsync(new List() + { + new PayoutLineItem() + { + Product = Product.Mortgage, + Role = Role.Teller, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept1.DepartmentId, + PayoutTotal = 50 + }, + new PayoutLineItem() + { + Product = Product.Vehicle, + Role = Role.Teller, + EmployeeId = employee.EmployeeId, + DepartmentId = userDept1.DepartmentId, + PayoutTotal = 60 + } + }); + + _mockUserRepository.Setup(x => x.FindUserByEmployeeIdAsync(It.IsAny())).ReturnsAsync(employee); + _mockUnitOfWork.Setup(x => x.CompleteAsync()).Verifiable(); + _mockUserRepository.Setup(x => x.GetAllUsersToPayoutAsync(It.IsAny())).ReturnsAsync(employees); + _mockUnitOfWork.Setup(x => x.CompleteAsync()).Verifiable(); + + _mockPayoutRepository + .Setup(activity => activity.GetEmployeePayoutsForPeriod(employee, It.IsAny())) + .ReturnsAsync(new List()); + + var deptPayout = new DepartmentPayout(employee, userDept1, range, new List(), new List()) + { + CommissionPayouts = new List() + { + new PayoutLineItem() + { + Product = Product.Mortgage, + ActivityTotal = 500, + CommissionId = 19, + CommissionValue = 10, + PayoutTotal = 5000 + } + } + }; + + _mockPayoutCalculateService + .Setup(x => x.BuildDepartmentPayoutView(employee, userDept1, It.IsAny())) + .ReturnsAsync(deptPayout); + + _mockPayoutRepository + .Setup(activity => activity.SavePayoutLineItemRangeAsync(It.IsAny>())) + .Verifiable(); + + var request = new PayoutIncentiveRequest() + { + MonthsBack = -2, + }; + + var results = await _payoutService.CreateMonthlyIncentivePayouts(request); + Assert.NotNull(results); + + _mockUserRepository.Verify(x => x.GetAllUsersToPayoutAsync(It.IsAny()), Times.Once); + _mockUnitOfWork.Verify(x => x.CompleteAsync(), Times.Once); + } + + private AppUser GetBasicTestEmployee(Role role) + { + AppUser employee = new AppUser() + { + Departments = new List(), + AdEmail = "testpayout@test.com", + BaseRole = "BM", + EmployeeId = "11771", + FullName = "Jane Test", + HomeDepartment = "Home", + PersonId = 11234 + }; + return employee; + } + + private UserDepartment GetBasicDepartmentAssignment(AppUser employee, DateTime theTestDate, Role role) + { + var department = new Department() + { + State = DepartmentState.Active, + DepartmentName = "Test1", + BusinessLineCode = BusinessLine.Operations.Code, + Id = 99 + }; + + var assignment1 = new UserDepartment() + { + DepartmentId = 99, + Department = department, + Employee = employee, + Note = "Default!", + IsHome = true, + StartDate = new DateTime(theTestDate.Year, theTestDate.Month, 1), + Role = role, + SlidingPayout = 100 + }; + return assignment1; + } + + private UserDepartment GetOtherDepartmentAssignment(int DepartmentId, AppUser employee, DateTime theTestDate) + { + var department = new Department() + { + State = DepartmentState.Active, + DepartmentName = "Test1", + BusinessLineCode = BusinessLine.Operations.Code, + Id = DepartmentId + }; + + var assignment1 = new UserDepartment() + { + DepartmentId = DepartmentId, + Department = department, + Employee = employee, + Note = "Custom", + IsHome = false, + StartDate = new DateTime(theTestDate.Year, theTestDate.Month, 1), + SlidingPayout = 100 + }; + return assignment1; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ProcessorHistoryServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ProcessorHistoryServiceTest.cs new file mode 100644 index 0000000..f774313 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/ProcessorHistoryServiceTest.cs @@ -0,0 +1,71 @@ +using System; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Moq; +using NUnit.Framework; + +namespace Application.UnitTests.Services +{ + public class ProcessorHistoryServiceTest + { + private ProcessorHistoryService _processorHistoryService; + private Mock _mockRepository; + + public ProcessorHistoryServiceTest() + { + _mockRepository = new Mock(); + _processorHistoryService = new ProcessorHistoryService(_mockRepository.Object, new Mock().Object); + } + + [Test] + public async Task GetProcessorHistory_OK() + { + _mockRepository.Setup(repo => repo.GetProcessorRunHistory(It.IsAny())).ReturnsAsync(new ProcessorHistoryRecord() + { + Process = Process.ImportCheckingAccountSalesFromDna, + LastProcessedRecordId = 5 + }).Verifiable(); + + var res = await _processorHistoryService.GetProcessorHistory(Process.ImportCheckingAccountSalesFromDna); + Assert.AreEqual(Process.ImportCheckingAccountSalesFromDna, res.Process); + Assert.AreEqual(5, res.LastProcessedRecordId); + + _mockRepository.Verify( x => x.GetProcessorRunHistory(It.Is(x => x == Process.ImportCheckingAccountSalesFromDna))); + } + + [Test] + public void GetProcessorHistory_Exception() + { + _mockRepository.Setup(repo => repo.GetProcessorRunHistory(It.IsAny())) + .ThrowsAsync(new Exception("oops")); + Assert.ThrowsAsync( async () => await _processorHistoryService.GetProcessorHistory(Process.ImportCheckingAccountSalesFromDna)); + } + + [Test] + public void SaveProcessorHistory_Exception() + { + _mockRepository.Setup(repo => repo.SaveProcessorRunHistory(It.IsAny())) + .ThrowsAsync(new Exception("Oops")); + + Assert.ThrowsAsync( async () => await _processorHistoryService.SaveProcessorHistory(new ProcessorHistoryRecord() + { + LastProcessedRecordId = 100 + })); + } + + [Test] + public async Task SaveProcessorHistory_OK() + { + _mockRepository.Setup(repo => repo.SaveProcessorRunHistory(It.IsAny())).ReturnsAsync(Process.ImportCheckingAccountSalesFromDna).Verifiable(); + + var res = await _processorHistoryService.SaveProcessorHistory(new ProcessorHistoryRecord() + { + LastProcessedRecordId = 100 + }); + Assert.AreEqual(Process.ImportCheckingAccountSalesFromDna, res); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Temenos/ImportCreditCardSalesServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Temenos/ImportCreditCardSalesServiceTest.cs new file mode 100644 index 0000000..6e90607 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Temenos/ImportCreditCardSalesServiceTest.cs @@ -0,0 +1,323 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using CsvHelper; +using CsvHelper.Configuration; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using Moq; +using NUnit.Framework; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.API.Internal.Operations.IncentiveApi.Services.Temenos; +using LCU.API.Internal.Operations.IncentiveApi.Models.Temenos; +using LCU.API.Internal.Operations.IncentiveApi.Models.Constants; +using LCU.Library.Internal.FileOperationsModels.Models; + +namespace Application.UnitTests.Services.Temenos +{ + public class ImportCreditCardSalesServiceTest + { + private ImportCreditCardSalesService _importCreditCardSalesService; + private Mock _mockUserRepository; + private Mock _mockActivityService; + private Mock _mockFileOperationsApiService; + private Mock _mockDeadActivityService; + + [SetUp] + public void Setup() + { + _mockUserRepository = new Mock(); + _mockActivityService = new Mock(); + _mockFileOperationsApiService = new Mock(); + _mockDeadActivityService = new Mock(); + _importCreditCardSalesService = new ImportCreditCardSalesService(_mockActivityService.Object, + _mockUserRepository.Object, + _mockFileOperationsApiService.Object, + _mockDeadActivityService.Object); + } + + [Test] + public async Task HandleImportCreditCardSales_BusinessPersonalAndDeadActivities_OK() + { + string fileContents = GetFileContent(); + var fileResponse = new string[] { "filename" }; + _mockFileOperationsApiService.Setup(x => x.BuildDynamicFileList(It.IsAny())).ReturnsAsync(fileResponse); + _mockFileOperationsApiService.Setup(x => x.GetFileContent(It.IsAny())).ReturnsAsync(fileContents); + + AppUser user = GetAppUser(); + _mockUserRepository.Setup(x => x.FindUserByAdEmailAsync(It.IsAny())).ReturnsAsync(user); + + _mockActivityService.Setup(x => x.SaveActivityRangeAsync(It.IsAny>())); + _mockDeadActivityService.Setup(x => x.SaveDeadActivityRange(It.IsAny>())); + + var request = new ImportCreditCardSalesRequest() + { + FilePath = "path", + DynamicFileName = "filename" + + }; + + await _importCreditCardSalesService.HandleImportCreditCardSales(request); + + _mockFileOperationsApiService.Verify(x => x.BuildDynamicFileList(It.IsAny()), Times.Once); + _mockFileOperationsApiService.Verify(x => x.GetFileContent(It.IsAny()), Times.Once); + _mockUserRepository.Verify(x => x.FindUserByAdEmailAsync(It.IsAny()), Times.Exactly(3)); + _mockActivityService.Verify(x => x.SaveActivityRangeAsync(It.IsAny>()), Times.Once); + _mockDeadActivityService.Verify(x => x.SaveDeadActivityRange(It.IsAny>()), Times.Once); + } + + [Test] + public async Task HandleImportCreditCardSales_UserRepositoryFails_ActivitesSavedAsDead() + { + string fileContents = GetFileContent(); + var fileResponse = new string[] { "filename" }; + _mockFileOperationsApiService.Setup(x => x.BuildDynamicFileList(It.IsAny())).ReturnsAsync(fileResponse); + _mockFileOperationsApiService.Setup(x => x.GetFileContent(It.IsAny())).ReturnsAsync(fileContents); + _mockUserRepository.Setup(x => x.FindUserByAdEmailAsync(It.IsAny())).ThrowsAsync(new Exception("Failure")); + _mockDeadActivityService.Setup(x => x.SaveDeadActivityRange(It.IsAny>())); + + var request = new ImportCreditCardSalesRequest() + { + FilePath = "path", + DynamicFileName = "filename" + + }; + + await _importCreditCardSalesService.HandleImportCreditCardSales(request); + + _mockFileOperationsApiService.Verify(x => x.BuildDynamicFileList(It.IsAny()), Times.Once); + _mockFileOperationsApiService.Verify(x => x.GetFileContent(It.IsAny()), Times.Once); + _mockUserRepository.Verify(x => x.FindUserByAdEmailAsync(It.IsAny()), Times.Exactly(3)); + _mockActivityService.Verify(x => x.SaveActivityRangeAsync(It.IsAny>()), Times.Never); + _mockDeadActivityService.Verify(x => x.SaveDeadActivityRange(It.IsAny>()), Times.Once); + + } + + [Test] + public async Task HandleImportCreditCardSales_UserHomeDeptIsNull_ActivitesSavedAsDead() + { + string fileContents = GetFileContent(); + var fileResponse = new string[] { "filename" }; + _mockFileOperationsApiService.Setup(x => x.BuildDynamicFileList(It.IsAny())).ReturnsAsync(fileResponse); + _mockFileOperationsApiService.Setup(x => x.GetFileContent(It.IsAny())).ReturnsAsync(fileContents); + + AppUser user = GetAppUser(false); + _mockUserRepository.Setup(x => x.FindUserByAdEmailAsync(It.IsAny())).ReturnsAsync(user); + + _mockDeadActivityService.Setup(x => x.SaveDeadActivityRange(It.IsAny>())); + + var request = new ImportCreditCardSalesRequest() + { + FilePath = "path", + DynamicFileName = "filename" + + }; + + await _importCreditCardSalesService.HandleImportCreditCardSales(request); + + _mockFileOperationsApiService.Verify(x => x.BuildDynamicFileList(It.IsAny()), Times.Once); + _mockFileOperationsApiService.Verify(x => x.GetFileContent(It.IsAny()), Times.Once); + _mockUserRepository.Verify(x => x.FindUserByAdEmailAsync(It.IsAny()), Times.Exactly(3)); + _mockActivityService.Verify(x => x.SaveActivityRangeAsync(It.IsAny>()), Times.Never); + _mockDeadActivityService.Verify(x => x.SaveDeadActivityRange(It.IsAny>()), Times.Once); + } + + [Test] + public async Task HandleImportCreditCardSales_DeadActivityServiceFails_OtherActivitiesSave() + { + string fileContents = GetFileContent(); + var fileResponse = new string[] { "filename" }; + _mockFileOperationsApiService.Setup(x => x.BuildDynamicFileList(It.IsAny())).ReturnsAsync(fileResponse); + _mockFileOperationsApiService.Setup(x => x.GetFileContent(It.IsAny())).ReturnsAsync(fileContents); + + AppUser user = GetAppUser(); + _mockUserRepository.Setup(x => x.FindUserByAdEmailAsync(It.IsAny())).ReturnsAsync(user); + + _mockActivityService.Setup(x => x.SaveActivityRangeAsync(It.IsAny>())); + _mockDeadActivityService.Setup(x => x.SaveDeadActivityRange(It.IsAny>())).ThrowsAsync(new Exception("Failure")); + + var request = new ImportCreditCardSalesRequest() + { + FilePath = "path", + DynamicFileName = "filename" + + }; + + await _importCreditCardSalesService.HandleImportCreditCardSales(request); + + _mockFileOperationsApiService.Verify(x => x.BuildDynamicFileList(It.IsAny()), Times.Once); + _mockFileOperationsApiService.Verify(x => x.GetFileContent(It.IsAny()), Times.Once); + _mockUserRepository.Verify(x => x.FindUserByAdEmailAsync(It.IsAny()), Times.Exactly(3)); + _mockActivityService.Verify(x => x.SaveActivityRangeAsync(It.IsAny>()), Times.Once); + _mockDeadActivityService.Verify(x => x.SaveDeadActivityRange(It.IsAny>()), Times.Once); + } + + [Test] + public void HandleImportCreditCardSales_FileOperationsFindFilesFails_ExceptionThrown() + { + string fileContents = GetFileContent(); + var fileResponse = new string[] { "filename" }; + _mockFileOperationsApiService.Setup(x => x.BuildDynamicFileList(It.IsAny())).ThrowsAsync(new Exception("Failure")); + + var request = new ImportCreditCardSalesRequest() + { + FilePath = "path", + DynamicFileName = "filename" + + }; + + Assert.ThrowsAsync(() => _importCreditCardSalesService.HandleImportCreditCardSales(request)); + + _mockFileOperationsApiService.Verify(x => x.BuildDynamicFileList(It.IsAny()), Times.Once); + _mockFileOperationsApiService.Verify(x => x.GetFileContent(It.IsAny()), Times.Never); + _mockUserRepository.Verify(x => x.FindUserByAdEmailAsync(It.IsAny()), Times.Never); + _mockActivityService.Verify(x => x.SaveActivityRangeAsync(It.IsAny>()), Times.Never); + } + + [Test] + public void HandleImportCreditCardSales_FileOperationsGetFileContentFails_ExceptionThrown() + { + string fileContents = GetFileContent(); + var fileResponse = new string[] { "filename" }; + _mockFileOperationsApiService.Setup(x => x.BuildDynamicFileList(It.IsAny())).ReturnsAsync(fileResponse); + _mockFileOperationsApiService.Setup(x => x.GetFileContent("path\\filename")).ThrowsAsync(new Exception("Failure")); + + var request = new ImportCreditCardSalesRequest() + { + FilePath = "path", + DynamicFileName = "filename" + + }; + + Assert.ThrowsAsync(() => _importCreditCardSalesService.HandleImportCreditCardSales(request)); + + _mockFileOperationsApiService.Verify(x => x.BuildDynamicFileList(It.IsAny()), Times.Once); + _mockFileOperationsApiService.Verify(x => x.GetFileContent(It.IsAny()), Times.Once); + _mockUserRepository.Verify(x => x.FindUserByAdEmailAsync(It.IsAny()), Times.Never); + _mockActivityService.Verify(x => x.SaveActivityRangeAsync(It.IsAny>()), Times.Never); + } + + private static AppUser GetAppUser(bool assignHomeDepartment = true) + { + var department = new Department + { + DepartmentName = "Dept1", + Id = 55, + State = DepartmentState.Active + }; + var user = new AppUser + { + AdEmail = "johndoe@test.com", + EmployeeId = "11771", + FullName = "John Doe", + Departments = new List() + }; + var assignment = new UserDepartment + { + Id = 99, + Department = department, + Employee = user, + Role = Role.BM, + IsHome = true, + StartDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1), + EndDate = null + }; + + if (assignHomeDepartment) + { + user.Departments.Add(assignment); + } + + return user; + } + + private static string GetFileContent() + { + var fileContent = new List() + { + new() + { + ApplicationID = "123", + SubProduct = TemenosImportConstants.PersonalCardSubProduct, + FinalLoanAmount = "$1000.00", + DisbursedDate = DateTime.Now.AddDays(-1).ToString("MM/dd/yyyy"), + ApplicantFirstName = "FName", + ApplicantLastName = "LName", + OrganizationName = string.Empty, + LOResponsibleEmployeeEmail = string.Empty, + LODirectLoanOfficer = string.Empty, + ReferringEmployeeEmail = "tester@mail.com", + }, + new() + { + ApplicationID = "456", + SubProduct = TemenosImportConstants.PersonalCardSubProduct, + FinalLoanAmount = "$1000.00", + DisbursedDate = DateTime.Now.AddDays(-1).ToString("MM/dd/yyyy"), + ApplicantFirstName = "FName", + ApplicantLastName = "LName", + OrganizationName = string.Empty, + LOResponsibleEmployeeEmail = string.Empty, + LODirectLoanOfficer = "LastName, FirstName", + ReferringEmployeeEmail = string.Empty, + }, + new() + { + ApplicationID = "789", + SubProduct = "BusinessCard", + FinalLoanAmount = "$1000.00", + DisbursedDate = DateTime.Now.AddDays(-1).ToString("MM/dd/yyyy"), + ApplicantFirstName = string.Empty, + ApplicantLastName = string.Empty, + OrganizationName = "Business Name", + LOResponsibleEmployeeEmail = "tester@mail.com", + LODirectLoanOfficer = string.Empty, + ReferringEmployeeEmail = string.Empty, + }, + new() + { + ApplicationID = "987", + SubProduct = "BusinessCard", + FinalLoanAmount = "$1000.00", + DisbursedDate = DateTime.Now.AddDays(-1).ToString("MM/dd/yyyy"), + ApplicantFirstName = "Dead", + ApplicantLastName = "Activity", + OrganizationName = string.Empty, + LOResponsibleEmployeeEmail = string.Empty, + LODirectLoanOfficer = string.Empty, + ReferringEmployeeEmail = string.Empty, + }, + new() + { + ApplicationID = "654", + SubProduct = string.Empty, + FinalLoanAmount = "$1000.00", + DisbursedDate = DateTime.Now.AddDays(-1).ToString("MM/dd/yyyy"), + ApplicantFirstName = "Record", + ApplicantLastName = "Ignored", + OrganizationName = string.Empty, + LOResponsibleEmployeeEmail = string.Empty, + LODirectLoanOfficer = string.Empty, + ReferringEmployeeEmail = string.Empty, + } + }; + + var writer = new StringWriter(); + + var config = new CsvConfiguration(CultureInfo.InvariantCulture) { Delimiter = "|", HasHeaderRecord = false }; + + using (var csv = new CsvWriter(writer, config)) + { + csv.WriteRecords(fileContent); + } + + return writer.ToString(); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Temenos/ImportIndirectVehicleSalesServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Temenos/ImportIndirectVehicleSalesServiceTest.cs new file mode 100644 index 0000000..ef8027c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/Temenos/ImportIndirectVehicleSalesServiceTest.cs @@ -0,0 +1,673 @@ +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using Moq; +using NUnit.Framework; +using System.Threading.Tasks; +using System; +using System.Collections.Generic; +using CsvHelper.Configuration; +using CsvHelper; +using System.Globalization; +using System.IO; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.Exceptions; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.API.Internal.Operations.IncentiveApi.Services.Temenos; +using LCU.API.Internal.Operations.IncentiveApi.Models.Temenos; +using LCU.Library.Internal.FileOperationsModels.Models; + +namespace Application.UnitTests.Services.Temenos +{ + public class ImportIndirectVehicleSalesServiceTest + { + private ImportIndirectVehicleSalesService _importIndirectVehicleSalesService; + private Mock _mockUserRepository; + private Mock _mockActivityService; + private Mock _mockFileOperationsApiService; + private Mock _mockDeadActivityService; + + [SetUp] + public void SetUp() + { + _mockUserRepository = new Mock(); + _mockActivityService = new Mock(); + _mockFileOperationsApiService = new Mock(); + _mockDeadActivityService = new Mock(); + _importIndirectVehicleSalesService = new ImportIndirectVehicleSalesService(_mockActivityService.Object, + _mockUserRepository.Object, + _mockFileOperationsApiService.Object, + _mockDeadActivityService.Object); + } + + [Test] + public async Task HandleImportIndirectVehicleSales_DeadAndAcceptedActivities_Ok() + { + var directFileContent = new List() + { + new DirectVehicleSaleRecord() + { + ApplicantSSN = "123-45-6789", + ApplicationID = "123", + CreatedDate = DateTime.Now.AddDays(-50).ToString(), + ReferringEmployeeEmail = "matched@mail.com" + }, + new DirectVehicleSaleRecord() + { + ApplicantSSN = "555-55-5555", + ApplicationID = "321", + CreatedDate = DateTime.Now.AddDays(-30).ToString(), + ReferringEmployeeEmail = "dead@mail.com" + } + }; + var directWriter = GetWriter(directFileContent); + + var indirectFileContent = new List() + { + new IndirectVehicleSaleRecord() + { + ApplicantSSN = "123-45-6789", + CreatedDate = DateTime.Now.AddDays(-40).ToString(), + ApplicationID = "123", + DisbursedDate = DateTime.Now.ToString() + }, + new IndirectVehicleSaleRecord() + { + ApplicantSSN = "555-55-5555", + CreatedDate = DateTime.Now.AddDays(-10).ToString(), + ApplicationID = "321", + DisbursedDate = DateTime.Now.ToString() + } + }; + var indirectWriter = GetWriter(indirectFileContent); + + var fileResponse = new string[] { "filename" }; + _mockFileOperationsApiService.Setup(x => x.BuildDynamicFileList(It.IsAny())).ReturnsAsync(fileResponse); + + _mockFileOperationsApiService.SetupSequence(x => x.GetFileContent(It.IsAny())) + .ReturnsAsync(directWriter.ToString()) + .ReturnsAsync(indirectWriter.ToString()); + + var user = GetAppUser("matched@mail.com"); + _mockUserRepository.SetupSequence(x => x.FindUserByAdEmailAsync(It.IsAny())) + .ReturnsAsync(user) + .ReturnsAsync((AppUser)null); + + var request = new ImportIndirectVehicleSalesRequest() + { + FilePath = "path\\", + DirectDynamicFileName = "directFileName", + IndirectDynamicFileName = "indirectFileName" + }; + + await _importIndirectVehicleSalesService.HandleImportIndirectVehicleSales(request); + + _mockDeadActivityService.Verify(x => x.SaveDeadActivityRange(It.IsAny>()), Times.Once); + _mockActivityService.Verify(x => x.SaveActivityRangeAsync(It.IsAny>()), Times.Once); + _mockUserRepository.Verify(x => x.FindUserByAdEmailAsync(It.IsAny()), Times.Exactly(2)); + _mockFileOperationsApiService.Verify(x => x.GetFileContent(It.IsAny()), Times.Exactly(2)); + } + + [Test] + public async Task HandleImportIndirectVehicleSales_NoMatches_NoActivitiesSaved() + { + + var directFileContent = new List() + { + new DirectVehicleSaleRecord() + { + ApplicantSSN = "123-45-6789", + ApplicationID = "123", + CreatedDate = DateTime.Now.AddDays(-10).ToString(), + ReferringEmployeeEmail = "test@mail.com" + }, + new DirectVehicleSaleRecord() + { + ApplicantSSN = "555-55-5555", + ApplicationID = "321", + CreatedDate = DateTime.Now.AddDays(-10).ToString(), + ReferringEmployeeEmail = "test@mail.com" + } + }; + var directWriter = GetWriter(directFileContent); + + var indirectFileContent = new List() + { + new IndirectVehicleSaleRecord() + { + ApplicantSSN = "987-65-4321", + CreatedDate = DateTime.Now.AddDays(-40).ToString(), + ApplicationID = "123", + DisbursedDate = DateTime.Now.ToString() + }, + new IndirectVehicleSaleRecord() + { + ApplicantSSN = "555-55-5555", + CreatedDate = DateTime.Now.AddDays(-20).ToString(), + ApplicationID = "321", + DisbursedDate = DateTime.Now.ToString() + } + }; + var indirectWriter = GetWriter(indirectFileContent); + + var fileResponse = new string[] { "filename" }; + _mockFileOperationsApiService.Setup(x => x.BuildDynamicFileList(It.IsAny())).ReturnsAsync(fileResponse); + + _mockFileOperationsApiService.SetupSequence(x => x.GetFileContent(It.IsAny())) + .ReturnsAsync(directWriter.ToString()) + .ReturnsAsync(indirectWriter.ToString()); + + var user = GetAppUser("matched@mail.com"); + _mockUserRepository.SetupSequence(x => x.FindUserByAdEmailAsync(It.IsAny())) + .ReturnsAsync(user) + .ReturnsAsync((AppUser)null); + + var request = new ImportIndirectVehicleSalesRequest() + { + FilePath = "path\\", + DirectDynamicFileName = "directFileName", + IndirectDynamicFileName = "indirectFileName" + }; + + await _importIndirectVehicleSalesService.HandleImportIndirectVehicleSales(request); + + _mockDeadActivityService.Verify(x => x.SaveDeadActivityRange(It.IsAny>()), Times.Never); + _mockActivityService.Verify(x => x.SaveActivityRangeAsync(It.IsAny>()), Times.Never); + _mockUserRepository.Verify(x => x.FindUserByAdEmailAsync(It.IsAny()), Times.Never); + _mockFileOperationsApiService.Verify(x => x.GetFileContent(It.IsAny()), Times.Exactly(2)); + } + + [Test] + public async Task HandleImportIndirectVehicleSales_OneIndirectMatch_CorrectActivitySaved() + { + + var referringEmail = "matched@mail.com"; + var matchedSsn = "123-45-6789"; + var directCreatedDate = DateTime.Now.AddDays(-50).ToString(); + var indirectCreatedDate = DateTime.Now.AddDays(-10).ToString(); + var disbursedDate = DateTime.Now.ToString(); + + var directFileContent = new List() + { + new DirectVehicleSaleRecord() + { + ApplicantSSN = matchedSsn, + ApplicationID = "123", + CreatedDate = directCreatedDate, + ReferringEmployeeEmail = referringEmail + }, + new DirectVehicleSaleRecord() + { + ApplicantSSN = "123-45-9876", + ApplicationID = "123", + CreatedDate = DateTime.Now.AddDays(-30).ToString(), + ReferringEmployeeEmail = "test@test.com" + } + }; + var directWriter = GetWriter(directFileContent); + + var indirectFileContent = new List() + { + new IndirectVehicleSaleRecord() + { + ApplicantSSN = matchedSsn, + CreatedDate = DateTime.Now.AddDays(-40).ToString(), + ApplicationID = "123", + DisbursedDate = disbursedDate + }, + new IndirectVehicleSaleRecord() + { + ApplicantSSN = "555-55-5555", + CreatedDate = DateTime.Now.AddDays(-60).ToString(), + ApplicationID = "123", + DisbursedDate = DateTime.Now.AddDays(-70).ToString() + }, + new IndirectVehicleSaleRecord() + { + ApplicantSSN = "999-99-9999", + CreatedDate = DateTime.Now.AddDays(-60).ToString(), + ApplicationID = "999", + DisbursedDate = DateTime.Now.AddDays(-65).ToString() + } + }; + var indirectWriter = GetWriter(indirectFileContent); + + var fileResponse = new string[] { "filename" }; + _mockFileOperationsApiService.Setup(x => x.BuildDynamicFileList(It.IsAny())).ReturnsAsync(fileResponse); + + _mockFileOperationsApiService.SetupSequence(x => x.GetFileContent(It.IsAny())) + .ReturnsAsync(directWriter.ToString()) + .ReturnsAsync(indirectWriter.ToString()); + + var user = GetAppUser(referringEmail); + _mockUserRepository.Setup(x => x.FindUserByAdEmailAsync(It.IsAny())).ReturnsAsync(user); + + _mockActivityService.Setup(x => x.SaveActivityRangeAsync(It.IsAny>())); + + var request = new ImportIndirectVehicleSalesRequest() + { + FilePath = "path\\", + DirectDynamicFileName = "filename", + IndirectDynamicFileName = "indirectFileName" + }; + + await _importIndirectVehicleSalesService.HandleImportIndirectVehicleSales(request); + + _mockActivityService.Verify(x => x.SaveActivityRangeAsync(It.Is>(x => x.Count == 1 + && x[0].EmployeeEmail == referringEmail + && x[0].SourceCreated == DateTime.Parse(disbursedDate) + && x[0].Accepted == DateTime.Parse(disbursedDate).Date.AddHours(12)))); + _mockDeadActivityService.Verify(x => x.SaveDeadActivityRange(It.IsAny>()), Times.Never); + _mockUserRepository.Verify(x => x.FindUserByAdEmailAsync(It.IsAny()), Times.Once); + _mockFileOperationsApiService.Verify(x => x.GetFileContent(It.IsAny()), Times.Exactly(2)); + } + + [Test] + public async Task HandleImportIndirectVehicleSales_MultipleIndirectApplicants_FirstRecordMatches() + { + var referringEmail = "matched@mail.com"; + var matchedSsn = "123-45-6789"; + var directCreatedDate = DateTime.Now.AddDays(-50).ToString(); + var indirectCreatedDate = DateTime.Now.AddDays(-10).ToString(); + var disbursedDate = DateTime.Now.ToString(); + var matchedApplicantName = "matchedName"; + + var directFileContent = new List() + { + new DirectVehicleSaleRecord() + { + ApplicantSSN = matchedSsn, + ApplicationID = "123", + CreatedDate = directCreatedDate, + ReferringEmployeeEmail = referringEmail + }, + new DirectVehicleSaleRecord() + { + ApplicantSSN = matchedSsn, + ApplicationID = "123", + CreatedDate = directCreatedDate, + ReferringEmployeeEmail = "secondapplicant@test.com" + } + }; + var directWriter = GetWriter(directFileContent); + + var indirectFileContent = new List() + { + new IndirectVehicleSaleRecord() + { + ApplicantSSN = matchedSsn, + CreatedDate = DateTime.Now.AddDays(-40).ToString(), + ApplicationID = "123", + DisbursedDate = disbursedDate, + ApplicantName = matchedApplicantName + }, + new IndirectVehicleSaleRecord() + { + ApplicantSSN = matchedSsn, + CreatedDate = DateTime.Now.AddDays(-40).ToString(), + ApplicationID = "123", + DisbursedDate = disbursedDate + } + }; + var indirectWriter = GetWriter(indirectFileContent); + + var fileResponse = new string[] { "filename" }; + _mockFileOperationsApiService.Setup(x => x.BuildDynamicFileList(It.IsAny())).ReturnsAsync(fileResponse); + + _mockFileOperationsApiService.SetupSequence(x => x.GetFileContent(It.IsAny())) + .ReturnsAsync(directWriter.ToString()) + .ReturnsAsync(indirectWriter.ToString()); + + var user = GetAppUser(referringEmail); + _mockUserRepository.Setup(x => x.FindUserByAdEmailAsync(It.IsAny())).ReturnsAsync(user); + + _mockActivityService.Setup(x => x.SaveActivityRangeAsync(It.IsAny>())); + + var request = new ImportIndirectVehicleSalesRequest() + { + FilePath = "path\\", + DirectDynamicFileName = "filename", + IndirectDynamicFileName = "indirectFileName" + }; + + await _importIndirectVehicleSalesService.HandleImportIndirectVehicleSales(request); + + _mockActivityService.Verify(x => x.SaveActivityRangeAsync(It.Is>(x => x.Count == 1 + && x[0].Detail.MemberName == matchedApplicantName + && x[0].EmployeeEmail == referringEmail + && x[0].SourceCreated == DateTime.Parse(disbursedDate) + && x[0].Accepted == DateTime.Parse(disbursedDate).Date.AddHours(12)))); + _mockDeadActivityService.Verify(x => x.SaveDeadActivityRange(It.IsAny>()), Times.Never); + _mockUserRepository.Verify(x => x.FindUserByAdEmailAsync(It.IsAny()), Times.Once); + _mockFileOperationsApiService.Verify(x => x.GetFileContent(It.IsAny()), Times.Exactly(2)); + } + + [Test] + public void HandleImportIndirectVehicleSales_DeadActivityServiceFails_ExceptionThrown() + { + var directFileContent = new List() + { + new DirectVehicleSaleRecord() + { + ApplicantSSN = "555-55-5555", + ApplicationID = "321", + CreatedDate = DateTime.Now.AddDays(-30).ToString(), + ReferringEmployeeEmail = "dead@mail.com" + } + }; + var directWriter = GetWriter(directFileContent); + + var indirectFileContent = new List() + { + new IndirectVehicleSaleRecord() + { + ApplicantSSN = "123-45-6789", + CreatedDate = DateTime.Now.AddDays(-40).ToString(), + ApplicationID = "123", + DisbursedDate = DateTime.Now.ToString() + }, + new IndirectVehicleSaleRecord() + { + ApplicantSSN = "555-55-5555", + CreatedDate = DateTime.Now.AddDays(-10).ToString(), + ApplicationID = "321", + DisbursedDate = DateTime.Now.ToString() + } + }; + var indirectWriter = GetWriter(indirectFileContent); + + var fileResponse = new string[] { "filename" }; + _mockFileOperationsApiService.Setup(x => x.BuildDynamicFileList(It.IsAny())).ReturnsAsync(fileResponse); + + _mockFileOperationsApiService.SetupSequence(x => x.GetFileContent(It.IsAny())) + .ReturnsAsync(directWriter.ToString()) + .ReturnsAsync(indirectWriter.ToString()); + + var user = GetAppUser("matched@mail.com"); + _mockUserRepository.Setup(x => x.FindUserByAdEmailAsync(It.IsAny())).ReturnsAsync((AppUser)null); + + _mockDeadActivityService.Setup(x => x.SaveDeadActivityRange(It.IsAny>())).ThrowsAsync(new Exception("Failure")); + + var request = new ImportIndirectVehicleSalesRequest() + { + FilePath = "path\\", + DirectDynamicFileName = "directFileName", + IndirectDynamicFileName = "indirectFileName" + }; + + Assert.ThrowsAsync(() => _importIndirectVehicleSalesService.HandleImportIndirectVehicleSales(request)); + + _mockDeadActivityService.Verify(x => x.SaveDeadActivityRange(It.IsAny>()), Times.Once); + _mockActivityService.Verify(x => x.SaveActivityRangeAsync(It.IsAny>()), Times.Never); + _mockUserRepository.Verify(x => x.FindUserByAdEmailAsync(It.IsAny()), Times.Once); + _mockFileOperationsApiService.Verify(x => x.GetFileContent(It.IsAny()), Times.Exactly(2)); + } + + [Test] + public void HandleImportIndirectVehicleSales_ActivityServiceFails_ExceptionThrown() + { + var directFileContent = new List() + { + new DirectVehicleSaleRecord() + { + ApplicantSSN = "555-55-5555", + ApplicationID = "321", + CreatedDate = DateTime.Now.AddDays(-30).ToString(), + ReferringEmployeeEmail = "test@mail.com" + } + }; + var directWriter = GetWriter(directFileContent); + + var indirectFileContent = new List() + { + new IndirectVehicleSaleRecord() + { + ApplicantSSN = "555-55-5555", + CreatedDate = DateTime.Now.AddDays(-10).ToString(), + ApplicationID = "123", + DisbursedDate = DateTime.Now.ToString() + } + }; + var indirectWriter = GetWriter(indirectFileContent); + + var fileResponse = new string[] { "filename" }; + _mockFileOperationsApiService.Setup(x => x.BuildDynamicFileList(It.IsAny())) + .ReturnsAsync(fileResponse); + + _mockFileOperationsApiService.SetupSequence(x => x.GetFileContent(It.IsAny())) + .ReturnsAsync(directWriter.ToString()) + .ReturnsAsync(indirectWriter.ToString()); + + var user = GetAppUser("matched@mail.com"); + _mockUserRepository.Setup(x => x.FindUserByAdEmailAsync(It.IsAny())).ReturnsAsync(user); + + _mockActivityService.Setup(x => x.SaveActivityRangeAsync(It.IsAny>())).ThrowsAsync(new Exception("Failure")); + + var request = new ImportIndirectVehicleSalesRequest() + { + FilePath = "path\\", + DirectDynamicFileName = "directFileName", + IndirectDynamicFileName = "indirectFileName" + }; + + Assert.ThrowsAsync(() => _importIndirectVehicleSalesService.HandleImportIndirectVehicleSales(request)); + + _mockDeadActivityService.Verify(x => x.SaveDeadActivityRange(It.IsAny>()), Times.Never); + _mockActivityService.Verify(x => x.SaveActivityRangeAsync(It.IsAny>()), Times.Once); + _mockUserRepository.Verify(x => x.FindUserByAdEmailAsync(It.IsAny()), Times.Once); + _mockFileOperationsApiService.Verify(x => x.GetFileContent(It.IsAny()), Times.Exactly(2)); + } + + [Test] + public async Task HandleImportIndirectVehicleSales_UserRepositoryFails_DeadActivitySaved() + { + var directFileContent = new List() + { + new DirectVehicleSaleRecord() + { + ApplicantSSN = "123-45-6789", + ApplicationID = "123", + CreatedDate = DateTime.Now.AddDays(-50).ToString(), + ReferringEmployeeEmail = "matched@mail.com" + } + }; + var directWriter = GetWriter(directFileContent); + + var indirectFileContent = new List() + { + new IndirectVehicleSaleRecord() + { + ApplicantSSN = "123-45-6789", + CreatedDate = DateTime.Now.AddDays(-40).ToString(), + ApplicationID = "123", + DisbursedDate = DateTime.Now.ToString() + }, + new IndirectVehicleSaleRecord() + { + ApplicantSSN = "555-55-5555", + CreatedDate = DateTime.Now.AddDays(-10).ToString(), + ApplicationID = "321", + DisbursedDate = DateTime.Now.ToString() + } + }; + var indirectWriter = GetWriter(indirectFileContent); + + var fileResponse = new string[] { "filename" }; + _mockFileOperationsApiService.Setup(x => x.BuildDynamicFileList(It.IsAny())).ReturnsAsync(fileResponse); + + _mockFileOperationsApiService.SetupSequence(x => x.GetFileContent(It.IsAny())) + .ReturnsAsync(directWriter.ToString()) + .ReturnsAsync(indirectWriter.ToString()); + + _mockUserRepository.Setup(x => x.FindUserByAdEmailAsync(It.IsAny())).ThrowsAsync(new Exception("Failure")); + + var request = new ImportIndirectVehicleSalesRequest() + { + FilePath = "path\\", + DirectDynamicFileName = "directFileName", + IndirectDynamicFileName = "indirectFileName" + }; + + await _importIndirectVehicleSalesService.HandleImportIndirectVehicleSales(request); + + _mockDeadActivityService.Verify(x => x.SaveDeadActivityRange(It.Is>(x => x.Count == 1)), Times.Once); + _mockActivityService.Verify(x => x.SaveActivityRangeAsync(It.IsAny>()), Times.Never); + _mockUserRepository.Verify(x => x.FindUserByAdEmailAsync(It.IsAny()), Times.Once); + _mockFileOperationsApiService.Verify(x => x.GetFileContent(It.IsAny()), Times.Exactly(2)); + } + + [Test] + public async Task HandleImportIndirectVehicleSales_NullEmployeeHomedepartment_DeadActivitySaved() + { + var directFileContent = new List() + { + new DirectVehicleSaleRecord() + { + ApplicantSSN = "123-45-6789", + ApplicationID = "123", + CreatedDate = DateTime.Now.AddDays(-50).ToString(), + ReferringEmployeeEmail = "matched@mail.com" + } + }; + var directWriter = GetWriter(directFileContent); + + var indirectFileContent = new List() + { + new IndirectVehicleSaleRecord() + { + ApplicantSSN = "123-45-6789", + CreatedDate = DateTime.Now.AddDays(-40).ToString(), + ApplicationID = "123", + DisbursedDate = DateTime.Now.ToString() + }, + new IndirectVehicleSaleRecord() + { + ApplicantSSN = "555-55-5555", + CreatedDate = DateTime.Now.AddDays(-10).ToString(), + ApplicationID = "321", + DisbursedDate = DateTime.Now.ToString() + } + }; + var indirectWriter = GetWriter(indirectFileContent); + + var fileResponse = new string[] { "filename" }; + _mockFileOperationsApiService.Setup(x => x.BuildDynamicFileList(It.IsAny())).ReturnsAsync(fileResponse); + + _mockFileOperationsApiService.SetupSequence(x => x.GetFileContent(It.IsAny())) + .ReturnsAsync(directWriter.ToString()) + .ReturnsAsync(indirectWriter.ToString()); + + var user = GetAppUser("matched@mail.com", false); + _mockUserRepository.Setup(x => x.FindUserByAdEmailAsync(It.IsAny())).ReturnsAsync(user); + + var request = new ImportIndirectVehicleSalesRequest() + { + FilePath = "path\\", + DirectDynamicFileName = "directFileName", + IndirectDynamicFileName = "indirectFileName" + }; + + await _importIndirectVehicleSalesService.HandleImportIndirectVehicleSales(request); + + _mockDeadActivityService.Verify(x => x.SaveDeadActivityRange(It.Is>(x => x.Count == 1)), Times.Once); + _mockActivityService.Verify(x => x.SaveActivityRangeAsync(It.IsAny>()), Times.Never); + _mockUserRepository.Verify(x => x.FindUserByAdEmailAsync(It.IsAny()), Times.Once); + _mockFileOperationsApiService.Verify(x => x.GetFileContent(It.IsAny()), Times.Exactly(2)); + } + + [Test] + public Task HandleImportIndirectVehicleSales_FileOperationsGetContentServiceFails_ExceptionThrown() + { + var fileResponse = new string[] { "filename" }; + _mockFileOperationsApiService.Setup(x => x.BuildDynamicFileList(It.IsAny())).ReturnsAsync(fileResponse); + + _mockFileOperationsApiService.Setup(x => x.GetFileContent(It.IsAny())).ThrowsAsync(new Exception("Failure")); + + var request = new ImportIndirectVehicleSalesRequest() + { + FilePath = "path\\", + DirectDynamicFileName = "directFileName", + IndirectDynamicFileName = "indirectFileName" + }; + + Assert.ThrowsAsync(() => _importIndirectVehicleSalesService.HandleImportIndirectVehicleSales(request)); + + _mockDeadActivityService.Verify(x => x.SaveDeadActivityRange(It.IsAny>()), Times.Never); + _mockActivityService.Verify(x => x.SaveActivityRangeAsync(It.IsAny>()), Times.Never); + _mockUserRepository.Verify(x => x.FindUserByAdEmailAsync(It.IsAny()), Times.Never); + _mockFileOperationsApiService.Verify(x => x.GetFileContent(It.IsAny()), Times.Once); + return Task.CompletedTask; + } + + [Test] + public Task HandleImportIndirectVehicleSales_FileOperationsBuildListServiceFails_ExceptionThrown() + { + var fileResponse = new string[] { "filename" }; + _mockFileOperationsApiService.Setup(x => x.BuildDynamicFileList(It.IsAny())).ThrowsAsync(new Exception("Failure")); + + var request = new ImportIndirectVehicleSalesRequest() + { + FilePath = "path\\", + DirectDynamicFileName = "directFileName", + IndirectDynamicFileName = "indirectFileName" + }; + + Assert.ThrowsAsync(() => _importIndirectVehicleSalesService.HandleImportIndirectVehicleSales(request)); + + _mockDeadActivityService.Verify(x => x.SaveDeadActivityRange(It.IsAny>()), Times.Never); + _mockActivityService.Verify(x => x.SaveActivityRangeAsync(It.IsAny>()), Times.Never); + _mockUserRepository.Verify(x => x.FindUserByAdEmailAsync(It.IsAny()), Times.Never); + _mockFileOperationsApiService.Verify(x => x.GetFileContent(It.IsAny()), Times.Never); + return Task.CompletedTask; + } + + private StringWriter GetWriter(List fileContent) + { + var writer = new StringWriter(); + + var config = new CsvConfiguration(CultureInfo.InvariantCulture) { Delimiter = "|", HasHeaderRecord = false }; + + using (var csv = new CsvWriter(writer, config)) + { + csv.WriteRecords(fileContent); + } + + return writer; + } + + private AppUser GetAppUser(string emailAddress, bool assignHomeDepartment = true) + { + var department = new Department + { + DepartmentName = "Dept1", + Id = 55, + State = DepartmentState.Active + }; + var user = new AppUser + { + AdEmail = emailAddress, + EmployeeId = "11771", + FullName = "John Doe", + Departments = new List() + }; + var assignment = new UserDepartment + { + Id = 99, + Department = department, + Employee = user, + Role = Role.BM, + IsHome = true, + StartDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1), + EndDate = null + }; + + if (assignHomeDepartment) + { + user.Departments.Add(assignment); + } + + return user; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/UserAssignmentServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/UserAssignmentServiceTest.cs new file mode 100644 index 0000000..8befaec --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/UserAssignmentServiceTest.cs @@ -0,0 +1,217 @@ +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Moq; +using NUnit.Framework; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; + +namespace Application.UnitTests.Services +{ + public class UserAssignmentServiceTest + { + private UserAssignmentService _service; + private Mock _mockUserAssignmentRepository; + private Mock _mockUserService; + private Mock _mockUnitOfWork; + + [SetUp] + public void Setup() + { + _mockUserAssignmentRepository = new Mock(); + _mockUserService = new Mock(); + _mockUnitOfWork = new Mock(); + _service = new UserAssignmentService(_mockUserAssignmentRepository.Object, _mockUserService.Object, + _mockUnitOfWork.Object); + } + + [Test] + public async Task GetAllUserAssignmentsAsync_OK() + { + var assignmentsFromRepo = new List + { + new UserAssignment { Id = 1 }, + new UserAssignment { Id = 2 } + }; + _mockUserAssignmentRepository.Setup(repo => repo.GetAllUserAssignmentsAsync()) + .ReturnsAsync(assignmentsFromRepo); + + var result = await _service.GetAllUserAssignmentsAsync(); + + Assert.NotNull(result); + Assert.True(result.Any(x => x.Id == 1)); + Assert.True(result.Any(x => x.Id == 2)); + } + + [Test] + public async Task SetRegionalManager_OK() + { + var assignment = new UserAssignmentRequest + { + EmployeeId = "123", + Key = "NORTH", + Type = AssignmentType.RegionalManager + }; + var validUser = new AppUser + { + Assignments = new List(), + Departments = new List() + { + new UserDepartment() + { + Role = Role.RegionalManager + } + } + }; + _mockUserService.Setup(service => service.GetUserByEmployeeIdAsync(assignment.EmployeeId)) + .ReturnsAsync(validUser); + + var result = await _service.SetRegionalManager(assignment); + Assert.NotNull(result); + + _mockUnitOfWork.Verify(mock => mock.CompleteAsync(), Times.Once); + } + + [Test] + public void SetRegionalManager_ThrowsNotFoundException() + { + var assignment = new UserAssignmentRequest + { + EmployeeId = "123" + }; + _mockUserService.Setup(service => service.GetUserByEmployeeIdAsync(assignment.EmployeeId)) + .ReturnsAsync((AppUser)null); + + var exception = Assert.ThrowsAsync(async() => + { + await _service.SetRegionalManager(assignment); + }); + + Assert.AreEqual("Employee Not Found", exception.Message); + } + + [Test] + public void SetRegionalManager_ThrowsBadUserAssignmentException() + { + var assignment = new UserAssignmentRequest + { + EmployeeId = "123" + }; + var nonRegionalManager = new AppUser { Role = Role.Teller }; + _mockUserService.Setup(service => service.GetUserByEmployeeIdAsync(assignment.EmployeeId)) + .ReturnsAsync(nonRegionalManager); + var exception = Assert.ThrowsAsync(async () => + { + await _service.SetRegionalManager(assignment); + }); + + Assert.AreEqual("Employee must have the Regional Manager role", exception.Message); + } + + [Test] + public async Task SetRegionalManager_SetsUserToAssignment() + { + var assignment = new UserAssignmentRequest + { + EmployeeId = "123", + Key = "NORTH", + Type = AssignmentType.RegionalManager + }; + var userFromRepo = new AppUser + { + EmployeeId = "123", + AdEmail = "test@landmarkcu.com", + PersonId = 456, + Assignments = new List(), + Departments = new List() + { + new UserDepartment() + { + Role = Role.RegionalManager + } + } + }; + var existingAssignment = new UserAssignment + { + Id = 1, + Key = "NORTH", + Type = AssignmentType.RegionalManager, + AppUser = new AppUser() + }; + _mockUserService.Setup(service => service.GetUserByEmployeeIdAsync(assignment.EmployeeId)) + .ReturnsAsync(userFromRepo); + _mockUserAssignmentRepository.Setup(service => service.FindExistingAssignmentAsync(assignment.Type.Value, + assignment.Key)) + .ReturnsAsync(existingAssignment); + + var result = await _service.SetRegionalManager(assignment); + Assert.NotNull(result); + + Assert.AreEqual(existingAssignment.AppUser.EmployeeId, userFromRepo.EmployeeId); + Assert.AreEqual(existingAssignment.AppUser.AdEmail, userFromRepo.AdEmail); + Assert.AreEqual(existingAssignment.AppUser.PersonId, userFromRepo.PersonId); + } + + [Test] + public async Task SetRegionalManager_AddsAssignmentToUser() + { + var assignment = new UserAssignmentRequest + { + EmployeeId = "123", + Key = "NORTH", + Type = AssignmentType.RegionalManager + }; + var userFromRepo = new AppUser + { + Assignments = new List(), + Departments = new List() + { + new UserDepartment() + { + Role = Role.RegionalManager + } + } + }; + _mockUserService.Setup(service => service.GetUserByEmployeeIdAsync(assignment.EmployeeId)) + .ReturnsAsync(userFromRepo); + + var result = await _service.SetRegionalManager(assignment); + Assert.NotNull(result); + + Assert.AreEqual(1, userFromRepo.Assignments.Count()); + Assert.AreEqual("NORTH", userFromRepo.Assignments.First().Key); + Assert.AreEqual(AssignmentType.RegionalManager, userFromRepo.Assignments.First().Type); + Assert.AreEqual(Role.RegionalManager, userFromRepo.Assignments.First().AppUser.Departments.First().Role); + } + + [Test] + public async Task GetUserAssignment_ReturnsAssignment() + { + var type = AssignmentType.RegionalManager; + var key = "NORTH"; + var assignment = new UserAssignment { + Id = 1, + Key = key, + Type = type, + AppUser = new AppUser { EmployeeId = "C0000" } + }; + _mockUserAssignmentRepository.Setup(mock => mock.FindExistingAssignmentAsync(type, key)) + .ReturnsAsync(assignment); + + var result = await _service.GetUserAssignment(type, key); + + Assert.AreEqual(AssignmentType.RegionalManager, result.Type); + Assert.AreEqual("NORTH", result.Key); + Assert.AreEqual(AssignmentType.RegionalManager, result.Type); + Assert.AreEqual("C0000", result.AppUser.EmployeeId); + + _mockUserAssignmentRepository.Verify(mock => mock.FindExistingAssignmentAsync(type, key), + Times.Once); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/UserServiceTest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/UserServiceTest.cs new file mode 100644 index 0000000..8908c75 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi.UnitTests/Services/UserServiceTest.cs @@ -0,0 +1,1582 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; +using LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Results; +using LCU.API.Internal.Operations.IncentiveApi.Extensions; +using Moq; +using NUnit.Framework; + +namespace Application.UnitTests.Services +{ + public class UserServiceTest + { + private IApplicationDbIntegrityService _applicationDbIntegrityService; + private IDepartmentRepository _departmentRepository; + private IRoleMappingRepository _roleMappingRepository; + private IUnitOfWork _unitOfWork; + private IUserDepartmentHistoryRepository _userDepartmentHistoryRepository; + private IUserPermissionHistoryRepository _userPermissionHistoryRepository; + private IUserRepository _userRepository; + private UserService _userService; + + [SetUp] + public void Setup() + { + _applicationDbIntegrityService = Mock.Of(); + _departmentRepository = Mock.Of(); + _roleMappingRepository = Mock.Of(); + _unitOfWork = Mock.Of(); + _userDepartmentHistoryRepository = Mock.Of(); + _userPermissionHistoryRepository = Mock.Of(); + _userRepository = Mock.Of(); + + var roleMappings = new List + { + new() { ApplicationRole = Role.Teller, BusinessRole = "ACH REPRESENTATIVE" } + }; + + Mock.Get(_roleMappingRepository) + .Setup(s => s.FindMatchingRoleFromBusinessRole(It.IsAny())) + .Returns(roleMappings.FirstOrDefault()?.ApplicationRole); + + _userService = new UserService( + _applicationDbIntegrityService, + _departmentRepository, + _roleMappingRepository, + _unitOfWork, + _userDepartmentHistoryRepository, + _userPermissionHistoryRepository, + _userRepository + ); + } + + [Test] + public async Task GetAllUsers_NoQuery() + { + Mock.Get(_userRepository) + .Setup(s => s.GetAllUsersAsync()) + .ReturnsAsync(new List + { + new AppUser + { + AdEmail = "test1@test.com" + } + }); + + Mock.Get(_userRepository) + .Setup(s => s.SearchUsersAsync(It.IsAny())) + .ReturnsAsync(new List + { + new AppUser + { + AdEmail = "test2@test.com" + } + }); + + var results = await _userService.GetAllUsersAsync(new UserSearchRequest()); + Assert.NotNull(results); + Assert.AreEqual(1, results.Count); + Assert.AreEqual("test1@test.com", results.First().AdEmail); + } + + [Test] + public async Task FindManager_OK() + { + var manager = new AppUser + { + EmployeeId = "11771" + }; + var employee = new AppUser + { + EmployeeId = "C00002", + ManagerKey = manager.EmployeeId + }; + + Mock.Get(_userRepository) + .Setup(s => s.FindUserByEmployeeIdAsync("11771")) + .ReturnsAsync(manager); + + var results = await _userService.FindManager(employee); + Assert.NotNull(results); + Assert.AreEqual(manager, results); + } + + [Test] + public async Task GetAllUsers_query() + { + Mock.Get(_userRepository) + .Setup(s => s.GetAllUsersAsync()) + .ReturnsAsync(new List + { + new AppUser + { + AdEmail = "test1@test.com" + } + }); + + Mock.Get(_userRepository) + .Setup(s => s.SearchUsersAsync(It.IsAny())) + .ReturnsAsync(new List + { + new AppUser + { + AdEmail = "test2@test.com" + } + }); + + var searchRequest = new UserSearchRequest { Query = "test2" }; + var results = await _userService.GetAllUsersAsync(searchRequest); + Assert.NotNull(results); + Assert.AreEqual(1, results.Count); + Assert.AreEqual("test2@test.com", results.First().AdEmail); + } + + [Test] + public async Task GetUsersByEmployeeId() + { + Mock.Get(_userRepository) + .Setup(s => s.FindUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync( + new AppUser + { + AdEmail = "test1@test.com" + } + ); + Mock.Get(_userRepository) + .Setup(s => s.FindUserByAdEmailAsync(It.IsAny())) + .ReturnsAsync( + new AppUser + { + AdEmail = "test1@test.com" + } + ); + + var results = await _userService.GetUserByEmployeeIdAsync("11771"); + Assert.NotNull(results); + Mock.Get(_userRepository).Verify(s => s.FindUserByEmployeeIdAsync(It.IsAny()), Times.Once); + Mock.Get(_userRepository).Verify(s => s.FindUserByAdEmailAsync(It.IsAny()), Times.Never); + } + + [Test] + public async Task GetUsersByAdEmail() + { + Mock.Get(_userRepository) + .Setup(s => s.FindUserByAdEmailAsync(It.IsAny())) + .ReturnsAsync( + new AppUser + { + AdEmail = "test1@test.com" + } + ); + + var results = await _userService.GetUserByAdEmailAsync("test1@test.com"); + Assert.NotNull(results); + Mock.Get(_userRepository).Verify(s => s.FindUserByAdEmailAsync(It.IsAny()), Times.Once); + Mock.Get(_userRepository).Verify(s => s.FindUserByEmployeeIdAsync(It.IsAny()), Times.Never); + } + + [Test] + public async Task FindUser() + { + Mock.Get(_userRepository) + .Setup(s => s.FindUserAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync( + new AppUser + { + AdEmail = "test1@test.com" + } + ); + + var results = await _userService.FindUserAsync("11771", "test1@test.com"); + Assert.NotNull(results); + Mock.Get(_userRepository).Verify(s => s.FindUserAsync(It.IsAny(), It.IsAny()), Times.Once); + Mock.Get(_userRepository).Verify(s => s.FindUserByAdEmailAsync(It.IsAny()), Times.Never); + Mock.Get(_userRepository).Verify(s => s.FindUserByEmployeeIdAsync(It.IsAny()), Times.Never); + } + + [Test] + public async Task SyncDayForce_NewUser() + { + var rawData = new Dictionary + { + ["EmploymentStatus_ShortName"] = "Active", + ["Employee_DisplayName"] = "Doe, Test", + ["EmployeeEmploymentStatus_EmployeeNumber"] = "11771", + ["IdLibrary_LoginName"] = "testdoe@landmarkcu.com", + ["OrgUnit_ShortName"] = "WAUWATOSA - BRANCH", + ["Job_ShortName"] = "TELLER II", + ["OrgUnit_LedgerCode"] = "101", + ["Employee_HireDate"] = "2019-09-23T00:00:00.0000000" + }; + + var toProcess = new ActiveEmployeeResult(rawData); + + Mock.Get(_departmentRepository) + .Setup(s => s.FindOrCreateDepartment(toProcess)) + .ReturnsAsync( + new Department + { + Id = 1, + State = DepartmentState.Active, + DepartmentName = "WAUWATOSA", + BusinessLineCode = BusinessLine.Operations.Code + }); + Mock.Get(_userRepository).Setup(s => s.FindUserByEmployeeIdAsync("11771")).ReturnsAsync((AppUser)null); + Mock.Get(_userRepository).Setup(s => s.SaveUserAsync(It.IsAny())).ReturnsAsync((AppUser x) => x); + Mock.Get(_unitOfWork).Setup(s => s.CompleteAsync()).Verifiable(); + + var newUser = await _userService.SyncDayForceEmployee(toProcess); + Assert.NotNull(newUser); + Assert.AreEqual(rawData["IdLibrary_LoginName"], newUser.AdEmail); + Assert.NotNull(newUser.Departments); + Assert.IsNotEmpty(newUser.Departments); + + var userDept = newUser.Departments.First(); + Assert.NotNull(userDept.StartDate); + Assert.AreEqual(9, userDept.StartDate.Month); + Assert.AreEqual(1, userDept.StartDate.Day); + Assert.NotNull(userDept.Role); + Assert.AreEqual(Role.Teller, userDept.Role); + + Mock.Get(_userRepository).Verify(s => s.FindUserByEmployeeIdAsync("11771"), Times.Once); + Mock.Get(_departmentRepository).Verify(s => s.FindOrCreateDepartment(toProcess), Times.Once); + Mock.Get(_unitOfWork).Verify(s => s.CompleteAsync(), Times.Once); + } + + [Test] + public async Task SyncDayForce_NewUser_BadHireDate() + { + var rawData = new Dictionary + { + ["EmploymentStatus_ShortName"] = "Active", + ["Employee_DisplayName"] = "Doe, Test", + ["EmployeeEmploymentStatus_EmployeeNumber"] = "11771", + ["IdLibrary_LoginName"] = "testdoe@landmarkcu.com", + ["OrgUnit_ShortName"] = "WAUWATOSA - BRANCH", + ["Job_ShortName"] = "TELLER II", + ["OrgUnit_LedgerCode"] = "101", + ["Employee_HireDate"] = DateTime.MinValue.ToLongTimeString() + }; + + var toProcess = new ActiveEmployeeResult(rawData); + + Mock.Get(_departmentRepository) + .Setup(s => s.FindOrCreateDepartment(toProcess)) + .ReturnsAsync( + new Department + { + Id = 1, + State = DepartmentState.Active, + DepartmentName = "WAUWATOSA", + BusinessLineCode = BusinessLine.Operations.Code + }); + Mock.Get(_userRepository).Setup(s => s.FindUserByEmployeeIdAsync("11771")).ReturnsAsync((AppUser)null); + Mock.Get(_userRepository).Setup(s => s.SaveUserAsync(It.IsAny())).ReturnsAsync((AppUser x) => x); + Mock.Get(_unitOfWork).Setup(s => s.CompleteAsync()).Verifiable(); + + var newUser = await _userService.SyncDayForceEmployee(toProcess); + Assert.NotNull(newUser); + Assert.AreEqual(rawData["IdLibrary_LoginName"], newUser.AdEmail); + Assert.NotNull(newUser.Departments); + Assert.IsNotEmpty(newUser.Departments); + + var userDept = newUser.Departments.First(); + Assert.NotNull(userDept.StartDate); + Assert.AreEqual(DateTime.Today.Year, userDept.StartDate.Year); + Assert.AreEqual(DateTime.Today.Month, userDept.StartDate.Month); + Assert.AreEqual(1, userDept.StartDate.Day); + Assert.NotNull(userDept.Role); + Assert.AreEqual(Role.Teller, userDept.Role); + + Mock.Get(_userRepository).Verify(s => s.FindUserByEmployeeIdAsync("11771"), Times.Once); + Mock.Get(_departmentRepository).Verify(s => s.FindOrCreateDepartment(toProcess), Times.Once); + Mock.Get(_unitOfWork).Verify(s => s.CompleteAsync(), Times.Once); + } + + [Test] + public async Task SyncDayForce_ExistingUser_SameDept_nochanges() + { + var rawData = new Dictionary + { + ["EmploymentStatus_ShortName"] = "Active", + ["Employee_DisplayName"] = "Doe, Test", + ["EmployeeEmploymentStatus_EmployeeNumber"] = "11771", + ["IdLibrary_LoginName"] = "testdoe@landmarkcu.com", + ["OrgUnit_ShortName"] = "WAUWATOSA - BRANCH", + ["Job_ShortName"] = "TELLER II", + ["OrgUnit_LedgerCode"] = "101", + ["Employee_HireDate"] = "2024-10-05T00:00:00.0000000" + }; + + var toProcess = new ActiveEmployeeResult(rawData); + + var department = new Department + { + Id = 1, + State = DepartmentState.Active, + DepartmentName = "WAUWATOSA", + BusinessLineCode = BusinessLine.Operations.Code + }; + var user = new AppUser + { + AdEmail = "test@test.com", + BaseRole = "fun", + EmployeeId = "11771", + HomeDepartment = "home", + Departments = new List(), + FullName = "Doe, Test" + }; + var userDepartment = new UserDepartment + { + Id = 11, + Department = department, + Employee = user, + StartDate = DateTime.Parse("2024-11-01T00:00:00.0000000"), + IsHome = true, + Note = "For Testing", + Role = Role.Teller + }; + user.Departments.Add(userDepartment); + + Mock.Get(_departmentRepository).Setup(s => s.FindOrCreateDepartment(toProcess)).ReturnsAsync(department); + Mock.Get(_userRepository).Setup(s => s.FindUserByEmployeeIdAsync("11771")).ReturnsAsync(user); + Mock.Get(_userRepository).Setup(s => s.SaveUserAsync(It.IsAny())).ReturnsAsync((AppUser x) => x); + Mock.Get(_unitOfWork).Setup(s => s.CompleteAsync()).Verifiable(); + + var existingUser = await _userService.SyncDayForceEmployee(toProcess); + Assert.NotNull(existingUser); + Assert.NotNull(existingUser.Departments); + Assert.IsNotEmpty(existingUser.Departments); + Assert.AreEqual(1, existingUser.Departments.Count); + + userDepartment = existingUser.Departments.FirstOrDefault(x => x.IsHome && x.EndDate == null); + Assert.NotNull(userDepartment.Role); + Assert.AreEqual(Role.Teller, userDepartment.Role); + Assert.NotNull(userDepartment.StartDate); + Assert.AreEqual(2024, userDepartment.StartDate.Year); + Assert.AreEqual(11, userDepartment.StartDate.Month); + Assert.Null(userDepartment.EndDate); + + Assert.AreEqual(userDepartment.Department.Id, department.Id); + + Mock.Get(_userRepository).Verify(s => s.FindUserByEmployeeIdAsync("11771"), Times.Once); + Mock.Get(_departmentRepository).Verify(s => s.FindOrCreateDepartment(toProcess), Times.Once); + Mock.Get(_unitOfWork).Verify(s => s.CompleteAsync(), Times.Once); + } + + [Test] + public void DotNet_Quick_Date_Test() + { + // 1. Start in december. + var date1 = DateTime.Parse("2025-12-12T00:00:00.0000000"); + Assert.AreEqual(12, date1.Month); + + // 2. It will throw if month = 13, some frameworks figure this out... C#... no. + Assert.Throws(() => + { + var throwAway = new DateTime(date1.Year, date1.Month + 1, 1); + }); + + // 3. Move to using AddMonths to try and get the first of the year. + var date2 = new DateTime(date1.Year, date1.Month, 1).AddMonths(1); + Assert.AreEqual(1, date2.Month); + Assert.AreEqual(1, date2.Day); + Assert.AreEqual(date1.Year + 1, date2.Year); + + // 4. Subtracting a day should give 12/31 of the original year + var endDate = date2.Subtract(TimeSpan.FromDays(1)); + Assert.AreEqual(12, endDate.Month); + Assert.AreEqual(date1.Year, endDate.Year); + Assert.AreEqual(31, endDate.Day); + } + + [Test] + public async Task SyncDayForce_ExistingUser_ChangeDept() + { + var rawData = new Dictionary + { + ["EmploymentStatus_ShortName"] = "Active", + ["Employee_DisplayName"] = "Doe, Test", + ["EmployeeEmploymentStatus_EmployeeNumber"] = "11771", + ["IdLibrary_LoginName"] = "testdoe@landmarkcu.com", + ["OrgUnit_ShortName"] = "WAUWATOSA - BRANCH", + ["Job_ShortName"] = "BRANCH MANAGER", + ["OrgUnit_LedgerCode"] = "101", + ["Employee_HireDate"] = "2025-10-05T00:00:00.0000000" + }; + + var toProcess = new ActiveEmployeeResult(rawData); + + var department = new Department + { + Id = 1, + State = DepartmentState.Active, + DepartmentName = "WAUWATESTA", + BusinessLineCode = BusinessLine.BranchOperations.Code + }; + var departmentNew = new Department + { + Id = 2, + State = DepartmentState.Active, + DepartmentName = "WAUWATOSA", + BusinessLineCode = BusinessLine.BranchOperations.Code + }; + var user = new AppUser + { + AdEmail = "test@test.com", + BaseRole = "fun", + EmployeeId = "11771", + HomeDepartment = "home", + Departments = new List(), + FullName = "Doe, Test" + }; + var userDepartment = new UserDepartment + { + Id = 11, + Department = department, + Employee = user, + StartDate = DateTime.Parse("2024-10-05T00:00:00.0000000"), + IsHome = true, + Role = Role.Teller, + Note = "Auto Generated Home Assignment", + }; + user.Departments.Add(userDepartment); + + Mock.Get(_roleMappingRepository).Setup(s => s.FindMatchingRoleFromBusinessRole(It.IsAny())).Returns(Role.BM); + Mock.Get(_departmentRepository).Setup(s => s.FindOrCreateDepartment(toProcess)).ReturnsAsync(departmentNew); + Mock.Get(_userRepository).Setup(s => s.FindUserByEmployeeIdAsync("11771")).ReturnsAsync(user); + Mock.Get(_userRepository).Setup(s => s.SaveUserAsync(It.IsAny())).ReturnsAsync((AppUser x) => x); + Mock.Get(_userRepository).Setup(s => s.SaveUserAsync(It.IsAny())).ReturnsAsync((AppUser x) => x); + Mock.Get(_unitOfWork).Setup(s => s.CompleteAsync()).Verifiable(); + + var existingUser = await _userService.SyncDayForceEmployee(toProcess); + Assert.NotNull(existingUser); + Assert.NotNull(existingUser.Departments); + Assert.IsNotEmpty(existingUser.Departments); + Assert.AreEqual(2, existingUser.Departments.Count); + + var newUserDepartment = existingUser.Departments.FirstOrDefault(x => x.IsHome && x.EndDate == null); + Assert.NotNull(newUserDepartment.StartDate); + Assert.AreEqual(DateTime.Today.Year, newUserDepartment.StartDate.Year); + Assert.AreEqual(DateTime.Today.Month, newUserDepartment.StartDate.Month); + Assert.Null(newUserDepartment.EndDate); + Assert.True(newUserDepartment.IsHome); + Assert.NotNull(newUserDepartment.Role); + Assert.AreEqual(Role.BM, newUserDepartment.Role); + + var oldUserDepartment = existingUser.Departments.FirstOrDefault(x => x.Department.Id == 1); + Assert.NotNull(oldUserDepartment.StartDate); + Assert.NotNull(oldUserDepartment.EndDate); + Assert.False(oldUserDepartment.IsHome); + Assert.NotNull(oldUserDepartment.Role); + Assert.IsEmpty(oldUserDepartment.Note); + Assert.AreEqual(Role.Teller, oldUserDepartment.Role); + + Assert.AreEqual(newUserDepartment.Department.Id, departmentNew.Id); + Assert.AreNotEqual(department.Id, departmentNew.Id); + + Mock.Get(_userRepository).Verify(s => s.SaveUserAsync(It.IsAny()), Times.Never); + Mock.Get(_userRepository).Verify(s => s.FindUserByEmployeeIdAsync("11771"), Times.Once); + Mock.Get(_departmentRepository).Verify(s => s.FindOrCreateDepartment(toProcess), Times.Once); + Mock.Get(_unitOfWork).Verify(s => s.CompleteAsync(), Times.Once); + } + + [Test] + public async Task SyncDayForce_ExistingUser_ChangeDept_Back() + { + var monthBegin = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); + var monthEnd = monthBegin.EndOfMonth(); + var rawData = new Dictionary + { + ["EmploymentStatus_ShortName"] = "Active", + ["Employee_DisplayName"] = "Doe, Test", + ["EmployeeEmploymentStatus_EmployeeNumber"] = "11771", + ["IdLibrary_LoginName"] = "testdoe@landmarkcu.com", + ["OrgUnit_ShortName"] = "WAUWATOSA - BRANCH", + ["Job_ShortName"] = "CLIENT SRVC ASST-LIC", + ["OrgUnit_LedgerCode"] = "101", + ["Employee_HireDate"] = DateTime.Today.ToLongTimeString() + }; + + var toProcess = new ActiveEmployeeResult(rawData); + + var user = new AppUser + { + AdEmail = "test@test.com", + BaseRole = "fun", + EmployeeId = "11771", + HomeDepartment = "home", + Departments = new List(), + FullName = "Doe, Test" + }; + var departmentA = new Department + { + Id = 10, + State = DepartmentState.Active, + DepartmentName = "WAUWATESTA", + BusinessLineCode = BusinessLine.BranchOperations.Code + }; + var departmentB = new Department + { + Id = 20, + State = DepartmentState.Active, + DepartmentName = "WAUWATOSA", + BusinessLineCode = BusinessLine.BranchOperations.Code + }; + var currentHomeUserDept = new UserDepartment + { + Id = 11, + Department = departmentA, + Employee = user, + StartDate = monthBegin, + EndDate = null, + IsHome = true, + Note = "For Testing", + Role = Role.Teller + }; + var userDepartmentB = new UserDepartment + { + Id = 21, + Department = departmentB, + Employee = user, + StartDate = monthBegin, + EndDate = monthEnd, + IsHome = true, + Note = "For Testing", + Role = Role.BM + }; + user.Departments.Add(currentHomeUserDept); + user.Departments.Add(userDepartmentB); + + Mock.Get(_departmentRepository).Setup(s => s.FindOrCreateDepartment(toProcess)).ReturnsAsync(departmentB); + Mock.Get(_userRepository).Setup(s => s.FindUserByEmployeeIdAsync("11771")).ReturnsAsync(user); + Mock.Get(_userRepository).Setup(s => s.SaveUserAsync(It.IsAny())).ReturnsAsync((AppUser x) => x); + Mock.Get(_unitOfWork).Setup(s => s.CompleteAsync()).Verifiable(); + Mock.Get(_roleMappingRepository).Setup(s => s.FindMatchingRoleFromBusinessRole(It.IsAny())).Returns(Role.LIC); + + var existingUser = await _userService.SyncDayForceEmployee(toProcess); + Assert.NotNull(existingUser); + Assert.NotNull(existingUser.Departments); + Assert.IsNotEmpty(existingUser.Departments); + Assert.AreEqual(2, existingUser.Departments.Count); + + var newUserDepartment = existingUser.Departments.FirstOrDefault(x => x.IsHome && x.EndDate == null); + Assert.NotNull(newUserDepartment.StartDate); + Assert.Null(newUserDepartment.EndDate); + Assert.NotNull(currentHomeUserDept.EndDate); + Assert.True(newUserDepartment.IsHome); + Assert.NotNull(newUserDepartment.Role); + Assert.AreEqual(Role.LIC, newUserDepartment.Role); + + var oldUserDepartment = existingUser.Departments.FirstOrDefault(x => x.Department.Id == 10); + Assert.NotNull(oldUserDepartment.StartDate); + Assert.NotNull(oldUserDepartment.EndDate); + Assert.False(oldUserDepartment.IsHome); + Assert.NotNull(oldUserDepartment.Role); + Assert.AreEqual(Role.Teller, oldUserDepartment.Role); + + Assert.AreEqual(newUserDepartment.Department.Id, departmentB.Id); + + Mock.Get(_userRepository).Verify(s => s.FindUserByEmployeeIdAsync("11771"), Times.Once); + Mock.Get(_departmentRepository).Verify(s => s.FindOrCreateDepartment(toProcess), Times.Once); + Mock.Get(_unitOfWork).Verify(s => s.CompleteAsync(), Times.Once); + } + + [Test] + public async Task SyncDayForce_ExistingUser_ChangeFloat_To_Home() + { + var monthBegin = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); + var rawData = new Dictionary + { + ["EmploymentStatus_ShortName"] = "Active", + ["Employee_DisplayName"] = "Doe, Test", + ["EmployeeEmploymentStatus_EmployeeNumber"] = "11771", + ["IdLibrary_LoginName"] = "testdoe@landmarkcu.com", + ["OrgUnit_ShortName"] = "WAUWATOSA - BRANCH", + ["Job_ShortName"] = "TELLER II", + ["OrgUnit_LedgerCode"] = "101", + ["Employee_HireDate"] = DateTime.Today.ToLongTimeString() + }; + + var toProcess = new ActiveEmployeeResult(rawData); + + var user = new AppUser + { + Role = Role.Teller, + AdEmail = "test@test.com", + BaseRole = "fun", + EmployeeId = "11771", + HomeDepartment = "home", + Departments = new List(), + FullName = "Doe, Test" + }; + var departmentA = new Department + { + Id = 10, + State = DepartmentState.Active, + DepartmentName = "WAUWATESTA", + BusinessLineCode = BusinessLine.BranchOperations.Code + }; + var departmentB = new Department + { + Id = 20, + State = DepartmentState.Active, + DepartmentName = "WAUWATOSA", + BusinessLineCode = BusinessLine.BranchOperations.Code + }; + var userDepartmentA = new UserDepartment + { + Id = 11, + Department = departmentA, + Employee = user, + StartDate = monthBegin, + EndDate = null, + IsHome = true, + Note = "For Testing" + }; + var userDepartmentB = new UserDepartment + { + Id = 21, + Department = departmentB, + Employee = user, + StartDate = monthBegin, + EndDate = null, + IsHome = false, + Note = "For Testing" + }; + user.Departments.Add(userDepartmentA); + user.Departments.Add(userDepartmentB); + + Mock.Get(_departmentRepository).Setup(s => s.FindOrCreateDepartment(toProcess)).ReturnsAsync(departmentB); + Mock.Get(_userRepository).Setup(s => s.FindUserByEmployeeIdAsync("11771")).ReturnsAsync(user); + Mock.Get(_userRepository).Setup(s => s.SaveUserAsync(It.IsAny())).ReturnsAsync((AppUser x) => x); + Mock.Get(_unitOfWork).Setup(s => s.CompleteAsync()).Verifiable(); + + var existingUser = await _userService.SyncDayForceEmployee(toProcess); + Assert.NotNull(existingUser); + Assert.AreEqual(Role.Teller, existingUser.Role); + Assert.NotNull(existingUser.Departments); + Assert.IsNotEmpty(existingUser.Departments); + Assert.AreEqual(2, existingUser.Departments.Count); + + var newUserDepartment = existingUser.Departments.FirstOrDefault(x => x.IsHome && x.EndDate == null); + Assert.NotNull(newUserDepartment.StartDate); + Assert.Null(newUserDepartment.EndDate); + Assert.IsTrue(newUserDepartment.IsHome); + Assert.NotNull(userDepartmentA.EndDate); + + Assert.AreEqual(newUserDepartment.Department.Id, departmentB.Id); + + Mock.Get(_userRepository).Verify(s => s.FindUserByEmployeeIdAsync("11771"), Times.Once); + Mock.Get(_departmentRepository).Verify(s => s.FindOrCreateDepartment(toProcess), Times.Once); + Mock.Get(_unitOfWork).Verify(s => s.CompleteAsync(), Times.Once); + } + + [Test] + public async Task AssignDepartment_OK() + { + var department = new Department + { + Id = 1, + State = DepartmentState.Active, + DepartmentName = "Test Dept", + BusinessLineCode = BusinessLine.BranchOperations.Code + }; + + var assignment = new DepartmentAssignmentRequest + { + Note = "Test Assignment", + DepartmentId = new int[1], + StartDate = DateTime.Today, + SlidingPayout = 100, + Role = Role.Teller.Name + }; + + var employee = new AppUser + { + Role = Role.Teller, + AdEmail = "test@test.com", + BaseRole = "fun", + EmployeeId = "11771", + HomeDepartment = "home" + }; + + Mock.Get(_departmentRepository) + .Setup(s => s.FindDepartmentByIdAsync(It.IsAny())) + .ReturnsAsync(department); + Mock.Get(_userRepository) + .Setup(s => s.FindUserByEmployeeIdAsync(employee.EmployeeId)) + .ReturnsAsync( + employee); + Mock.Get(_userRepository).Setup(s => s.SaveUserAsync(It.IsAny())).ReturnsAsync((AppUser x) => x); + Mock.Get(_unitOfWork).Setup(s => s.CompleteAsync()).Verifiable(); + + var newUserDepartment = await _userService.AssignDepartments(employee.EmployeeId, assignment); + Assert.NotNull(newUserDepartment); + + Mock.Get(_userRepository).Verify(s => s.FindUserByEmployeeIdAsync("11771"), Times.Once); + Mock.Get(_unitOfWork).Verify(s => s.CompleteAsync(), Times.Once); + } + + [Test] + public void AssignDepartment_Bad() + { + var department = new Department + { + Id = 555, + State = DepartmentState.Active, + DepartmentName = "Test Dept", + BusinessLineCode = BusinessLine.Operations.Code + }; + + var assignment = new DepartmentAssignmentRequest + { + Note = "Test Assignment", + DepartmentId = new int[555], + StartDate = DateTime.Today + }; + + var employee = new AppUser + { + Role = Role.Teller, + AdEmail = "test@test.com", + BaseRole = "fun", + EmployeeId = "11771", + HomeDepartment = "home" + }; + + Mock.Get(_departmentRepository) + .Setup(s => s.FindDepartmentByIdAsync(department.Id.Value)) + .ThrowsAsync(new NotFoundException("Oops")); + Mock.Get(_userRepository) + .Setup(s => s.FindUserByEmployeeIdAsync(employee.EmployeeId)) + .ReturnsAsync( + employee); + Mock.Get(_userRepository).Setup(s => s.SaveUserAsync(It.IsAny())).ReturnsAsync((AppUser x) => x); + Mock.Get(_unitOfWork).Setup(s => s.CompleteAsync()).Verifiable(); + + Assert.ThrowsAsync(async () => { await _userService.AssignDepartments(employee.EmployeeId, assignment); }); + + Mock.Get(_userRepository).Verify(s => s.FindUserByEmployeeIdAsync("11771"), Times.Once); + Mock.Get(_unitOfWork).Verify(s => s.CompleteAsync(), Times.Never); + } + + [Test] + public void AssignDepartment_Bad_Role() + { + var department = new Department + { + Id = 555, + State = DepartmentState.Active, + DepartmentName = "Test Dept", + BusinessLineCode = BusinessLine.Operations.Code + }; + + var assignment = new DepartmentAssignmentRequest + { + Note = "Test Assignment", + DepartmentId = new int[555], + StartDate = DateTime.Today, + SlidingPayout = 200 // oh yeah, double payout! + }; + + var employee = new AppUser + { + Role = Role.Teller, + AdEmail = "test@test.com", + BaseRole = "fun", + EmployeeId = "11771", + HomeDepartment = "home" + }; + + Mock.Get(_departmentRepository) + .Setup(s => s.FindDepartmentByIdAsync(department.Id.Value)) + .ReturnsAsync(department); + Mock.Get(_userRepository) + .Setup(s => s.FindUserByEmployeeIdAsync(employee.EmployeeId)) + .ReturnsAsync( + employee); + Mock.Get(_userRepository).Setup(s => s.SaveUserAsync(It.IsAny())).ReturnsAsync((AppUser x) => x); + Mock.Get(_unitOfWork).Setup(s => s.CompleteAsync()).Verifiable(); + + Assert.ThrowsAsync(async () => { await _userService.AssignDepartments(employee.EmployeeId, assignment); }); + + assignment.Role = Role.Teller.Name; + + // Should still throw... no payout window + Assert.ThrowsAsync(async () => { await _userService.AssignDepartments(employee.EmployeeId, assignment); }); + + Mock.Get(_userRepository).Verify(s => s.FindUserByEmployeeIdAsync("11771"), Times.Exactly(2)); + Mock.Get(_unitOfWork).Verify(s => s.CompleteAsync(), Times.Never); + } + + [Test] + public async Task UpdatePermissions_OK() + { + Mock.Get(_userRepository) + .Setup(x => x.FindUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync( + new AppUser + { + //Perms: ElligableForReferrals, ViewDashboard + Departments = new List + { + new UserDepartment + { + Role = Role.Teller, + StartDate = DateTime.Today.Subtract(TimeSpan.FromDays(30)) + } + }, + AdditionalPermissions = new List + { + new UserPermission + { + Permission = Permission.HaveSalesGoals + } + } + }); + + var res = await _userService.UpdatePermissions("123", new AdditionalPermissionRequest + { + //Already has Eligible for referrals based on role, should not add + AddPermCodes = new List { "ELIGIBLE_FOR_REFERRALS", "MANAGE_DEPARTMENT_GOALS" }, + RemovePermCodes = new List { "HAVE_SALES_GOALS", "UNKOWN" } + }); + + Assert.AreEqual(1, res.Count); + Assert.True(res.Contains(Permission.ManageDepartmentGoals)); + } + + [Test] + public async Task UpdatePermissions_BadCodes_OK() + { + Mock.Get(_userRepository) + .Setup(x => x.FindUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync( + new AppUser + { + //Perms: ElligableForReferrals, ViewDashboard + Departments = new List + { + new UserDepartment + { + Role = Role.Teller, + StartDate = DateTime.Today.Subtract(TimeSpan.FromDays(30)) + } + }, + AdditionalPermissions = new List + { + new UserPermission + { + Permission = Permission.HaveSalesGoals + } + } + }); + + var res = await _userService.UpdatePermissions("123", new AdditionalPermissionRequest + { + //Already has Eligible for referrals based on role, should not add + AddPermCodes = new List { "ELIGIBLE_FOR_REFERRALS", "MANAGE_DEPARTMENT_GOALS", "NOTREAL", "FAKE", "DISREGARD" }, + RemovePermCodes = new List { "HAVE_SALES_GOALS", "UNKOWN", "HELLO", "FUTURE", "TESTER" } + }); + + Assert.AreEqual(1, res.Count); + Assert.True(res.Contains(Permission.ManageDepartmentGoals)); + } + + [Test] + public async Task UpdatePermissions_Add_OK() + { + Mock.Get(_userRepository) + .Setup(x => x.FindUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync( + new AppUser + { + //Perms: EligibleForReferrals, ViewDashboard + Departments = new List + { + new UserDepartment + { + Role = Role.Teller + } + }, + AdditionalPermissions = new List + { + new UserPermission + { + Permission = Permission.HaveSalesGoals + } + } + }); + + var res = await _userService.UpdatePermissions("123", new AdditionalPermissionRequest + { + //Already has Eligible for referrals based on role, should not add + AddPermCodes = new List { "ELIGIBLE_FOR_REFERRALS", "MANAGE_DEPARTMENT_GOALS" }, + RemovePermCodes = new List() + }); + + Assert.AreEqual(2, res.Count); + Assert.True(res.Contains(Permission.ManageDepartmentGoals)); + Assert.True(res.Contains(Permission.HaveSalesGoals)); + } + + [Test] + public async Task UpdatePermissions_Remove_OK() + { + Mock.Get(_userRepository) + .Setup(x => x.FindUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync( + new AppUser + { + //Perms: ElligibleForReferrals, ViewDashboard + Departments = new List + { + new UserDepartment + { + Role = Role.Teller + } + }, + AdditionalPermissions = new List + { + new UserPermission + { + Permission = Permission.HaveSalesGoals, + }, + new UserPermission + { + Permission = Permission.Unknown + } + } + }); + + var res = await _userService.UpdatePermissions("123", new AdditionalPermissionRequest + { + //Has Eligible for referrals based on role, should not remove + RemovePermCodes = new List { "ELIGIBLE_FOR_REFERRALS", "UNKNOWN" }, + AddPermCodes = new List() + }); + + Assert.AreEqual(1, res.Count); + Assert.AreEqual(Permission.HaveSalesGoals, res.Single()); + } + + [Test] + public async Task UpdatePermissions_Add_AlreadyExisting_OK() + { + Mock.Get(_userRepository) + .Setup(x => x.FindUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync( + new AppUser + { + //Perms: ElligibleForReferrals, ViewDashboard + Departments = new List + { + new UserDepartment + { + Role = Role.Teller + } + }, + AdditionalPermissions = new List + { + new UserPermission + { + Permission = Permission.HaveSalesGoals + } + } + }); + + var res = await _userService.UpdatePermissions("123", new AdditionalPermissionRequest + { + //Already has Eligible for referrals based on role, should not add + AddPermCodes = new List { "HAVE_SALES_GOALS" }, + RemovePermCodes = new List() + }); + + Assert.AreEqual(1, res.Count); + Assert.True(res.Contains(Permission.HaveSalesGoals)); + } + + [Test] + public async Task UpdatePermissions_Remove_NonExistant_OK() + { + Mock.Get(_userRepository) + .Setup(x => x.FindUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync( + new AppUser + { + //Perms: EligibleForReferrals, ViewDashboard + Departments = new List + { + new UserDepartment + { + Role = Role.Teller + } + }, + AdditionalPermissions = new List + { + new UserPermission + { + Permission = Permission.HaveSalesGoals, + } + } + }); + + var res = await _userService.UpdatePermissions("123", new AdditionalPermissionRequest + { + //Has Eligible for referrals based on role, should not remove + RemovePermCodes = new List { "UNKNOWN" }, + AddPermCodes = new List() + }); + + Assert.AreEqual(1, res.Count); + Assert.True(res.Contains(Permission.HaveSalesGoals)); + } + + [Test] + public async Task TerminateDayforce_Skip_AlreadyTermed() + { + var rawData = new Dictionary + { + ["Employee_DisplayName"] = "Doe, Test", + ["EmployeeEmploymentStatus_EmployeeNumber"] = "11771", + ["EmployeeManager_ManagerEmployeeNumber"] = "C00002", + ["IdLibrary_LoginName"] = "testdoe@landmarkcu.com", + ["OrgUnit_ShortName"] = "WAUWATOSA - BRANCH", + ["Job_ShortName"] = "TELLER II", + ["Employee_TerminationDate"] = "2019-09-23T00:00:00.0000000" + }; + var terminated = new TerminatedEmployeeResult(rawData); + + Mock.Get(_userRepository) + .Setup(x => x.FindUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync( + new AppUser + { + Role = Role.Teller, + AdditionalPermissions = new List(), + Terminated = DateTime.Today + }); + Mock.Get(_userRepository).Setup(s => s.SaveUserAsync(It.IsAny())).ReturnsAsync((AppUser x) => x); + Mock.Get(_unitOfWork).Setup(s => s.CompleteAsync()).Verifiable(); + + var result = await _userService.TerminateDayForceEmployee(terminated); + Assert.Null(result); + + Mock.Get(_userRepository).Verify(s => s.FindUserByEmployeeIdAsync("11771"), Times.Once); + Mock.Get(_userRepository).Verify(s => s.SaveUserAsync(It.IsAny()), Times.Never); + Mock.Get(_unitOfWork).Verify(s => s.CompleteAsync(), Times.Never); + } + + [Test] + public async Task TerminateDayforce_Basic() + { + var rawData = new Dictionary + { + ["Employee_DisplayName"] = "Doe, Test", + ["EmployeeEmploymentStatus_EmployeeNumber"] = "11771", + ["EmployeeManager_ManagerEmployeeNumber"] = "C00002", + ["IdLibrary_LoginName"] = "testdoe@landmarkcu.com", + ["OrgUnit_ShortName"] = "WAUWATOSA - BRANCH", + ["Job_ShortName"] = "TELLER II", + ["Employee_TerminationDate"] = "2019-09-23T00:00:00.0000000" + }; + var terminated = new TerminatedEmployeeResult(rawData); + var termUser = new AppUser + { + AdditionalPermissions = new List + { + new UserPermission + { + Permission = Permission.HaveSalesGoals, + } + }, + Departments = new List + { + new UserDepartment + { + StartDate = DateTime.Today, + EndDate = null, + Role = Role.Teller, + } + } + }; + + Mock.Get(_userRepository) + .Setup(x => x.FindUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync( + termUser); + Mock.Get(_userRepository).Setup(s => s.SaveUserAsync(It.IsAny())).ReturnsAsync((AppUser x) => x); + Mock.Get(_applicationDbIntegrityService).Setup(s => s.SyncIdsWithDb(It.IsAny(), false)).ReturnsAsync(true); + Mock.Get(_unitOfWork).Setup(s => s.CompleteAsync()).Verifiable(); + + Assert.Null(termUser.Terminated); + Assert.NotNull(termUser.AdditionalPermissions); + Assert.IsNotEmpty(termUser.AdditionalPermissions); + foreach (var department in termUser.Departments) + { + Assert.Null(department.EndDate); + } + + var result = await _userService.TerminateDayForceEmployee(terminated); + Assert.NotNull(result); + Assert.NotNull(result.Terminated); + Assert.NotNull(result.AdditionalPermissions); + Assert.IsEmpty(result.AdditionalPermissions); + foreach (var department in result.Departments) + { + Assert.NotNull(department.EndDate); + } + + Mock.Get(_userRepository).Verify(s => s.FindUserByEmployeeIdAsync("11771"), Times.Once); + // SaveUserAsync is for new users. + Mock.Get(_userRepository).Verify(s => s.SaveUserAsync(It.IsAny()), Times.Never); + Mock.Get(_unitOfWork).Verify(s => s.CompleteAsync(), Times.Once); + } + + [Test] + public void UpdatePermissions_NoUser_OK() + { + Mock.Get(_userRepository) + .Setup(x => x.FindUserByEmployeeIdAsync(It.IsAny())) + .ReturnsAsync((AppUser)null); + + Assert.ThrowsAsync(async () => await _userService.UpdatePermissions("123", + new AdditionalPermissionRequest + { + //Already has Eligible for referrals based on role, should not add + AddPermCodes = new List { "ELIGIBLE_FOR_REFERRALS", "APPROVE_REFERRALS" }, + RemovePermCodes = new List { "HAVE_SALES_GOALS", "UNKOWN" } + })); + } + + [Test] + public async Task TryFindSingleUserByName_OK() + { + Mock.Get(_userRepository) + .Setup(x => x.SearchUsersAsync(It.IsAny())) + .ReturnsAsync( + new List + { + new AppUser + { + EmployeeId = "1" + } + }); + + var res = await _userService.TryFindSingleUserByName(new UserNameSearchRequest()); + + Mock.Get(_userRepository).Verify(x => x.SearchUsersAsync(It.IsAny()), Times.Once); + Assert.NotNull(res); + Assert.AreEqual("1", res.EmployeeId); + } + + [Test] + public void TryFindSingleUserByName_ExceptsWhenMultiple() + { + Mock.Get(_userRepository) + .Setup(x => x.SearchUsersAsync(It.IsAny())) + .ReturnsAsync( + new List + { + new AppUser + { + EmployeeId = "1" + }, + new AppUser + { + EmployeeId = "2" + } + }); + + Assert.ThrowsAsync(async () => + await _userService.TryFindSingleUserByName(new UserNameSearchRequest())); + } + + [Test] + public async Task TryFindSingleUserByName_TriesTwice() + { + Mock.Get(_userRepository) + .Setup(x => x.SearchUsersAsync(It.Is(x => + !string.IsNullOrWhiteSpace(x.FirstName) && + !string.IsNullOrWhiteSpace(x.LastName) && + !string.IsNullOrWhiteSpace(x.MiddleInit) && + !string.IsNullOrWhiteSpace(x.DnaOrgNbr) + ))) + .ReturnsAsync(new List()); + + Mock.Get(_userRepository) + .Setup(x => x.SearchUsersAsync(It.Is(x => + !string.IsNullOrWhiteSpace(x.FirstName) && + !string.IsNullOrWhiteSpace(x.LastName) && + !string.IsNullOrWhiteSpace(x.DnaOrgNbr) && + string.IsNullOrWhiteSpace(x.MiddleInit) + ))) + .ReturnsAsync(new List + { + new AppUser + { + EmployeeId = "1" + } + }); + + var res = await _userService.TryFindSingleUserByName(new UserNameSearchRequest + { + FirstName = "John", + LastName = "Smith", + DnaOrgNbr = "5", + MiddleInit = "D" + }); + + Mock.Get(_userRepository).Verify(x => x.SearchUsersAsync(It.IsAny()), Times.Exactly(2)); + + Assert.NotNull(res); + Assert.AreEqual("1", res.EmployeeId); + } + + [Test] + public async Task TryFindSingleUserByName_TriesThrice() + { + Mock.Get(_userRepository) + .Setup(x => x.SearchUsersAsync(It.Is(x => + !string.IsNullOrWhiteSpace(x.FirstName) && + !string.IsNullOrWhiteSpace(x.LastName) && + !string.IsNullOrWhiteSpace(x.MiddleInit) && + !string.IsNullOrWhiteSpace(x.DnaOrgNbr) + ))) + .ReturnsAsync(new List()); + + Mock.Get(_userRepository) + .Setup(x => x.SearchUsersAsync(It.Is(user => + !string.IsNullOrWhiteSpace(user.FirstName) && + !string.IsNullOrWhiteSpace(user.LastName) && + !string.IsNullOrWhiteSpace(user.DnaOrgNbr) && + string.IsNullOrWhiteSpace(user.MiddleInit) + ))) + .ReturnsAsync(new List()); + + Mock.Get(_userRepository) + .Setup(x => x.SearchUsersAsync(It.Is(x => + !string.IsNullOrWhiteSpace(x.FirstName) && + !string.IsNullOrWhiteSpace(x.LastName) && + string.IsNullOrWhiteSpace(x.DnaOrgNbr) && + string.IsNullOrWhiteSpace(x.MiddleInit) + ))) + .ReturnsAsync(new List + { + new AppUser + { + EmployeeId = "1" + }, + }); + + var res = await _userService.TryFindSingleUserByName(new UserNameSearchRequest + { + FirstName = "John", + LastName = "Smith", + DnaOrgNbr = "5", + MiddleInit = "D" + }); + + Mock.Get(_userRepository).Verify(x => x.SearchUsersAsync(It.IsAny()), Times.Exactly(3)); + + Assert.NotNull(res); + Assert.AreEqual("1", res.EmployeeId); + } + + [Test] + public Task TryFindSingleUserByName_SkipsTry2IfNoMiddle_ThrowIfNotFound() + { + Mock.Get(_userRepository) + .Setup(x => x.SearchUsersAsync(It.Is(x => + !string.IsNullOrWhiteSpace(x.FirstName) && + !string.IsNullOrWhiteSpace(x.LastName) && + !string.IsNullOrWhiteSpace(x.MiddleInit) && + !string.IsNullOrWhiteSpace(x.DnaOrgNbr) + ))) + .ReturnsAsync(new List()); + + Mock.Get(_userRepository) + .Setup(x => x.SearchUsersAsync(It.Is(x => + !string.IsNullOrWhiteSpace(x.FirstName) && + !string.IsNullOrWhiteSpace(x.LastName) && + !string.IsNullOrWhiteSpace(x.DnaOrgNbr) && + string.IsNullOrWhiteSpace(x.MiddleInit) + ))) + .ReturnsAsync(new List()); + + Mock.Get(_userRepository) + .Setup(x => x.SearchUsersAsync(It.Is(x => + !string.IsNullOrWhiteSpace(x.FirstName) && + !string.IsNullOrWhiteSpace(x.LastName) && + string.IsNullOrWhiteSpace(x.DnaOrgNbr) && + string.IsNullOrWhiteSpace(x.MiddleInit) + ))) + .ReturnsAsync(new List()); + + Assert.ThrowsAsync(async () => + await _userService.TryFindSingleUserByName(new UserNameSearchRequest + { + FirstName = "John", + LastName = "Smith", + DnaOrgNbr = "5", + })); + + + Mock.Get(_userRepository).Verify(x => x.SearchUsersAsync(It.IsAny()), Times.Exactly(2)); + return Task.CompletedTask; + } + + [Test] + public async Task TryFindSingleUserByName_SkipsTry3IfFound() + { + Mock.Get(_userRepository) + .Setup(x => x.SearchUsersAsync(It.Is(x => + !string.IsNullOrWhiteSpace(x.FirstName) && + !string.IsNullOrWhiteSpace(x.LastName) && + !string.IsNullOrWhiteSpace(x.MiddleInit) && + !string.IsNullOrWhiteSpace(x.DnaOrgNbr) + ))) + .ReturnsAsync(new List()); + + Mock.Get(_userRepository) + .Setup(x => x.SearchUsersAsync(It.Is(x => + !string.IsNullOrWhiteSpace(x.FirstName) && + !string.IsNullOrWhiteSpace(x.LastName) && + !string.IsNullOrWhiteSpace(x.DnaOrgNbr) && + string.IsNullOrWhiteSpace(x.MiddleInit) + ))) + .ReturnsAsync(new List + { + new AppUser + { + EmployeeId = "1" + } + }); + + Mock.Get(_userRepository) + .Setup(x => x.SearchUsersAsync(It.Is(x => + !string.IsNullOrWhiteSpace(x.FirstName) && + !string.IsNullOrWhiteSpace(x.LastName) && + string.IsNullOrWhiteSpace(x.DnaOrgNbr) && + string.IsNullOrWhiteSpace(x.MiddleInit) + ))) + .ReturnsAsync(new List()); + + var res = await _userService.TryFindSingleUserByName(new UserNameSearchRequest + { + FirstName = "John", + LastName = "Smith", + DnaOrgNbr = "5", + MiddleInit = "D" + }); + + Mock.Get(_userRepository).Verify(x => x.SearchUsersAsync(It.IsAny()), Times.Exactly(2)); + + Assert.NotNull(res); + Assert.AreEqual("1", res.EmployeeId); + } + + [Test] + public Task TryFindSingleUserByName_SkipsTry3AndThrowsIfMultipleFound() + { + Mock.Get(_userRepository) + .Setup(x => x.SearchUsersAsync(It.Is(x => + !string.IsNullOrWhiteSpace(x.FirstName) && + !string.IsNullOrWhiteSpace(x.LastName) && + !string.IsNullOrWhiteSpace(x.MiddleInit) && + !string.IsNullOrWhiteSpace(x.DnaOrgNbr) + ))) + .ReturnsAsync(new List()); + + Mock.Get(_userRepository) + .Setup(x => x.SearchUsersAsync(It.Is(x => + !string.IsNullOrWhiteSpace(x.FirstName) && + !string.IsNullOrWhiteSpace(x.LastName) && + !string.IsNullOrWhiteSpace(x.DnaOrgNbr) && + string.IsNullOrWhiteSpace(x.MiddleInit) + ))) + .ReturnsAsync(new List + { + new AppUser + { + EmployeeId = "1" + }, + new AppUser + { + EmployeeId = "3" + } + }); + + Mock.Get(_userRepository) + .Setup(x => x.SearchUsersAsync(It.Is(x => + !string.IsNullOrWhiteSpace(x.FirstName) && + !string.IsNullOrWhiteSpace(x.LastName) && + string.IsNullOrWhiteSpace(x.DnaOrgNbr) && + string.IsNullOrWhiteSpace(x.MiddleInit) + ))) + .ReturnsAsync(new List()); + + Assert.ThrowsAsync(async () => + await _userService.TryFindSingleUserByName(new UserNameSearchRequest + { + FirstName = "John", + LastName = "Smith", + DnaOrgNbr = "5", + MiddleInit = "D" + })); + return Task.CompletedTask; + } + + [Test] + public Task TryFindSingleUserByName_Exception() + { + Mock.Get(_userRepository) + .Setup(x => x.SearchUsersAsync(It.Is(x => + !string.IsNullOrWhiteSpace(x.FirstName) && + !string.IsNullOrWhiteSpace(x.LastName) && + !string.IsNullOrWhiteSpace(x.MiddleInit) && + !string.IsNullOrWhiteSpace(x.DnaOrgNbr) + ))) + .ReturnsAsync(new List()); + + Mock.Get(_userRepository) + .Setup(x => x.SearchUsersAsync(It.Is(x => + !string.IsNullOrWhiteSpace(x.FirstName) && + !string.IsNullOrWhiteSpace(x.LastName) && + !string.IsNullOrWhiteSpace(x.DnaOrgNbr) && + string.IsNullOrWhiteSpace(x.MiddleInit) + ))) + .ThrowsAsync(new Exception("OOf")); + + Mock.Get(_userRepository) + .Setup(x => x.SearchUsersAsync(It.Is(x => + !string.IsNullOrWhiteSpace(x.FirstName) && + !string.IsNullOrWhiteSpace(x.LastName) && + string.IsNullOrWhiteSpace(x.DnaOrgNbr) && + string.IsNullOrWhiteSpace(x.MiddleInit) + ))) + .ReturnsAsync(new List()); + + Assert.ThrowsAsync(async () => + await _userService.TryFindSingleUserByName(new UserNameSearchRequest + { + FirstName = "John", + LastName = "Smith", + DnaOrgNbr = "5", + MiddleInit = "D" + })); + return Task.CompletedTask; + } + + [Test] + public async Task FindDepartmentManagers_Ok() + { + var bm = new AppUser + { + AdEmail = "bm@landmarkcu.com", + Departments = new List + { + new UserDepartment + { + Role = Role.BM, + DepartmentId = 1 + } + } + }; + var teller = new AppUser + { + AdEmail = "teller@landmarkcu.com", + Departments = new List + { + new UserDepartment + { + Role = Role.Teller, + DepartmentId = 1 + } + } + }; + var bom = new AppUser + { + AdEmail = "bom@landmarkcu.com", + Departments = new List + { + new UserDepartment + { + Role = Role.BOM, + DepartmentId = 1 + } + } + }; + var abm = new AppUser + { + AdEmail = "abm@landmarkcu.com", + Departments = new List + { + new UserDepartment + { + Role = Role.ABM, + DepartmentId = 1 + } + } + }; + var manager = new AppUser + { + AdEmail = "manager@landmarkcu.com", + Departments = new List + { + new UserDepartment + { + Role = Role.Manager, + DepartmentId = 1 + } + } + }; + var managerOtherDepartment = new AppUser + { + AdEmail = "manager@landmarkcu.com", + Departments = new List + { + new UserDepartment + { + Role = Role.Manager, + DepartmentId = 2 + } + } + }; + + var usersFromRepo = new List + { + bm, + teller, + bom, + abm, + manager, + managerOtherDepartment + }; + + Mock.Get(_departmentRepository) + .Setup(x => x.FindDepartmentAssignments(It.IsAny())) + .ReturnsAsync(usersFromRepo); + + var result = await _userService.FindDepartmentManagers(1); + + Assert.AreEqual(4, result.Count); + Assert.True(result.Any(x => x.AdEmail.Equals("bm@landmarkcu.com"))); + Assert.True(result.Any(x => x.AdEmail.Equals("manager@landmarkcu.com"))); + Assert.True(result.Any(x => x.AdEmail.Equals("bom@landmarkcu.com"))); + Assert.True(result.Any(x => x.AdEmail.Equals("abm@landmarkcu.com"))); + Assert.False(result.Any(x => x.AdEmail.Equals("teller@landmarkcu.com"))); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Authorization/PermissionAuthorizationHandler.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Authorization/PermissionAuthorizationHandler.cs new file mode 100644 index 0000000..7ec2415 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Authorization/PermissionAuthorizationHandler.cs @@ -0,0 +1,44 @@ +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using Microsoft.AspNetCore.Authorization; + +namespace LCU.API.Internal.Operations.IncentiveApi.Authorization; + +public class PermissionAuthorizationHandler : AuthorizationHandler +{ + private readonly ICurrentUserService _currentUserService; + private readonly IUserService _userService; + + public PermissionAuthorizationHandler(ICurrentUserService currentUserService, IUserService userService) + { + _currentUserService = currentUserService; + _userService = userService; + } + + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, + PermissionRequirement requirement) + { + try + { + var allowedPermissions = requirement.Permissions.Split(',').ToList(); + var appUser = _currentUserService.GetCurrentUser(); + if (appUser.Email == null) + { + context.Fail(); + return; + } + + if (await _userService.UserHasAnyRequiredPermission(appUser.Email, allowedPermissions)) + { + context.Succeed(requirement); + return; + } + context.Fail(); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + context.Fail(); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Authorization/PermissionPolicyProvider.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Authorization/PermissionPolicyProvider.cs new file mode 100644 index 0000000..220ae5e --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Authorization/PermissionPolicyProvider.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Options; + +namespace LCU.API.Internal.Operations.IncentiveApi.Authorization; + +public class PermissionPolicyProvider : IAuthorizationPolicyProvider +{ + const string POLICY_PREFIX = "Permission"; + public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; } + + public PermissionPolicyProvider(IOptions options) + { + FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options); + } + + public Task GetDefaultPolicyAsync() => FallbackPolicyProvider.GetDefaultPolicyAsync(); + + public Task GetFallbackPolicyAsync() => FallbackPolicyProvider.GetFallbackPolicyAsync(); + + public Task GetPolicyAsync(string policyName) + { + if (policyName.StartsWith(POLICY_PREFIX, StringComparison.OrdinalIgnoreCase)) + { + var allowedPermissions = policyName.Substring(POLICY_PREFIX.Length); + var policy = new AuthorizationPolicyBuilder(); + policy.AddRequirements(new PermissionRequirement(allowedPermissions)); + return Task.FromResult(policy.Build()); + } + + return FallbackPolicyProvider.GetPolicyAsync(policyName); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Authorization/PermissionRequirement.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Authorization/PermissionRequirement.cs new file mode 100644 index 0000000..cf74089 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Authorization/PermissionRequirement.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Authorization; + +namespace LCU.API.Internal.Operations.IncentiveApi.Authorization; + +public class PermissionRequirement : IAuthorizationRequirement +{ + public string Permissions { get; private set; } + public PermissionRequirement(string permissions) + { + Permissions = permissions; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Authorization/RequirePermissionAttribute.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Authorization/RequirePermissionAttribute.cs new file mode 100644 index 0000000..61b7c3a --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Authorization/RequirePermissionAttribute.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Authorization; + +namespace LCU.API.Internal.Operations.IncentiveApi.Authorization; + +[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] +public class RequirePermissionAttribute : AuthorizeAttribute +{ + const string POLICY_PREFIX = "Permission"; + + public RequirePermissionAttribute(string permissions) => Permissions = permissions; + + public string Permissions + { + get => Policy.Substring(POLICY_PREFIX.Length); + set => Policy = $"{POLICY_PREFIX}{value}"; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/ApiConfiguration.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/ApiConfiguration.cs new file mode 100644 index 0000000..6a9a131 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/ApiConfiguration.cs @@ -0,0 +1,12 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Configuration; + +public class ApiConfiguration +{ + public string AppName { get; set; } + public string SeqUrl { get; set; } + public string SeqKey { get; set; } + public string[] CorsAllowedOrigins { get; set; } + public string IncentiveManagerClientUrl { get; set; } + public FeatureFlags FeatureFlags { get; set; } + public string ApplicationDbIntegrityCronSchedule { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/CoreApiWrapperConfiguration.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/CoreApiWrapperConfiguration.cs new file mode 100644 index 0000000..475085a --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/CoreApiWrapperConfiguration.cs @@ -0,0 +1,41 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace LCU.API.Internal.Operations.IncentiveApi.Configuration; + +public class CoreApiWrapperConfiguration +{ + public string Url { get; set; } + public string GetNewCheckingAccountActivityEndpoint { get; set; } + public string GetNewDigitalBankingCheckingAccountActivityEndpoint { get; set; } + public string GetNewATMCheckCardActivityEndpoint { get; set; } + public string GetNewActivityManagerReferralsActivityEndpoint { get; set; } + public string GetNewMortgageAccountActivityEndpoint { get; set; } + public string GetNewVehicleSaleActivityEndpoint { get; set; } + public string GetNewDPEligibleAccountsActivityEndpoint { get; set; } + public string QueryActivityManagerActivityEndpoint { get; set; } + public string GetAccountActivityAccountDetailsEndpoint { get; set; } + public string GetTransactionHistoryEndpoint { get; set; } + public string GetEStatementDeliveryMethodsEndpoint { get; set; } + [NotMapped] + public string GetNewCheckingAccountActivityUrl => Url + GetNewCheckingAccountActivityEndpoint; + [NotMapped] + public string GetNewDigitalBankingCheckingAccountActivityUrl => Url + GetNewDigitalBankingCheckingAccountActivityEndpoint; + [NotMapped] + public string GetNewATMCheckCardActivityUrl => Url + GetNewATMCheckCardActivityEndpoint; + [NotMapped] + public string GetNewActivityManagerReferralsActivityUrl => Url + GetNewActivityManagerReferralsActivityEndpoint; + [NotMapped] + public string GetNewMortgageAccountActivityUrl => Url + GetNewMortgageAccountActivityEndpoint; + [NotMapped] + public string GetNewVehicleSaleActivityUrl => Url + GetNewVehicleSaleActivityEndpoint; + [NotMapped] + public string GetNewDPEligibleAccountsActivityUrl => Url + GetNewDPEligibleAccountsActivityEndpoint; + [NotMapped] + public string QueryActivityManagerActivityUrl => Url + QueryActivityManagerActivityEndpoint; + [NotMapped] + public string GetAccountActivityAccountDetailsUrl => Url + GetAccountActivityAccountDetailsEndpoint; + [NotMapped] + public string GetTransactionHistoryUrl => Url + GetTransactionHistoryEndpoint; + [NotMapped] + public string GetEStatementDeliveryMethodsUrl => Url + GetEStatementDeliveryMethodsEndpoint; +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/DayForceConfiguration.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/DayForceConfiguration.cs new file mode 100644 index 0000000..eee0a5d --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/DayForceConfiguration.cs @@ -0,0 +1,13 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Configuration; + +public class DayForceConfiguration +{ + public string Url { get; set; } + public string DayForceClientName { get; set; } + public string DayForceApiVersion { get; set; } + public string DayForceUsername { get; set; } + public string DayForcePassword { get; set; } + public string DayForceActiveUserReport { get; set; } + public string DayForceTerminatedUserReport { get; set; } + public List DayForceManualReportRecipients { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/EmailRecipientsConfiguration.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/EmailRecipientsConfiguration.cs new file mode 100644 index 0000000..0f4e6fe --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/EmailRecipientsConfiguration.cs @@ -0,0 +1,7 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Configuration; + +public class EmailRecipientsConfiguration +{ + public List TransferRequestInitiatedNotification { get; set; } + public bool OverrideRecipients { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/FeatureFlags.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/FeatureFlags.cs new file mode 100644 index 0000000..0e722a2 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/FeatureFlags.cs @@ -0,0 +1,18 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Configuration; + +public class FeatureFlags +{ + public const string EnableControllers = "EnableControllers"; + public bool RunImportCreditCardSalesConsumer { get; set; } + public bool RunImportIndirectVehicleSalesConsumer { get; set; } + public bool RunReportingProcessConsumer { get; set; } + public bool RunGoalDispersalProcessConsumer { get; set; } + public bool RunPayoutIncentiveProcessConsumer { get; set; } + public bool RunDayforceSyncProcessConsumer { get; set; } + public bool RunGapMbpProcessConsumer { get; set; } + public bool RunActionCreditCardProcessConsumer { get; set; } + public bool RunActionVehicleProcessConsumer { get; set; } + public bool RunIncentiveProcessesConsumer { get; set; } + public bool RunNightlyIncentiveProcessesConsumer { get; set; } + public bool RunApplicationDbIntegrityHostedService { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/FileOperationsApiConfiguration.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/FileOperationsApiConfiguration.cs new file mode 100644 index 0000000..97979e0 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/FileOperationsApiConfiguration.cs @@ -0,0 +1,20 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace LCU.API.Internal.Operations.IncentiveApi.Configuration; + +public class FileOperationsApiConfiguration +{ + public string Url { get; set; } + public string GetFileContentEndpoint { get; set; } + public string WriteFileEndpoint { get; set; } + public string BuildDynamicFileListEndpoint { get; set; } + public int RetryCount { get; set; } + [NotMapped] + public Uri GetFileContentUri => new Uri(Url + GetFileContentEndpoint); + [NotMapped] + public Uri BuildDynamicFileListUri => new Uri(Url + BuildDynamicFileListEndpoint); + [NotMapped] + public Uri WriteFileEndpointUri => new Uri(Url + WriteFileEndpoint); + [NotMapped] + public Uri BuildDynamicFileListEndpointUri => new Uri(Url + BuildDynamicFileListEndpoint); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/NotificationApiConfiguration.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/NotificationApiConfiguration.cs new file mode 100644 index 0000000..1437ce9 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/NotificationApiConfiguration.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace LCU.API.Internal.Operations.IncentiveApi.Configuration; + +public class NotificationApiConfiguration +{ + public string Url { get; set; } + public string Email { get; set; } + public int RetryCount { get; set; } + [NotMapped] + public string EmailUrl => Url + Email; +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/ReceiveEndpointConfigurations.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/ReceiveEndpointConfigurations.cs new file mode 100644 index 0000000..4628f70 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/ReceiveEndpointConfigurations.cs @@ -0,0 +1,19 @@ +using LCU.Library.Internal.LcuMassTransitModels; + +namespace LCU.API.Internal.Operations.IncentiveApi.Configuration; + +public class ReceiveEndpointConfigurations +{ + public ReceiveEndpointConfiguration ImportCreditCardSalesEndpoint { get; set; } + public ReceiveEndpointConfiguration ImportIndirectVehicleSalesEndpoint { get; set; } + public ReceiveEndpointConfiguration ReportingProcessEndpoint { get; set; } + public ReceiveEndpointConfiguration GoalDispersalProcessEndpoint { get; set; } + public ReceiveEndpointConfiguration PayoutIncentiveProcessEndpoint { get; set; } + public ReceiveEndpointConfiguration DayforceSyncProcessEndpoint { get; set; } + public ReceiveEndpointConfiguration GapMbpProcessEndpoint { get; set; } + public ReceiveEndpointConfiguration ActionCreditCardProcessEndpoint { get; set; } + public ReceiveEndpointConfiguration ActionVehicleProcessEndpoint { get; set; } + public ReceiveEndpointConfiguration IncentiveProcessesEndpoint { get; set; } + public ReceiveEndpointConfiguration NightlyIncentiveProcessesEndpoint { get; set; } + +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/ReportConfiguration.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/ReportConfiguration.cs new file mode 100644 index 0000000..6d09465 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/ReportConfiguration.cs @@ -0,0 +1,8 @@ +using LCU.API.Internal.Operations.IncentiveApi.Models; + +namespace LCU.API.Internal.Operations.IncentiveApi.Configuration; + +public class ReportConfiguration +{ + public List ReportConfigs { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/SqlConnectionConfiguration.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/SqlConnectionConfiguration.cs new file mode 100644 index 0000000..f60654f --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Configuration/SqlConnectionConfiguration.cs @@ -0,0 +1,8 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Configuration; + +public class SqlConnectionConfiguration +{ + public string ConnectionString { get; set; } + public int MaxRetryCount { get; set; } + public TimeSpan MaxRetryDelay { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/ActionCreditCardConsumer.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/ActionCreditCardConsumer.cs new file mode 100644 index 0000000..96e56b0 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/ActionCreditCardConsumer.cs @@ -0,0 +1,45 @@ +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.API.Internal.Operations.IncentiveApi.Services.Action; +using Microsoft.Extensions.DependencyInjection; +using MassTransit; + +namespace LCU.API.Internal.Operations.IncentiveApi.Consumers; + +public class ActionCreditCardConsumer : IConsumer +{ + private readonly IServiceScopeFactory _scopeFactory; + + public ActionCreditCardConsumer(IServiceScopeFactory scopeFactory) + { + _scopeFactory = scopeFactory; + } + + public async Task Consume(ConsumeContext context) + { + try + { + Log.Debug("Starting Action Credit Card Process."); + using var scope = _scopeFactory.CreateScope(); + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().RunActionCreditCardProcess(context.Message)); + Log.Debug("Finished Action Credit Card Process."); + } + catch (ServiceException serviceException) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Error(serviceException, $"Action Credit Card Failure. " + + $"The Action Credit Card Process Request is placed on LCU.IncentiveApi.ActionCreditCardProcess_error."); + throw; + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Action Credit Card Failure. " + + "The Action Credit Card Process Request is placed on LCU.IncentiveApi.ActionCreditCardProcess_error."); + throw; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/ActionVehicleConsumer.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/ActionVehicleConsumer.cs new file mode 100644 index 0000000..a3ee290 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/ActionVehicleConsumer.cs @@ -0,0 +1,45 @@ +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using Microsoft.Extensions.DependencyInjection; +using LCU.API.Internal.Operations.IncentiveApi.Services.Action; +using MassTransit; + +namespace LCU.API.Internal.Operations.IncentiveApi.Consumers; + +public class ActionVehicleConsumer : IConsumer +{ + private readonly IServiceScopeFactory _scopeFactory; + + public ActionVehicleConsumer(IServiceScopeFactory scopeFactory) + { + _scopeFactory = scopeFactory; + } + + public async Task Consume(ConsumeContext context) + { + try + { + Log.Debug("Starting Action Vehicle Process."); + using var scope = _scopeFactory.CreateScope(); + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().RunActionVehicleProcess(context.Message)); + Log.Debug("Finished Action Vehicle Process."); + } + catch (ServiceException serviceException) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Error(serviceException, $"Action Vehicle Failure. " + + $"The Action Vehicle Process Request is placed on LCU.IncentiveApi.ActionVehicleProcess_error."); + throw; + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Action Vehicle Failure. " + + "The Action Vehicle Process Request is placed on LCU.IncentiveApi.ActionVehicleProcess_error."); + throw; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/AlliedGapMbpConsumer.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/AlliedGapMbpConsumer.cs new file mode 100644 index 0000000..7fca53f --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/AlliedGapMbpConsumer.cs @@ -0,0 +1,45 @@ +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using Microsoft.Extensions.DependencyInjection; +using LCU.API.Internal.Operations.IncentiveApi.Services.Allied; +using MassTransit; + +namespace LCU.API.Internal.Operations.IncentiveApi.Consumers; + +public class AlliedGapMbpConsumer : IConsumer +{ + private readonly IServiceScopeFactory _scopeFactory; + + public AlliedGapMbpConsumer(IServiceScopeFactory scopeFactory) + { + _scopeFactory = scopeFactory; + } + + public async Task Consume(ConsumeContext context) + { + try + { + Log.Debug("Starting Allied Gap/Mbp Process."); + using var scope = _scopeFactory.CreateScope(); + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().RunAlliedGapMbpProcess(context.Message)); + Log.Debug("Finished Allied Gap/Mbp Process."); + } + catch (ServiceException serviceException) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Error(serviceException, $"Allied Gap/Mbp Failure. " + + $"The Allied Gap/Mbp Process Request is placed on LCU.IncentiveApi.AlliedGapMbpProcess_error."); + throw; + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Allied Gap/Mbp Failure. " + + "The Allied Gap/Mbp Process Request is placed on LCU.IncentiveApi.AlliedGapMbpProcess_error."); + throw; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/DayforceSyncProcessConsumer.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/DayforceSyncProcessConsumer.cs new file mode 100644 index 0000000..a3596f7 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/DayforceSyncProcessConsumer.cs @@ -0,0 +1,45 @@ +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using Microsoft.Extensions.DependencyInjection; +using LCU.API.Internal.Operations.IncentiveApi.Services.Dayforce; +using MassTransit; + +namespace LCU.API.Internal.Operations.IncentiveApi.Consumers; + +public class DayforceSyncProcessConsumer : IConsumer +{ + private readonly IServiceScopeFactory _scopeFactory; + + public DayforceSyncProcessConsumer(IServiceScopeFactory scopeFactory) + { + _scopeFactory = scopeFactory; + } + + public async Task Consume(ConsumeContext context) + { + try + { + Log.Debug("Starting Dayforce Sync Process."); + using var scope = _scopeFactory.CreateScope(); + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().SyncDayForceUsers(context.Message)); + Log.Debug("Finished Dayforce Sync Process."); + } + catch (ServiceException serviceException) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Error(serviceException, $"Dayforce Sync Failure. " + + $"The Dayforce Sync Process Request is placed on LCU.IncentiveApi.DayforceSyncProcess_error."); + throw; + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Dayforce Sync Failure. " + + $"The Dayforce Sync Failure Process Request is placed on LCU.IncentiveApi.DayforceSyncProcess_error."); + throw; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/GoalDispersalConsumer.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/GoalDispersalConsumer.cs new file mode 100644 index 0000000..e1dec60 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/GoalDispersalConsumer.cs @@ -0,0 +1,45 @@ +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using Microsoft.Extensions.DependencyInjection; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using MassTransit; + +namespace LCU.API.Internal.Operations.IncentiveApi.Consumers; + +public class GoalDispersalConsumer : IConsumer +{ + private readonly IServiceScopeFactory _scopeFactory; + + public GoalDispersalConsumer(IServiceScopeFactory scopeFactory) + { + _scopeFactory = scopeFactory; + } + + public async Task Consume(ConsumeContext context) + { + try + { + Log.Debug("Starting Goal Dispersal Process."); + using var scope = _scopeFactory.CreateScope(); + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().ExecuteMonthStartDispersal(context.Message)); + Log.Debug("Finished Goal Dispersal Process."); + } + catch (ServiceException serviceException) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Error(serviceException, $"Goal Dispersal Failure. " + + $"The Goal Dispersal Process Request is placed on LCU.IncentiveApi.GoalDispersalProcess_error."); + throw; + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Goal Dispersal Failure. " + + $"The Goal Dispersal Process Request is placed on LCU.IncentiveApi.GoalDispersalProcess_error."); + throw; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/ImportCreditCardSalesConsumer.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/ImportCreditCardSalesConsumer.cs new file mode 100644 index 0000000..1440d38 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/ImportCreditCardSalesConsumer.cs @@ -0,0 +1,45 @@ +using LCU.API.Internal.Operations.IncentiveApi.Services.Temenos; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using Microsoft.Extensions.DependencyInjection; +using MassTransit; + +namespace LCU.API.Internal.Operations.IncentiveApi.Consumers; + +public class ImportCreditCardSalesConsumer : IConsumer +{ + private readonly IServiceScopeFactory _scopeFactory; + + public ImportCreditCardSalesConsumer(IServiceScopeFactory scopeFactory) + { + _scopeFactory = scopeFactory; + } + + public async Task Consume(ConsumeContext context) + { + try + { + Log.Debug("Starting HandleImportCreditCardSales."); + using var scope = _scopeFactory.CreateScope(); + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().HandleImportCreditCardSales(context.Message)); + Log.Debug("Finished HandleImportCreditCardSales."); + } + catch (ServiceException serviceException) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Error(serviceException, $"Import Credit Card Sales Failure. " + + $"The Import Credit Card Sales Request is placed on LCU.IncentiveApi.ImportCreditCardSales_error."); + throw; + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Import Credit Card Sales Failure. " + + $"The Import Credit Card Sales Request is placed on LCU.IncentiveApi.ImportCreditCardSales_error."); + throw; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/ImportIndirectVehicleSalesConsumer.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/ImportIndirectVehicleSalesConsumer.cs new file mode 100644 index 0000000..c8ebba7 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/ImportIndirectVehicleSalesConsumer.cs @@ -0,0 +1,45 @@ +using LCU.API.Internal.Operations.IncentiveApi.Services.Temenos; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using Microsoft.Extensions.DependencyInjection; +using LCU.Library.Internal.Exceptions; +using MassTransit; + +namespace LCU.API.Internal.Operations.IncentiveApi.Consumers; + +public class ImportIndirectVehicleSalesConsumer : IConsumer +{ + private readonly IServiceScopeFactory _scopeFactory; + + public ImportIndirectVehicleSalesConsumer(IServiceScopeFactory scopeFactory) + { + _scopeFactory = scopeFactory; + } + + public async Task Consume(ConsumeContext context) + { + try + { + Log.Debug("Starting HandleImportIndirectVehicleSales."); + using var scope = _scopeFactory.CreateScope(); + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().HandleImportIndirectVehicleSales(context.Message)); + Log.Debug("Finished HandleImportIndirectVehicleSales."); + } + catch (ServiceException serviceException) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Error(serviceException, $"Import Indirect Vehicle Sales Failure. " + + $"The Import Indirect Vehicle Sales Request is placed on LCU.IncentiveApi.ImportIndirectVehicleSales_error."); + throw; + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Import Indirect Vehicle Sales Failure. " + + $"The Import Indirect Vehicle Sales Request is placed on LCU.IncentiveApi.ImportIndirectVehicleSales_error."); + throw; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/IncentiveProcessesConsumer.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/IncentiveProcessesConsumer.cs new file mode 100644 index 0000000..82906de --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/IncentiveProcessesConsumer.cs @@ -0,0 +1,43 @@ +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using Microsoft.Extensions.DependencyInjection; +using MassTransit; + +namespace LCU.API.Internal.Operations.IncentiveApi.Consumers; + +public class IncentiveProcessesConsumer : IConsumer +{ + private readonly IServiceScopeFactory _scopeFactory; + + public IncentiveProcessesConsumer(IServiceScopeFactory scopeFactory) + { + _scopeFactory = scopeFactory; + } + + public async Task Consume(ConsumeContext context) + { + try + { + using var scope = _scopeFactory.CreateScope(); + await Task.Run(() => scope.ServiceProvider.GetRequiredService().HandleProcessRequest(context.Message)); + } + catch (ServiceException serviceException) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Error(serviceException, $"Incentive Process Failure. " + + $"The Process Request is placed on LCU.IncentiveApi.Process_error."); + throw; + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Incentive Process Failure. " + + "The Process Request is placed on LCU.IncentiveApi.Process_error."); + throw; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/NightlyIncentiveProcessesConsumer.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/NightlyIncentiveProcessesConsumer.cs new file mode 100644 index 0000000..e7212f4 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/NightlyIncentiveProcessesConsumer.cs @@ -0,0 +1,43 @@ +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using Microsoft.Extensions.DependencyInjection; +using LCU.API.Internal.Operations.IncentiveApi.Services.NightlyDecisionServices; +using MassTransit; + +namespace LCU.API.Internal.Operations.IncentiveApi.Consumers; + +public class NightlyIncentiveProcessesConsumer : IConsumer +{ + private readonly IServiceScopeFactory _scopeFactory; + + public NightlyIncentiveProcessesConsumer(IServiceScopeFactory scopeFactory) + { + _scopeFactory = scopeFactory; + } + + public async Task Consume(ConsumeContext context) + { + try + { + using var scope = _scopeFactory.CreateScope(); + await Task.Run(() => scope.ServiceProvider.GetRequiredService().HandleProcessRequest(context.Message)); + } + catch (ServiceException serviceException) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Error(serviceException, $"Nightly Incentive Process Failure. " + + $"The Nightly Incentive Process Request is placed on LCU.IncentiveApi.NightlyIncentiveProcess_error."); + throw; + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Nightly Incentive Process Failure. " + + "The Nightly Incentive Process Request is placed on LCU.IncentiveApi.NightlyIncentiveProcess_error."); + throw; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/PayoutIncentiveConsumer.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/PayoutIncentiveConsumer.cs new file mode 100644 index 0000000..8344464 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/PayoutIncentiveConsumer.cs @@ -0,0 +1,45 @@ +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using Microsoft.Extensions.DependencyInjection; +using LCU.API.Internal.Operations.IncentiveApi.Services.Payout; +using MassTransit; + +namespace LCU.API.Internal.Operations.IncentiveApi.Consumers; + +public class PayoutIncentiveConsumer : IConsumer +{ + private readonly IServiceScopeFactory _scopeFactory; + + public PayoutIncentiveConsumer(IServiceScopeFactory scopeFactory) + { + _scopeFactory = scopeFactory; + } + + public async Task Consume(ConsumeContext context) + { + try + { + Log.Information("Starting Payout Incentive Process."); + using var scope = _scopeFactory.CreateScope(); + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().RunMonthlyIncentivePayouts(context.Message)); + Log.Information("Finished Payout Incentive Process."); + } + catch (ServiceException serviceException) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Error(serviceException, $"Payout Incentive Process Failure. " + + $"The Payout Incentive Process Request is placed on LCU.IncentiveApi.PayoutIncentiveProcess_error."); + throw; + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Payout Incentive Process Failure. " + + $"The Payout Incentive Process Request is placed on LCU.IncentiveApi.PayoutIncentiveProcess_error."); + throw; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/ReportingConsumer.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/ReportingConsumer.cs new file mode 100644 index 0000000..54dbee7 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Consumers/ReportingConsumer.cs @@ -0,0 +1,45 @@ +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using Microsoft.Extensions.DependencyInjection; +using LCU.API.Internal.Operations.IncentiveApi.Services.Reporting; +using MassTransit; + +namespace LCU.API.Internal.Operations.IncentiveApi.Consumers; + +public class ReportingConsumer : IConsumer +{ + private readonly IServiceScopeFactory _scopeFactory; + + public ReportingConsumer(IServiceScopeFactory scopeFactory) + { + _scopeFactory = scopeFactory; + } + + public async Task Consume(ConsumeContext context) + { + try + { + Log.Debug("Starting Reporting Process."); + using var scope = _scopeFactory.CreateScope(); + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().HandleMonthlyReporting(context.Message)); + Log.Debug("Finished Reporting Process."); + } + catch (ServiceException serviceException) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Error(serviceException, $"Reporting Process Failure. " + + $"The Reporting Process Request is placed on LCU.IncentiveApi.ReportingProcess_error."); + throw; + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Reporting Process Failure. " + + $"The Reporting Process Request is placed on LCU.IncentiveApi.ReportingProcess_error."); + throw; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/ActivityController.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/ActivityController.cs new file mode 100644 index 0000000..20d9ad6 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/ActivityController.cs @@ -0,0 +1,751 @@ +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; +using LCU.API.Internal.Operations.IncentiveApi.Models; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.FeatureManagement.Mvc; +using LCU.API.Internal.Operations.IncentiveApi.Authorization; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; + +namespace LCU.API.Internal.Operations.IncentiveApi.Controllers; + +[FeatureGate(FeatureFlags.EnableControllers)] +[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] +[ApiController] +[Route("api/[controller]")] +public class ActivityController : ControllerBase +{ + private readonly IActivityService _activityService; + private readonly IActivityDetailService _activityDetailService; + private readonly IDashboardService _dashboardService; + private readonly IUserService _userService; + private readonly ICurrentUserService _currentUser; + private readonly IActivityDecisionService _activityDecisionService; + private readonly IEStatementService _eStatementService; + + public ActivityController(IActivityService activityService, IActivityDetailService activityDetailService, IUserService userService, + ICurrentUserService currentUser, IDashboardService dashboardService, IActivityDecisionService activityDecisionService, + IEStatementService eStatementService) + { + _activityService = activityService; + _activityDetailService = activityDetailService; + _userService = userService; + _currentUser = currentUser; + _dashboardService = dashboardService; + _activityDecisionService = activityDecisionService; + _eStatementService = eStatementService; + } + + [HttpGet("paged")] + [ProducesResponseType(typeof(PagedResponse), 200)] + public async Task Get([FromQuery] ActivityFilter filter) + { + try + { + var searchResult = await _activityService.ListByFilterAsync(filter); + + foreach (var activity in searchResult.Items) + { + activity?.TransformForView(); + } + + var result = new PagedResponse(searchResult, filter, Url, Request); + + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("me", Name = "GetCurrentUserProgress")] + [RequirePermission("VIEW_DASHBOARD")] + public async Task GetCurrentUserProgress([FromQuery] int fromMonth, [FromQuery] int fromYear, + [FromQuery] int toMonth, [FromQuery] int toYear, [FromQuery] string employeeId = "") + { + try + { + var monthRange = new MonthRange(fromYear, fromMonth, toYear, toMonth); + var appUser = _currentUser.GetCurrentUser(); + + if (appUser?.Email == null) + { + return Unauthorized("No AD User Found."); + } + + var employee = await _userService.GetUserByAdEmailAsync(appUser.Email); + if (!string.IsNullOrWhiteSpace(employeeId)) + { + employee = await _userService.GetUserByEmployeeIdAsync(employeeId); + } + + if (employee == null) + { + return NoContent(); + } + + var result = await _dashboardService.GetEmployeeProgressForMonthRangeAsync(employee, monthRange); + + result?.TransformForView(); + + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("me/departments", Name = "GetCurrentUserDepartmentProgress")] + [RequirePermission("VIEW_DASHBOARD,VIEW_PERFORMANCE_FOR_DEPARTMENT")] + public async Task GetCurrentUserDepartmentProgress([FromQuery] int fromMonth, + [FromQuery] int fromYear, + [FromQuery] int toMonth, [FromQuery] int toYear, [FromQuery] int departmentId = 0) + { + try + { + var monthRange = new MonthRange(fromYear, fromMonth, toYear, toMonth); + var appUser = _currentUser.GetCurrentUser(); + + if (appUser?.Email == null) + { + return Unauthorized("No AD User Found."); + } + + var employee = await _userService.GetUserByAdEmailAsync(appUser.Email); + if (employee == null) + { + return NoContent(); + } + + var result = new List(); + if (departmentId != 0) + { + result.Add( + await _dashboardService.GetDepartmentProgressForMonthRangeAsync(departmentId, monthRange)); + } + else + { + result.AddRange( + await _dashboardService.GetDepartmentProgressForMonthRangeAsync(employee.Departments, + monthRange)); + } + + foreach (var departmentProgress in result) + { + departmentProgress?.TransformForView(); + } + + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("region", Name = "GetRegionProgress")] + [RequirePermission("VIEW_DASHBOARD,VIEW_PERFORMANCE_FOR_REGION")] + public async Task GetRegionProgress([FromQuery] int fromMonth, + [FromQuery] int fromYear, + [FromQuery] int toMonth, [FromQuery] int toYear, [FromQuery] string regionCode) + { + try + { + var monthRange = new MonthRange(fromYear, fromMonth, toYear, toMonth); + var appUser = _currentUser.GetCurrentUser(); + + if (appUser?.Email == null) + { + return Unauthorized("No AD User Found."); + } + + var employee = await _userService.GetUserByAdEmailAsync(appUser.Email); + if (employee == null) + { + return NoContent(); + } + + List regions = new List(); + if (string.IsNullOrEmpty(regionCode)) + { + var userDept = employee.Departments.FirstOrDefault(x => x.Role == Role.RegionalManager); + if (userDept == null) + { + return Unauthorized("No User-Dept for region found."); + } + + regions.Add(Region.FindByCode(userDept.Department.RegionCode)); + } + else if (regionCode == "ALL") + { + regions = Region.Regions; + } + else + { + regions.Add(Region.FindByCode(regionCode)); + } + + if (!regions.Any()) + { + return NoContent(); + } + + var regionProgress = new List(); + foreach (var region in regions) + { + var result = + await _dashboardService.GetRegionProgressForMonthRangeAsync(region, monthRange); + regionProgress.Add(result); + } + + foreach (var region in regionProgress) + { + region?.TransformForView(); + } + + return Ok(regionProgress); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("businessLine", Name = "GetBusinessLineProgress")] + [RequirePermission("VIEW_DASHBOARD,VIEW_PERFORMANCE_FOR_BUSINESS_LINE")] + public async Task GetBusinessLineProgress([FromQuery] int fromMonth, + [FromQuery] int fromYear, + [FromQuery] int toMonth, [FromQuery] int toYear, [FromQuery] string businessLineCode) + { + try + { + var monthRange = new MonthRange(fromYear, fromMonth, toYear, toMonth); + var appUser = _currentUser.GetCurrentUser(); + + if (appUser?.Email == null) + { + return Unauthorized("No AD User Found."); + } + + var employee = await _userService.GetUserByAdEmailAsync(appUser.Email); + if (employee == null) + { + return NoContent(); + } + + List bls = new List(); + if (string.IsNullOrEmpty(businessLineCode)) + { + var userDept = employee.FindDepartmentForRole(Role.BusinessLineManager); + if (userDept == null) + { + return Unauthorized("No User-Dept for business line found."); + } + + bls.Add(BusinessLine.FindByCode(userDept.Department.BusinessLineCode)); + } + else if (businessLineCode == "ALL") + { + bls = BusinessLine.BusinessLines; + } + else + { + bls.Add(BusinessLine.FindByCode(businessLineCode)); + } + + if (!bls.Any()) + { + return NoContent(); + } + + var blProgress = new List(); + foreach (var bl in bls) + { + var result = + await _dashboardService.GetBusinessLineProgressForMonthRangeAsync(bl, monthRange); + blProgress.Add(result); + } + + foreach (var bl in blProgress) + { + bl?.TransformForView(); + } + + return Ok(blProgress); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost] + public async Task CreateActivity([FromBody] Activity activity) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + var result = await _activityService.SaveActivityAsync(activity); + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}."); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost("StateChange")] + [HttpPost("ManualStateChange")] + public async Task ManuallyUpdateActivityState([FromBody] ActivityStateChange activityStateChange) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + var appUser = _currentUser.GetCurrentUser(); + if (appUser?.Email == null) + { + return Unauthorized("No AD User Found."); + } + + var employee = await _userService.GetUserByAdEmailAsync(appUser.Email); + + if (employee == null) + { + return Unauthorized("No AD User Found."); + } + + await _activityService.ValidateCanApplyActivityStateChange(employee, activityStateChange); + + var result = await _activityService.ApplyStateChange(activityStateChange); + + result?.TransformForView(); + + return Ok(result); + } + catch (BadActivityStateChangeException ex) + { + return BadRequest(ex.Message); + } + catch (NotAuthorizedException ex) + { + return StatusCode(403, ex.Message); + } + catch (NotFoundException) + { + return BadRequest("Activity Not Found."); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}."); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("{activityId}/StateChanges")] + public async Task GetActivityStateChanges([FromRoute] int activityId) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + var result = await _activityService.GetActivityStateChanges(activityId); + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}."); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("{activityId:int}/DetailChanges")] + public async Task GetActivityDetailChanges([FromRoute] int activityId) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + var activity = await _activityService.GetActivity(activityId); + var result = await _activityDetailService.GetAllActivityDetailUpdates(activity.Detail.Id); + if (result == null || !result.Any()) + { + NoContent(); + } + return Ok(result); + } + catch (NotFoundException) + { + return NotFound($"No activity with Id {activityId} could be found"); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}."); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("{activityId}/AvailableCommands")] + public async Task GetActivityAuthorizedCommands([FromRoute] int activityId, [FromQuery] string employeeId) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + var appUser = _currentUser.GetCurrentUser(); + if (appUser?.Email == null) + { + return Unauthorized("No AD User Found."); + } + + var employee = await _userService.GetUserByAdEmailAsync(appUser.Email); + if (!string.IsNullOrWhiteSpace(employeeId)) + { + employee = await _userService.GetUserByEmployeeIdAsync(employeeId); + } + + if (employee == null) + { + return Unauthorized("No AD User Found."); + } + var result = await _activityService.GetActivityAuthorizedCommands(employee, activityId); + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}."); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("{activityId}")] + public async Task Get([FromRoute] int activityId) + { + try + { + var result = await _activityService.GetActivity(activityId); + + result?.TransformForView(); + + return Ok(result); + } + catch (NotFoundException) + { + return BadRequest("Activity Not Found."); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}."); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost("RequestTransfer")] + [RequirePermission("TRANSFER_ACTIVITIES_BETWEEN_DEPARTMENTS, TRANSFER_ACTIVITIES_BETWEEN_ASSOCIATES")] + public async Task RequestTransfer([FromBody] ActivityTransferRequest transferRequest) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + var result = await _activityService.RequestTransfer(transferRequest); + + result?.TransformForView(); + + return Ok(result); + } + catch (NotFoundException) + { + return BadRequest("Activity Not Found."); + } + catch (BadActivityTransferActionException ex) + { + return BadRequest(ex.Message); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}."); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost("RequestTransferToOwnDepartment")] + public async Task RequestTransferToOwnDepartment([FromBody] ActivityTransferRequest transferRequest) + { + try + { + var appUser = _currentUser.GetCurrentUser(); + + if (appUser?.Email == null) + { + return Unauthorized("No AD User Found."); + } + + var employee = await _userService.GetUserByAdEmailAsync(appUser.Email); + if (employee == null) + { + return NoContent(); + } + + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + var result = await _activityService.TransferToOwnDepartment(transferRequest, employee); + + result?.TransformForView(); + + return Ok(result); + } + catch (NotFoundException) + { + return BadRequest("Activity Not Found."); + } + catch (BadActivityTransferActionException ex) + { + return BadRequest(ex.Message); + } + catch (NotAuthorizedException ex) + { + return Unauthorized(ex.Message); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}."); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost("ApproveTransfer")] + [RequirePermission("TRANSFER_ACTIVITIES_BETWEEN_DEPARTMENTS, TRANSFER_ACTIVITIES_BETWEEN_ASSOCIATES")] + public async Task ApproveTransfer([FromBody] ActivityTransferApprovalRequest approvalRequest) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + var appUser = _currentUser.GetCurrentUser(); + if (appUser?.Email == null) + { + return Unauthorized("No AD User Found."); + } + + var employee = await _userService.GetUserByAdEmailAsync(appUser.Email); + if (employee == null) + { + return Unauthorized("No AD User Found."); + } + + var result = await _activityService.ApproveTransfer(employee, approvalRequest); + + result?.TransformForView(); + + return Ok(result); + } + catch (NotFoundException) + { + return BadRequest("Activity Transfer Not Found."); + } + catch (NotAuthorizedException ex) + { + return StatusCode(403, ex.Message); + } + catch (BadActivityTransferActionException ex) + { + return BadRequest(ex.Message); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}."); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("{activityId}/RequestedTransferAction")] + public async Task GetTransferAction([FromRoute] int activityId) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + var result = await _activityService.GetRequestedTransfer(activityId); + + result?.TransformForView(); + + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}."); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("")] + [Obsolete("Added here for legacy client support, please use Paginated version")] + public async Task Get() + { + try + { + var result = await _activityService.GetActivitiesAsync(); + + foreach (var activity in result) + { + activity?.TransformForView(); + } + + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("matchActivities")] + [Obsolete("Moved to Processing Controller, please use endpoint QueueNightlyIncentiveProcess/UpdateReferralStatusProcess. Date: 1/1/2025")] + public async Task GetMatchActivityTemp() + { + try + { + var results = await _activityService.MatchOrExpireReferrals(); + return Ok(results); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("updateEStatementReferrals")] + [Obsolete("Moved to Processing Controller, please use endpoint QueueNightlyIncentiveProcess/UpdateEStatementProcess. Date: 1/1/2025")] + public async Task GetDeliveryMethodsForEStatements() + { + try + { + var results = await _eStatementService.UpdateOrExpireEStatementReferral(); + return Ok(results); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("RunDailyManualReviewRequery")] + [Obsolete("Moved to Processing Controller, please use endpoint QueueNightlyIncentiveProcess/UpdateAccountDecisionProcess. Date: 1/1/2025")] + public async Task RunDailyManualReviewRequery() + { + try + { + var result = await _activityDecisionService.UpdateAndDecideManualReviewActivities(); + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}."); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("RunDailyLICStationRequery")] + [Obsolete("Moved to Processing Controller, please use endpoint QueueNightlyIncentiveProcess/UpdateLICSalesProcess. Date: 1/1/2025")] + public async Task RunDailyLICRequery() + { + try + { + var result = await _activityDecisionService.UpdateAndDecideLicReferralActivities(); + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}."); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("RunDailyDirectDepositRequery")] + [Obsolete("Moved to Processing Controller, please use endpoint QueueNightlyIncentiveProcess/UpdateDirectDepositReferralsProcess. Date: 1/1/2025")] + public async Task RunDailyDirectDepositRequery() + { + try + { + var result = await _activityDecisionService.UpdateAndDecideDirectDepositPendingReferralActivities(); + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}."); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("RunDecisionService")] + [Obsolete("Moved to Processing Controller, please use endpoint QueueNightlyIncentiveProcess/UpdateAccountDecisionProcess. Date: 1/1/2025")] + public async Task RunDecisionService() + { + try + { + Log.Information("RunDecisionService was forced"); + var decisioned = await _activityDecisionService.DecisionActivityByState(ActivityState.Created); + Log.Information($"Auto-decisioned {decisioned.Count} activities in Created state"); + decisioned = await _activityDecisionService.DecisionActivityByState(ActivityState.ManualReview); + Log.Information($"Auto-decisioned {decisioned.Count} activities in Manual Review state"); + decisioned = await _activityDecisionService.UpdateAndDecideManualReviewActivities(); + Log.Information($"Updated and Auto-Decisioned {decisioned.Count} Activities"); + decisioned = await _activityDecisionService.UpdateAndDecideDpEligibleLoanActivities(); + Log.Information($"Updated and Auto-Decisioned {decisioned.Count} Debt Protection Eligible Loan Activities"); + return Ok(); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}."); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/ActivityImportController.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/ActivityImportController.cs new file mode 100644 index 0000000..68eafdc --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/ActivityImportController.cs @@ -0,0 +1,147 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.FeatureManagement.Mvc; +using LCU.API.Internal.Operations.IncentiveApi.Services.QueueServices; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; + +namespace LCU.API.Internal.Operations.IncentiveApi.Controllers; + +[FeatureGate(FeatureFlags.EnableControllers)] +[ApiController] +[Route("api/[controller]")] +public class ActivityImportController : ControllerBase +{ + private readonly IQueueCreditCardImportService _queueCreditCardImportService; + private readonly IQueueIndirectVehicleImportService _queueIndirectVehicleImportService; + private readonly IQueueActionCreditCardService _queueActionCreditCardService; + private readonly IQueueActionVehicleService _queueActionVehicleService; + private readonly IQueueAlliedGapMbpService _queueAlliedGapMbpService; + + public ActivityImportController(IQueueCreditCardImportService queueCreditCardImportService, + IQueueIndirectVehicleImportService queueIndirectVehicleImportService, IQueueActionCreditCardService queueActionCreditCardService, + IQueueActionVehicleService queueActionVehicleService, IQueueAlliedGapMbpService queueAlliedGapMbpService) + { + _queueCreditCardImportService = queueCreditCardImportService; + _queueIndirectVehicleImportService = queueIndirectVehicleImportService; + _queueActionCreditCardService = queueActionCreditCardService; + _queueActionVehicleService = queueActionVehicleService; + _queueAlliedGapMbpService = queueAlliedGapMbpService; + } + + [HttpPost("QueueCreditCardImportRequest")] + [ProducesResponseType(202)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task QueueCreditCardImportRequest([FromBody] ImportCreditCardSalesRequest request) + { + try + { + await _queueCreditCardImportService.QueueCreditCardSalesImport(request); + + return Accepted(); + } + catch (ServiceException exception) + { + return StatusCode(StatusCodes.Status500InternalServerError, exception.Message); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost("QueueIndirectVehicleImportRequest")] + [ProducesResponseType(202)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task QueueIndirectVehicleImportRequest([FromBody] ImportIndirectVehicleSalesRequest request) + { + try + { + await _queueIndirectVehicleImportService.QueueIndirectVehicleSalesImport(request); + + return Accepted(); + } + catch (ServiceException exception) + { + return StatusCode(StatusCodes.Status500InternalServerError, exception.Message); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost("QueueActionCreditCardRequest")] + [ProducesResponseType(202)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task QueueActionCreditCardRequest([FromBody] ActionCreditCardRequest request) + { + try + { + await _queueActionCreditCardService.QueueActionCreditCardProcess(request); + + return Accepted(); + } + catch (ServiceException exception) + { + return StatusCode(StatusCodes.Status500InternalServerError, exception.Message); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost("QueueActionVehicleRequest")] + [ProducesResponseType(202)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task QueueActionVehicleRequest([FromBody] ActionVehicleRequest request) + { + try + { + await _queueActionVehicleService.QueueActionVehicleProcess(request); + + return Accepted(); + } + catch (ServiceException exception) + { + return StatusCode(StatusCodes.Status500InternalServerError, exception.Message); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost("QueueAlliedGapMbpRequest")] + [ProducesResponseType(202)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task QueueAlliedGapMbpRequest([FromBody] AlliedGapMbpRequest request) + { + try + { + await _queueAlliedGapMbpService.QueueGapMbpProcess(request); + + return Accepted(); + } + catch (ServiceException exception) + { + return StatusCode(StatusCodes.Status500InternalServerError, exception.Message); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/CommissionController.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/CommissionController.cs new file mode 100644 index 0000000..008fc8f --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/CommissionController.cs @@ -0,0 +1,64 @@ +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.FeatureManagement.Mvc; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Controllers; + +[FeatureGate(FeatureFlags.EnableControllers)] +[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] +[Route("api/[controller]")] +[ApiController] +public class CommissionController : ControllerBase +{ + private readonly ICommissionService _commissionService; + + public CommissionController(ICommissionService commissionService) + { + _commissionService = commissionService; + } + + [HttpGet("")] + public async Task Get() + { + try + { + var results= await _commissionService.GetCommissionsAsync(); + return Ok(results); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPut("")] + public async Task SetCommissions(List commissions) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + await _commissionService.SaveCommissionsAsync(commissions); + return NoContent(); + } + catch (CommissionExistsException ex) + { + return Conflict(ex.Message); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}."); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/DeadActivityController.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/DeadActivityController.cs new file mode 100644 index 0000000..a7652e0 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/DeadActivityController.cs @@ -0,0 +1,88 @@ +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.FeatureManagement.Mvc; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace LCU.API.Internal.Operations.IncentiveApi.Controllers; + +[FeatureGate(FeatureFlags.EnableControllers)] +[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] +[ApiController] +[Route("api/[controller]")] +public class DeadActivityController: ControllerBase +{ + private readonly IDeadActivityService _deadActivityService; + public DeadActivityController(IDeadActivityService deadActivityService) + { + _deadActivityService = deadActivityService; + } + + [HttpGet("paged")] + [ProducesResponseType(typeof(PagedResponse), 200)] + public async Task Get([FromQuery] DeadActivityFilter filter) + { + try + { + var searchResult = await _deadActivityService.ListByFilterAsync(filter); + + foreach (var activity in searchResult.Items) + { + activity?.TransformForView(); + } + + var result = new PagedResponse(searchResult, filter, Url, Request); + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("{activityId}")] + public async Task Get([FromRoute] int activityId) + { + try + { + var result = await _deadActivityService.GetDeadActivity(activityId); + result?.TransformForView(); + return Ok(result); + } + catch (NotFoundException) + { + return BadRequest("DeadActivity Not Found."); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}."); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpDelete("{activityId}")] + public async Task Delete([FromRoute] int activityId, [FromQuery] int activityRef = -1) + { + try + { + var result = await _deadActivityService.ProcessDeadActivity(activityId, activityRef); + result?.TransformForView(); + return Ok(result); + } + catch (NotFoundException) + { + return BadRequest("DeadActivity Not Found."); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}."); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/DepartmentController.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/DepartmentController.cs new file mode 100644 index 0000000..ac9a452 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/DepartmentController.cs @@ -0,0 +1,165 @@ +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.FeatureManagement.Mvc; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; + +namespace LCU.API.Internal.Operations.IncentiveApi.Controllers; + +[FeatureGate(FeatureFlags.EnableControllers)] +[ApiController] +[Route("api/[controller]")] +[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] +public class DepartmentController : ControllerBase +{ + private readonly IDepartmentService _departmentService; + private readonly IUserService _userService; + private readonly ICurrentUserService _currentUser; + + public DepartmentController(IDepartmentService departmentService, IUserService userService, ICurrentUserService currentUser) + { + _departmentService = departmentService; + _userService = userService; + _currentUser = currentUser; + } + + [HttpGet("", Name = "GetAllDepartments")] + public async Task GetDepartments([FromQuery] bool? canEditDepartmentGoals, [FromQuery] bool? canEditPersonnelGoals) + { + try + { + if (canEditDepartmentGoals != null || canEditPersonnelGoals != null) + { + var appUser = _currentUser.GetCurrentUser(); + if (appUser?.Email == null) + { + return Unauthorized("No AD User Found."); + } + + var employee = await _userService.GetUserByAdEmailAsync(appUser.Email); + + if (employee == null) + { + return Unauthorized("No AD User Found."); + } + + if (canEditDepartmentGoals.HasValue) + { + return Ok(await _departmentService.GetHierarchicalDepartmentsForEmployeeAsync(employee, false)); + } + if (canEditPersonnelGoals.HasValue) + { + return Ok( await _departmentService.GetHierarchicalDepartmentsForEmployeeAsync(employee, true)); + } + } + var result = await _departmentService.GetAllDepartmentsAsync(); + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("{departmentId:int}", Name = "GetDepartmentById")] + public async Task GetDepartmentById([FromRoute] int departmentId) + { + try + { + var result = await _departmentService.GetDepartmentByIdAsync(departmentId); + return Ok(result); + } + catch (NotFoundException) + { + return NotFound($"Could not find Department with that Id"); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost("", Name = "CreateNewDepartment")] + public async Task CreateDepartment([FromBody] Department newDepartment) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + if (newDepartment.Id != null) + { + return BadRequest("Id Field Cannot Be Assigned"); + } + + var result = await _departmentService.SaveDepartmentAsync(newDepartment); + return (Ok(result)); + } + catch (DuplicateDataException) + { + return BadRequest($"Department Name Must Be Unique."); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPut("{id:int}", Name = "UpdateExistingDepartment")] + public async Task UpdateDepartment([FromBody] Department newDepartment, [FromRoute] int id) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + var result = await _departmentService.UpdateDepartmentAsync(newDepartment, id); + return (Ok(result)); + } + catch (DuplicateDataException) + { + return BadRequest($"Department Name Must Be Unique."); + } + catch (NotFoundException) + { + return NotFound($"Could not find Department with that Id"); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("{id:int}/details", Name = "GetDepartmentDetails")] + public async Task GetDepartmentDetails([FromRoute] int id) + { + try + { + var details = await _departmentService.GetDepartmentDetailsAsync(id); + return Ok(details); + } + catch (NotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/EmployeeController.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/EmployeeController.cs new file mode 100644 index 0000000..8dc1f6b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/EmployeeController.cs @@ -0,0 +1,366 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.FeatureManagement.Mvc; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.API.Internal.Operations.IncentiveApi.Extensions; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; +using LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; +using LCU.API.Internal.Operations.IncentiveApi.Authorization; +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.API.Internal.Operations.IncentiveApi.Services.Dayforce; + +namespace LCU.API.Internal.Operations.IncentiveApi.Controllers; + +[FeatureGate(FeatureFlags.EnableControllers)] +[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] +[ApiController] +[Route("api/[controller]")] +public class EmployeeController : ControllerBase +{ + private readonly ICurrentUserService _currentUserService; + private readonly IDayForceSyncService _dayForceSyncService; + private readonly IUserService _userService; + + public EmployeeController( + ICurrentUserService currentUserService, + IDayForceSyncService dayForceSyncService, + IUserService userService + ) + { + _currentUserService = currentUserService; + _dayForceSyncService = dayForceSyncService; + _userService = userService; + } + + [HttpGet("")] + public async Task GetAllUsers([FromQuery] string query, [FromQuery] string role) + { + try + { + var searchRequest = new UserSearchRequest + { + Query = query, + Role = role + }; + var result = await _userService.GetAllUsersAsync(searchRequest); + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("terminated")] + [RequirePermission("GENERATE_PAYOUT_FILES")] + public IActionResult GetTerminatedUsers([FromQuery] int month, [FromQuery] int year) + { + try + { + var monthRange = new MonthRange(year, month); + var result = _userService.GetTerminatedUsersAsync(monthRange); + return Ok(result); + } + catch (ArgumentException ex) + { + Log.Error(ex, $"Bad Request in {GetType().Name}"); + return BadRequest(ex.Message); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("resolve")] + public async Task ResolveUser( + [FromQuery] string employeeId = null, + [FromQuery] string email = null + ) + { + try + { + var count = 0; + if (!string.IsNullOrWhiteSpace(employeeId)) + { + count++; + } + + if (!string.IsNullOrWhiteSpace(email)) + { + count++; + } + + if (count > 1) + { + return BadRequest("Please provide only one query argument."); + } + + var result = await _userService.FindUserAsync(employeeId, email); + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("me")] + public async Task GetCurrentEmployee() + { + try + { + var currentUser = _currentUserService.GetCurrentUser(); + if (currentUser?.Email == null) + { + Log.Warning("Failed To Get Current User From Auth Context"); + return Unauthorized("No AD User Found."); + } + + var result = await _userService.GetUserByAdEmailAsync(currentUser.Email); + if (result == null) + { + Log.Warning($"Failed To Get Current User From Email: {currentUser.Email}"); + return NoContent(); + } + + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("{employeeId}")] + [RequirePermission("ASSIGN_PERMISSIONS_TO_ASSOCIATES,ASSIGN_ASSOCIATES_TO_DEPARTMENT")] + public async Task GetEmployeeById(string employeeId = null) + { + try + { + if (employeeId == null) + { + return BadRequest("no_employee_id"); + } + + var result = await _userService.GetUserByEmployeeIdAsync(employeeId); + if (result == null) + { + return NoContent(); + } + + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [RequirePermission("ASSIGN_ASSOCIATES_TO_DEPARTMENT")] + [HttpPost("{employeeId}/AssignDept", Name = "AssignDept")] + public async Task AssignDepartment( + string employeeId, + [FromBody] DepartmentAssignmentRequest assignment + ) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState.GetErrorMessages()); + } + + try + { + var currentUser = _currentUserService.GetCurrentUser(); + if (currentUser?.Email == null) + { + return Unauthorized("No AD User Found."); + } + + var employee = await _userService.GetUserByAdEmailAsync(currentUser.Email); + if (UserService.ValidateChangePermission(employee.EmployeeId, employeeId)) + { + return StatusCode(403, "Not allowed to assign yourself to a department."); + } + + await _userService.AssignDepartments(employeeId, assignment); + return (Ok()); + } + catch (NotFoundException ex) + { + return NotFound(ex.Message); + } + catch (BadDepartmentAssignmentException ex) + { + return BadRequest(ex.Message); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPut("{employeeId}/UpdatePerms", Name = "UpdatePerms")] + public async Task AssignPermission( + string employeeId, + [FromBody] AdditionalPermissionRequest request + ) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState.GetErrorMessages()); + } + + try + { + var response = await _userService.UpdatePermissions(employeeId, request); + return Ok(response); + } + catch (NotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [RequirePermission("ASSIGN_ASSOCIATES_TO_DEPARTMENT")] + [HttpPost("{employeeId}/UpdateDept/{assignmentId:int}", Name = "UpdateDept")] + public async Task UpdateDepartmentAssignment( + string employeeId, + int assignmentId, + [FromBody] DepartmentUpdateRequest request + ) + { + try + { + var currentUser = _currentUserService.GetCurrentUser(); + if (currentUser?.Email == null) + { + return Unauthorized("No AD User Found."); + } + + var employee = await _userService.GetUserByAdEmailAsync(currentUser.Email); + if (UserService.ValidateChangePermission(employee.EmployeeId, employeeId)) + { + return StatusCode(403, "Not allowed to terminate yourself from a department."); + } + + await _userService.UpdateDepartmentAssignment(request); + return (Ok()); + } + catch (NotFoundException ex) + { + return NotFound(ex.Message); + } + catch (BadDepartmentAssignmentException ex) + { + return BadRequest(ex.Message); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("GetHcmSystemUsers")] + [RequirePermission("MANAGE_EMPLOYEE_DETAILS")] + public async Task GetHcmSystemUsers(string email = "", string employeeId = "", string fullName = "") + { + try + { + var result = await _dayForceSyncService.GetMatchingHcmUsers(email, employeeId, fullName); + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost("UpdateEmployeeDetails", Name = "UpdateEmployeeDetails")] + [RequirePermission("MANAGE_EMPLOYEE_DETAILS")] + public async Task UpdateEmployeeDetails([FromBody] EmployeeDetailsUpdate request) + { + try + { + var currentUser = _currentUserService.GetCurrentUser(); + if (currentUser?.Email == null) + { + return Unauthorized("No AD User Found."); + } + + var result = await _userService.GetUserByAdEmailAsync(request.OldEmail); + if (result == null) + { + return NoContent(); + } + + var updatedUserResult = await _dayForceSyncService.UpdateEmployeeDetails(request); + + if (updatedUserResult.UpdatedSuccessfully) + { + return Ok(updatedUserResult); + } + + return BadRequest(updatedUserResult); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("SyncEmployeeIds")] + [RequirePermission("MANAGE_EMPLOYEE_DETAILS")] + public async Task SyncEmployeeIds() + { + try + { + var result = await _userService.SyncActiveUserIds(); + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost("sync", Name = "ForceRefresh")] + public async Task SyncDayForce() + { + try + { + Log.Information("DayForce Sync was forced"); + var request = new DayforceProcessRequest(); + await _dayForceSyncService.SyncDayForceUsers(request); + Log.Information("DayForce forced Sync has completed"); + return Ok(); + } + catch (Exception ex) + { + Log.Information("DayForce forced Sync has failed"); + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/GoalController.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/GoalController.cs new file mode 100644 index 0000000..7c041a2 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/GoalController.cs @@ -0,0 +1,268 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.FeatureManagement.Mvc; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; +using LCU.API.Internal.Operations.IncentiveApi.Models.GoalDispersal; +using LCU.API.Internal.Operations.IncentiveApi.Authorization; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; + +namespace LCU.API.Internal.Operations.IncentiveApi.Controllers; + +[FeatureGate(FeatureFlags.EnableControllers)] +[ApiController] +[Route("api/[controller]")] +[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] +public class GoalController : ControllerBase +{ + private readonly IGoalService _goalService; + private readonly IGoalDispersalService _goalDispersalService; + private readonly IUserService _userService; + private readonly ICurrentUserService _currentUser; + + public GoalController(IGoalService goalService, IGoalDispersalService goalDispersalService, ICurrentUserService currentUser, IUserService userService) + { + _goalService = goalService; + _currentUser = currentUser; + _userService = userService; + _goalDispersalService = goalDispersalService; + } + + [HttpGet("", Name = "GetGoals")] + public async Task GetGoals([FromQuery] int fromMonth, [FromQuery] int fromYear, + [FromQuery] int toMonth, [FromQuery] int toYear, [FromQuery] int departmentId, + [FromQuery] bool includePersonnel = true) + { + try + { + var monthRange = new MonthRange(fromYear, fromMonth, toYear, toMonth); + var query = new GoalSearchRequest() + { + DateRange = monthRange, + departmentId = departmentId, + IncludePersonnel = includePersonnel + }; + + var result = await _goalService.GetGoalsAsync(query); + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + + [HttpGet("department/", Name = "GetAllDepartmentsGoals")] + public async Task GetAllDepartmentsGoals([FromQuery] int year) + { + try + { + var searchYear = year > 0 ? year : DateTime.Now.Year; + var result = await _goalService.GetAllAnnualDepartmentGoalsAsync(searchYear); + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("department/{departmentId}", Name = "GetDepartmentGoals")] + public async Task GetDepartmentGoals(int departmentId, [FromQuery] int year) + { + try + { + var searchYear = year > 0 ? year : DateTime.Now.Year; + var result = await _goalService.GetAnnualDepartmentGoalsAsync(departmentId, searchYear); + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("personnel/{departmentId}", Name = "GetPersonnelGoals")] + public async Task GetPersonnelGoals(int departmentId, [FromQuery] int month, + [FromQuery] int year, [FromQuery] bool includeAllPersonnel = false) + { + try + { + var searchYear = year > 0 ? year : DateTime.Now.Year; + var searchMonth = month > 0 ? month : DateTime.Now.Month; + var searchRequest = new DepartmentPersonnelSearchRequest() + { + DepartmentId = departmentId, + Month = searchMonth, + Year = searchYear, + IncludeAllPersonnel = includeAllPersonnel + }; + var result = await _goalService.GetMonthlyPersonnelGoalsAsync(searchRequest); + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost("personnel/{departmentId}/disperse", Name = "DisperseByExtrapolation")] + public async Task GetPersonnelGoals(int departmentId, [FromBody] ExtrapolateGoalDispersalRequest request) + { + try + { + var result = await _goalDispersalService.DisperseGoalsByExtrapolatingLastMonth(departmentId, request.Month, request.Year); + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("me", Name = "GetCurrentUserGoals")] + [RequirePermission("VIEW_DASHBOARD")] + public async Task GetCurrentUserGoals([FromQuery] int fromMonth, [FromQuery] int fromYear, + [FromQuery] int toMonth, [FromQuery] int toYear) + { + try + { + var monthRange = new MonthRange(fromYear, fromMonth, toYear, toMonth); + var appUser = _currentUser.GetCurrentUser(); + + if (appUser?.Email == null) + { + return Unauthorized("No AD User Found."); + } + + var employee = await _userService.GetUserByAdEmailAsync(appUser.Email); + if (employee == null) + { + return NoContent(); + } + + var result = await _goalService.GetEmployeeGoalsForMonthRangeAsync(employee, monthRange); + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("me/departments", Name = "GetCurrentUserDepartmentsGoals")] + [RequirePermission("VIEW_DASHBOARD,VIEW_PERFORMANCE_FOR_DEPARTMENT")] + public async Task GetCurrentUserDepartmentsGoals([FromQuery] int fromMonth, + [FromQuery] int fromYear, + [FromQuery] int toMonth, [FromQuery] int toYear) + { + try + { + var monthRange = new MonthRange(fromYear, fromMonth, toYear, toMonth); + var appUser = _currentUser.GetCurrentUser(); + + if (appUser?.Email == null) + { + return Unauthorized("No AD User Found."); + } + + var employee = await _userService.GetUserByAdEmailAsync(appUser.Email); + if (employee == null) + { + return NoContent(); + } + + var result = await _goalService.GetEmployeeDepartmentsGoalsForMonthRangeAsync(employee, monthRange); + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost] + public async Task SetGoalForAudienceAndTimePeriod(GoalChange goalChange) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + var result = await _goalService.SaveGoalAsync(goalChange); + return Created($"{result.Select(x => x.Id)}", result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}."); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost("multi")] + public async Task SetGoalsForAudienceAndTimePeriod(List goalChanges) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + var result = await _goalService.SaveGoalRangeAsync(goalChanges); + return Created($"{result.Count}", result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}."); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost("DisperseAll")] + public async Task DisperseAll(GoalDispersalRequest request) + { + try + { + await _goalDispersalService.ExecuteMonthStartDispersal(request); + return Ok(); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}."); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost("personnel/CustomWeightedDispersal")] + public async Task CustomWeightedDispersal([FromBody] CustomWeightedDispersalRequest request) + { + try + { + var result = await _goalDispersalService.CustomWeightedDispersal(request); + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}."); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/HostedServicesController.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/HostedServicesController.cs new file mode 100644 index 0000000..b7a8935 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/HostedServicesController.cs @@ -0,0 +1,59 @@ +using LCU.API.Internal.Operations.IncentiveApi.HostedServices; +using LCU.Library.Internal.Exceptions; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace LCU.API.Internal.Operations.IncentiveApi.Controllers +{ + public class HostedServicesController : ControllerBase + { + private readonly IApplicationDbIntegrityHostedService _applicationDbIntegrityHostedServiceStarter; + + public HostedServicesController(IApplicationDbIntegrityHostedService applicationDbIntegrityHostedServiceStarter) + { + _applicationDbIntegrityHostedServiceStarter = applicationDbIntegrityHostedServiceStarter; + } + + [HttpPost("StartApplicationDbIntegrity", Name = "StartApplicationDbIntegrity")] + [ProducesResponseType(200)] + [ProducesResponseType(500)] + public async Task StartApplicationDbIntegrity() + { + try + { + await _applicationDbIntegrityHostedServiceStarter.StartAsync(); + return Ok(); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost("StopApplicationDbIntegrity", Name = "StopApplicationDbIntegrity")] + [ProducesResponseType(200)] + [ProducesResponseType(500)] + public async Task StopApplicationDbIntegrity() + { + try + { + await _applicationDbIntegrityHostedServiceStarter.StopAsync(); + return Ok(); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/PayoutController.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/PayoutController.cs new file mode 100644 index 0000000..a30a23e --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/PayoutController.cs @@ -0,0 +1,261 @@ +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.API.Internal.Operations.IncentiveApi.Services.Payout; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.FeatureManagement.Mvc; +using LCU.API.Internal.Operations.IncentiveApi.Authorization; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Controllers; + +[FeatureGate(FeatureFlags.EnableControllers)] +[ApiController] +[Route("api/[controller]")] +[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] +public class PayoutController : ControllerBase +{ + private readonly IPayoutService _payoutService; + private readonly IUserService _userService; + private readonly IActivityService _activityService; + private readonly ICurrentUserService _currentUser; + + public PayoutController(IPayoutService payoutService, IUserService userService, ICurrentUserService currentUser, IActivityService activityService) + { + _payoutService = payoutService; + _userService = userService; + _currentUser = currentUser; + _activityService = activityService; + } + + [HttpGet("{employeeId}", Name = "GetEmployeePayout")] + [RequirePermission("VIEW_DASHBOARD")] + public async Task GetEmployeePayout(string employeeId, [FromQuery] int year, + [FromQuery] int month) + { + try + { + var searchYear = year > 0 ? year : DateTime.Now.Year; + var searchMonth = month > 0 ? month : DateTime.Now.Month; + + var employee = await _userService.GetUserByEmployeeIdAsync(employeeId); + if (employee == null) + { + return NoContent(); + } + + var result = + await _payoutService.RetrieveCombinedDepartmentPayouts(employee, new MonthRange(searchYear, searchMonth)); + return Ok( result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("me", Name = "GetCurrentUserPayout")] + [RequirePermission("VIEW_DASHBOARD")] + public async Task GetCurrentUserPayout( [FromQuery] int year, [FromQuery] int month) + { + try + { + var searchYear = year > 0 ? year : DateTime.Now.Year; + var searchMonth = month > 0 ? month : DateTime.Now.Month; + + var appUser = _currentUser.GetCurrentUser(); + + if (appUser?.Email == null) + { + return Unauthorized("No AD User Found."); + } + + var employee = await _userService.GetUserByAdEmailAsync(appUser.Email); + if (employee == null) + { + return NoContent(); + } + + var result = + await _payoutService.CreateCombinedDepartmentPayouts(employee, new MonthRange(searchYear, searchMonth)); + + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("export", Name = "ExportMonthlyPayout")] + [RequirePermission("GENERATE_PAYOUT_FILES")] + public async Task ExportMonthlyPayout([FromQuery] int year, + [FromQuery] int month) + { + try + { + var searchYear = year > 0 ? year : DateTime.Today.Year; + var searchMonth = month > 0 ? month : DateTime.Today.Month; + + var appUser = _currentUser.GetCurrentUser(); + if (appUser?.Email == null) + { + return Unauthorized("No AD User Found."); + } + + var employee = await _userService.GetUserByAdEmailAsync(appUser.Email); + if (employee == null) + { + return NoContent(); + } + + var period = new DateTime(searchYear, searchMonth, 1); + var payouts = await _payoutService.RetrieveMonthlyIncentivePayouts(period); + return Ok(payouts); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("runMonthly", Name = "RunMonthlyPayout")] + [RequirePermission("GENERATE_PAYOUT_FILES")] + public async Task RunMonthlyPayout([FromQuery] int monthsBack = 0) + { + try + { + var appUser = _currentUser.GetCurrentUser(); + + if (appUser?.Email == null) + { + return Unauthorized("No AD User Found."); + } + + var employee = await _userService.GetUserByAdEmailAsync(appUser.Email); + if (employee == null) + { + return NoContent(); + } + + var request = new PayoutIncentiveRequest() + { + MonthsBack = monthsBack + }; + + var payouts = await _payoutService.CreateMonthlyIncentivePayouts(request); + + return Ok(payouts); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("runAccounting", Name = "RunMonthlyAccounting")] + [RequirePermission("GENERATE_PAYOUT_FILES")] + public async Task RunMonthlyAccounting([FromQuery] int monthsBack = 0) + { + try + { + var appUser = _currentUser.GetCurrentUser(); + + if (appUser?.Email == null) + { + return Unauthorized("No AD User Found."); + } + + var employee = await _userService.GetUserByAdEmailAsync(appUser.Email); + if (employee == null) + { + return NoContent(); + } + + var period = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); + period = period.AddMonths( -1 * monthsBack); + var range = new MonthRange(period); + var payouts = await _payoutService.CreateMonthlyAccountingPayouts(range); + + return Ok(payouts); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("getAccounting", Name = "GetMonthlyAccounting")] + [RequirePermission("GENERATE_PAYOUT_FILES")] + public async Task GetMonthlyAccounting([FromQuery] DateTime period) + { + try + { + var appUser = _currentUser.GetCurrentUser(); + + if (appUser?.Email == null) + { + return Unauthorized("No AD User Found."); + } + + var employee = await _userService.GetUserByAdEmailAsync(appUser.Email); + if (employee == null) + { + return NoContent(); + } + + var range = new MonthRange(period); + var payouts = await _payoutService.RetrieveMonthlyAccountingPayouts(range); + + return Ok(payouts); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("runMonthlySingle", Name = "RunMonthlyPayoutSingle")] + [RequirePermission("GENERATE_PAYOUT_FILES")] + public async Task RunMonthlyPayoutSingle([FromQuery] string employeeId, [FromQuery] int monthsBack = 0) + { + try + { + var appUser = _currentUser.GetCurrentUser(); + + if (appUser?.Email == null) + { + return Unauthorized("No AD User Found."); + } + + var employee = await _userService.GetUserByEmployeeIdAsync(employeeId); + if (employee == null) + { + return NoContent(); + } + + var period = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); + period = period.AddMonths( -1 * monthsBack); + var range = new MonthRange(period); + var payouts = await _payoutService.CreateCombinedDepartmentPayouts(employee, range); + + return Ok(payouts); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/ProcessingController.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/ProcessingController.cs new file mode 100644 index 0000000..2afe005 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/ProcessingController.cs @@ -0,0 +1,393 @@ +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.FeatureManagement.Mvc; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApi.Services.Allied; +using LCU.API.Internal.Operations.IncentiveApi.Services.Action; +using LCU.API.Internal.Operations.IncentiveApi.Models.Action; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.API.Internal.Operations.IncentiveApi.Models.Allied; +using LCU.API.Internal.Operations.IncentiveApi.Services.QueueServices; +using LCU.Library.Internal.Exceptions; +using System.Threading; + +namespace LCU.API.Internal.Operations.IncentiveApi.Controllers; + +[FeatureGate(FeatureFlags.EnableControllers)] +[ApiController] +[Route("api/[controller]")] +public class ProcessingController : Controller +{ + private readonly IActionCreditCardImportService _actionCreditCardImportService; + private readonly IActionVehicleImportService _actionVehicleImportService; + private readonly IAlliedGapMbpTransactionImportService _alliedTransactionImportService; + private readonly IApplicationDbIntegrityService _applicationDbIntegrityService; + private readonly IDnaActivitySyncService _dnaActivitySyncService; + private readonly IUserService _userService; + private readonly ICurrentUserService _currentUser; + private readonly IGapInsuranceActivityService _gapInsuranceActivityService; + private readonly IQueueIncentiveProcessesService _queueIncentiveProcessService; + private readonly IQueueNightlyIncentiveProcessesService _queueNightlyIncentiveProcessService; + private readonly IQueueReportingService _queueReportingService; + private readonly IQueuePayoutIncentiveService _queuePayoutIncentiveService; + private readonly IQueueGoalDispersalService _queueGoalDispersalService; + private readonly IQueueDayforceSyncService _queueDayforceSyncService; + + public ProcessingController( + IActionCreditCardImportService actionCreditCardImportService, + IActionVehicleImportService actionVehicleImportService, + IAlliedGapMbpTransactionImportService alliedTransactionImportService, + IApplicationDbIntegrityService applicationDbIntegrityService, + ICurrentUserService currentUserService, + IDnaActivitySyncService dnaActivitySyncService, + IUserService userService, + IGapInsuranceActivityService gapInsuranceActivityService, + IQueueIncentiveProcessesService queueIncentiveProcessService, + IQueueNightlyIncentiveProcessesService queueNightlyIncentiveProcessService, + IQueueReportingService queueReportingService, IQueuePayoutIncentiveService queuePayoutIncentiveService, + IQueueGoalDispersalService queueGoalDispersalService, IQueueDayforceSyncService queueDayforceSyncService + ) + { + _actionCreditCardImportService = actionCreditCardImportService; + _actionVehicleImportService = actionVehicleImportService; + _alliedTransactionImportService = alliedTransactionImportService; + _applicationDbIntegrityService = applicationDbIntegrityService; + _currentUser = currentUserService; + _dnaActivitySyncService = dnaActivitySyncService; + _userService = userService; + _gapInsuranceActivityService = gapInsuranceActivityService; + _queueIncentiveProcessService = queueIncentiveProcessService; + _queueNightlyIncentiveProcessService = queueNightlyIncentiveProcessService; + _queueReportingService = queueReportingService; + _queuePayoutIncentiveService = queuePayoutIncentiveService; + _queueGoalDispersalService = queueGoalDispersalService; + _queueDayforceSyncService = queueDayforceSyncService; + } + + [HttpPost("QueueIncentiveProcess")] + [ProducesResponseType(202)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task QueueIncentiveProcess([FromBody] IncentiveProcessRequest request) + { + try + { + await _queueIncentiveProcessService.QueueIncentiveProcess(request); + return Accepted(); + } + catch (ServiceException exception) + { + return StatusCode(StatusCodes.Status500InternalServerError, exception.Message); + } + catch (Exception exception) + { + Log.Error(exception, "Incentive Process Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost("QueueNightlyIncentiveProcess")] + [ProducesResponseType(202)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task QueueNightlyIncentiveProcess([FromBody] NightlyIncentiveProcessRequest request) + { + try + { + await _queueNightlyIncentiveProcessService.QueueNightlyIncentiveProcess(request); + return Accepted(); + } + catch (ServiceException exception) + { + return StatusCode(StatusCodes.Status500InternalServerError, exception.Message); + } + catch (Exception exception) + { + Log.Error(exception, "Nightly Incentive Process Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost("QueuePayoutProcess")] + [ProducesResponseType(202)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task QueuePayoutProcess([FromBody] PayoutIncentiveRequest request) + { + try + { + await _queuePayoutIncentiveService.QueuePayoutIncentiveProcess(request); + return Accepted(); + } + catch (ServiceException exception) + { + return StatusCode(StatusCodes.Status500InternalServerError, exception.Message); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost("QueueReportingRequest")] + [ProducesResponseType(202)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task QueueReportingRequest([FromBody] ReportingRequest request) + { + try + { + await _queueReportingService.QueueReportingProcess(request); + return Accepted(); + } + catch (ServiceException exception) + { + return StatusCode(StatusCodes.Status500InternalServerError, exception.Message); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost("QueueGoalDispersalSyncProcess")] + [ProducesResponseType(202)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task QueueGoalDispersalSyncProcess([FromBody] GoalDispersalRequest request) + { + try + { + await _queueGoalDispersalService.QueueGoalDispersalProcess(request); + return Accepted(); + } + catch (Exception ex) + { + Log.Information("Queue Goal Dispersal Process has failed"); + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError, ex.Message); + } + } + + [HttpPost("QueueDayforceSyncProcess")] + [ProducesResponseType(202)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task QueueDayforceSyncProcess([FromBody] DayforceProcessRequest request) + { + try + { + await _queueDayforceSyncService.QueueDayforceSyncProcess(request); + return Accepted(); + } + catch (Exception ex) + { + Log.Information("DayForce forced Sync has failed"); + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError, ex.Message); + } + } + + [HttpGet("RunAlliedImport")] + [Obsolete("Replaced with ActivityImport process, please use endpoint ActivityImport/QueueAlliedGapMbpRequest Date: 1/1/2025")] + public async Task RunAlliedImport([FromBody] AlliedGapMbpRequest request) + { + try + { + var appUser = _currentUser.GetCurrentUser(); + + if (appUser?.Email == null) + { + return Unauthorized("No AD User Found"); + } + + var employee = await _userService.GetUserByAdEmailAsync(appUser.Email); + if (employee == null) + { + Log.Information("No AppUser found"); + return NoContent(); + } + + Log.Information($"Importing transactions from Allied, kicked off manually"); + var imported = await _alliedTransactionImportService.ImportTransactions(request); + Log.Information($"Imported {imported.Count} transactions from Allied"); + + return Ok(); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("RunCreditCardImport")] + [Obsolete("Replaced with ActivityImport process, please use endpoint ActivityImport/QueueActionCreditCardRequest Date: 1/1/2025")] + public async Task RunCreditCardImport(ActionCreditCardRequest request) + { + try + { + var appUser = _currentUser.GetCurrentUser(); + + if (appUser?.Email == null) + { + return Unauthorized("No AD User Found"); + } + + var employee = await _userService.GetUserByAdEmailAsync(appUser.Email); + if (employee == null) + { + return NoContent(); + } + + Log.Information($"Importing credit cards, kicked off manually"); + var imported = await _actionCreditCardImportService.ImportTransactions("personal", request); + Log.Information($"Imported {imported.Count} credit cards - personal"); + imported = await _actionCreditCardImportService.ImportTransactions("business", request); + Log.Information($"Imported {imported.Count} credit cards - business"); + + return Ok(); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("RunVehicleImport")] + [Obsolete("Replaced with ActivityImport process, please use endpoint ActivityImport/QueueActionVehicleRequest Date: 1/1/2025")] + public async Task RunVehicleImport([FromBody] ActionVehicleRequest request) + { + try + { + var appUser = _currentUser.GetCurrentUser(); + + if (appUser?.Email == null) + { + return Unauthorized("No AD User Found"); + } + + var employee = await _userService.GetUserByAdEmailAsync(appUser.Email); + if (employee == null) + { + return NoContent(); + } + + Log.Information($"Importing vehicles, kicked off manually"); + var imported = await _actionVehicleImportService.ImportTransactions(request); + Log.Information($"Imported {imported.Count} vehicles"); + + return Ok(); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("RunDnaSyncQuery")] + [Obsolete("Replaced with Incentive Processes, please use QueueIncentiveProcesses. Date: 1/1/2025")] + public async Task RunDnaSyncQuery() + { + try + { + var appUser = _currentUser.GetCurrentUser(); + + if (appUser?.Email == null) + { + return Unauthorized("No AD User Found"); + } + + var employee = await _userService.GetUserByAdEmailAsync(appUser.Email); + if (employee == null) + { + return NoContent(); + } + + Log.Information("DNA Sync was forced"); + await _dnaActivitySyncService.SyncLatestActivitiesFromDna(); + return Ok(); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("MatchOrRemoveGapAccessories")] + [Obsolete("Moved to Nightly Processes Controller. Please use QueueNightlyIncentiveProcesses. Date: 1/1/2025")] + public async Task MatchOrRemoveGapAccessories( + [FromQuery] int fromMonth, + [FromQuery] int fromYear, + [FromQuery] int toMonth, + [FromQuery] int toYear + ) + { + try + { + var appUser = _currentUser.GetCurrentUser(); + + if (appUser?.Email == null) + { + return Unauthorized("No AD User Found"); + } + + var employee = await _userService.GetUserByAdEmailAsync(appUser.Email); + if (employee == null) + { + return NoContent(); + } + + var monthRange = new MonthRange(fromYear, fromMonth, toYear, toMonth); + Log.Information("MatchOrRemoveGapAccessories was forced"); + var updatedCount = await _gapInsuranceActivityService.MatchOrRemoveGapAccessories(monthRange); + Log.Information($"MatchOrRemoveGapAccessories updated {updatedCount} records"); + return Ok(); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("RunAppUserIdIntegrityMaintenance")] + [Obsolete("Moved to Nightly Processes Controller. Please use QueueNightlyIncentiveProcesses. Date: 1/1/2025")] + public async Task RunAppUserIdIntegrityMaintenance() + { + try + { + var appUser = _currentUser.GetCurrentUser(); + + if (appUser?.Email == null) + { + return Unauthorized("No AD User Found"); + } + + var employee = await _userService.GetUserByAdEmailAsync(appUser.Email); + if (employee == null) + { + return NoContent(); + } + + Log.Information("AppUserId Integrity Maintenance was forced"); + var token = new CancellationToken(); + await _applicationDbIntegrityService.AppUserIdIntegrityMaintenanceSucceeded(token); + return Ok(); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/SettingsController.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/SettingsController.cs new file mode 100644 index 0000000..173806f --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/SettingsController.cs @@ -0,0 +1,133 @@ +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.FeatureManagement.Mvc; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; + +namespace LCU.API.Internal.Operations.IncentiveApi.Controllers; + +[ApiController] +[FeatureGate(FeatureFlags.EnableControllers)] +[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] +[Route("api/[controller]")] +public class SettingsController : ControllerBase +{ + private readonly ICurrentUserService _currentUserService; + private readonly IDateTime _dateTimeService; + private readonly IDepartmentService _departmentService; + private readonly ISettingsService _settingsService; + private readonly IUserService _userService; + + public SettingsController( + ICurrentUserService currentUserService, + IDateTime dateTimeService, + IDepartmentService departmentService, + ISettingsService settingsService, + IUserService userService + ) + { + _currentUserService = currentUserService; + _dateTimeService = dateTimeService; + _departmentService = departmentService; + _settingsService = settingsService; + _userService = userService; + } + + [HttpGet("")] + [ProducesResponseType(200)] + public async Task Get() + { + try + { + var settings = new Settings(); + settings.Departments = await _departmentService.GetAllDepartmentsAsync(); + settings.Employees = await _userService.GetAllUserSummariesAsync(); + settings.NextBonusPeriod = _dateTimeService.getNextBonusPeriod(DateTime.Today); + + var currentUser = _currentUserService.GetCurrentUser(); + if (currentUser?.Email != null && currentUser.UserType() != UserType.System) + { + var appUser = await _userService.GetUserByAdEmailAsync(currentUser.Email); + settings.AuthorizedReports = await _settingsService.GetAuthorizedReportSettings(appUser); + + Log.Debug($"Returning Settings For AppUser:{appUser.FullName}"); + + return Ok(settings); + } + + Log.Warning("No Authorized AppUser Found, Returning Base Settings"); + return Ok(settings); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode((int)System.Net.HttpStatusCode.InternalServerError); + } + } + + [HttpGet("GetAllRoleMappings", Name = "GetAllRoleMappings")] + public async Task GetAllRoleMappings() + { + try + { + var result = await _settingsService.GetRoleMappingsAsync(); + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost("UpdateRoleMapping", Name = "UpdateRoleMapping")] + public async Task UpdateRoleMapping([FromBody] RoleMappingUpdateRequest request) + { + try + { + var currentUser = _currentUserService.GetCurrentUser(); + if (currentUser?.Email == null) + { + return Unauthorized("No AD User Found."); + } + + await _settingsService.SaveRoleMappingAsync(request); + return (Ok()); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("GetAuthorizedReportSettings", Name = "GetAuthorizedReportSettings")] + public async Task GetAuthorizedReports() + { + try + { + var currentUser = _currentUserService.GetCurrentUser(); + if (currentUser?.Email == null) + { + return Unauthorized("No AD User Found."); + } + + var appUser = await _userService.GetUserByAdEmailAsync(currentUser.Email); + + var result = await _settingsService.GetAuthorizedReportSettings(appUser); + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/StatusController.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/StatusController.cs new file mode 100644 index 0000000..1fa3d00 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/StatusController.cs @@ -0,0 +1,132 @@ +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.Status.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace LCU.API.Internal.Operations.IncentiveApi.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class StatusController : ControllerBase +{ + private readonly IApiHealthCheckService _apiHealthCheckService; + private readonly IDateTime _dateTime; + + public StatusController(IApiHealthCheckService apiHealthCheckService, IDateTime dateTime) + { + _apiHealthCheckService = apiHealthCheckService; + _dateTime = dateTime; + } + + [HttpGet] + [ProducesResponseType(200)] + public IActionResult Get() + { + try + { + return Ok(_apiHealthCheckService.CheckApiHealthAndBuildEnvironmentView()); + } + catch (ServiceException exception) + { + return StatusCode(StatusCodes.Status500InternalServerError, exception.Message); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("LocalTime")] + [ProducesResponseType(200)] + public IActionResult GetLocalTime() + { + try + { + return Ok(_dateTime.Now); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode((int) System.Net.HttpStatusCode.InternalServerError); + } + } + + [HttpGet("UtcTime")] + [ProducesResponseType(200)] + public IActionResult GetUTCTime() + { + try + { + return Ok(_dateTime.UTCNow); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode((int) System.Net.HttpStatusCode.InternalServerError); + } + } + + [HttpGet("WindowsCstTime")] + [ProducesResponseType(200)] + public IActionResult GetWindowsCSTTime() + { + try + { + return Ok(_dateTime.CSTWindowsNow); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode((int) System.Net.HttpStatusCode.InternalServerError); + } + } + + [HttpGet("LinuxCstTime")] + [ProducesResponseType(200)] + public IActionResult GetLinuxCSTTime() + { + try + { + return Ok(_dateTime.CSTLinuxNow); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode((int) System.Net.HttpStatusCode.InternalServerError); + } + } + + [HttpGet("ConvertCSTToUTCWindows/{dateTimeString}")] + [ProducesResponseType(200)] + public IActionResult ConvertCSTToUTCWindows(string dateTimeString) + { + try + { + var convertedTime = _dateTime.ConvertDateTimeFromCSTToUTCWindows(dateTimeString); + return Ok(convertedTime); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode((int) System.Net.HttpStatusCode.InternalServerError); + } + } + + [HttpGet("ConvertCSTToUTCLinux/{dateTimeString}")] + [ProducesResponseType(200)] + public IActionResult ConvertCSTToUTCLinux(string dateTimeString) + { + try + { + var convertedTime = _dateTime.ConvertDateTimeFromCSTToUTCLinux(dateTimeString); + return Ok(convertedTime); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode((int) System.Net.HttpStatusCode.InternalServerError); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/UserAssignmentController.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/UserAssignmentController.cs new file mode 100644 index 0000000..1d5577f --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Controllers/UserAssignmentController.cs @@ -0,0 +1,63 @@ +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.FeatureManagement.Mvc; +using LCU.API.Internal.Operations.IncentiveApi.Authorization; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Controllers; + +[FeatureGate(FeatureFlags.EnableControllers)] +[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] +[ApiController] +[Route("api/[controller]")] +public class UserAssignmentController : ControllerBase +{ + private readonly IUserAssignmentService _userAssignmentService; + + public UserAssignmentController(IUserAssignmentService userAssignmentService) + { + _userAssignmentService = userAssignmentService; + } + + [HttpGet("")] + public async Task GetAllUserAssignments() + { + try + { + var result = await _userAssignmentService.GetAllUserAssignmentsAsync(); + return Ok(result); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [RequirePermission("ASSIGN_REGIONAL_MANAGER")] + [HttpPost("SetRegionalManager", Name = "SetRegionalManager")] + public async Task SetRegionalManager([FromBody] UserAssignmentRequest assignmentRequest) + { + try + { + assignmentRequest.Type = AssignmentType.RegionalManager; + var result = await _userAssignmentService.SetRegionalManager(assignmentRequest); + return Ok(); + } + catch (BadUserAssignmentException ex) + { + return BadRequest(ex.Message); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure in {GetType().Name}"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/DataAccess/CoreApiWrapperDao.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/DataAccess/CoreApiWrapperDao.cs new file mode 100644 index 0000000..ed4707c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/DataAccess/CoreApiWrapperDao.cs @@ -0,0 +1,222 @@ +using System.Globalization; +using System.Web; +using Newtonsoft.Json; +using LCU.API.Internal.Operations.IncentiveApi.Models.Core; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Accounts; +using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Activity; +using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Transaction; +using LCU.Library.Internal.CoreApiWrapperModel.LcuModels.Account; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.Library.Internal.LcuHttpClient.HttpRequest; +using NewAtmCheckCardActivity = LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities.NewAtmCheckCardActivity; +using NewCheckingAccountActivity = LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities.NewCheckingAccountActivity; +using NewLoanSaleActivity = LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities.NewLoanSaleActivity; +using System.Net; + +namespace LCU.API.Internal.Operations.IncentiveApi.DataAccess; + +public class CoreApiWrapperDao : ICoreApiWrapperDao +{ + private readonly IGetRequest _getRequest; + private readonly IPostRequest _postRequest; + private readonly CoreApiWrapperConfiguration _coreApiWrapperConfiguration; + + public CoreApiWrapperDao(CoreApiWrapperConfiguration coreApiWrapperConfiguration, IGetRequest getRequest, IPostRequest postRequest) + { + _getRequest = getRequest; + _postRequest = postRequest; + _coreApiWrapperConfiguration = coreApiWrapperConfiguration; + } + + public async Task> GetNewCheckingAccountSalesActivity(DateTime activityDt) + { + try + { + var url = _coreApiWrapperConfiguration.GetNewCheckingAccountActivityUrl + $"?newerThanDateTime={DateTimeToUrlString(activityDt)}"; + + var coreResponse = await _getRequest.AsyncWithNewton>(url, null, 0, 300); + return coreResponse.Result; + } + catch (Exception ex) + { + Log.Error(ex, "Error Getting New Checking Account Sales from DNA"); + throw; + } + } + + public async Task> GetNewCheckingDigitalBankingAccountSalesActivity(DateTime activityDt) + { + try + { + var url = _coreApiWrapperConfiguration.GetNewDigitalBankingCheckingAccountActivityUrl + $"?newerThanDateTime={DateTimeToUrlString(activityDt)}"; + + var coreResponse = await _getRequest.AsyncWithNewton>(url,null, 0, 300); + return coreResponse.Result; + } + catch (Exception ex) + { + Log.Error(ex, "Error Getting New Checking Account Sales from DNA for Digital Banking"); + throw; + } + } + + public async Task> GetNewAtmCheckCardSalesActivity(DateTime activityDt) + { + try + { + var url = _coreApiWrapperConfiguration.GetNewATMCheckCardActivityUrl + $"?newerThanDateTime={DateTimeToUrlString(activityDt)}"; + + var coreResponse = await _getRequest.AsyncWithNewton>(url, null, 0, 500); + return coreResponse.Result; + } + catch (Exception ex) + { + Log.Error(ex, "Error Getting New ATM and Check Card Sales from DNA"); + throw; + } + } + + public async Task> GetNewActivityManagerReferralsActivity(DateTime activityDt) + { + try + { + var url = _coreApiWrapperConfiguration.GetNewActivityManagerReferralsActivityUrl + $"?newerThanOriginationDateTime={DateTimeToUrlString(activityDt)}"; + + var coreResponse = await _getRequest.AsyncWithNewton>(url, null, 0, 300); + return coreResponse.Result; + } + catch (Exception ex) + { + Log.Error(ex, "Error Getting New Activity Manager Referrals from DNA"); + throw; + } + } + + public async Task> GetNewMortgageSaleActivities(DateTime activityDt) + { + try + { + var url = _coreApiWrapperConfiguration.GetNewMortgageAccountActivityUrl + $"?newerThanDateTime={DateTimeToUrlString(activityDt)}"; + + var coreResponse = await _getRequest.AsyncWithNewton>(url, null, 0, 300); + return coreResponse.Result; + } + catch (Exception ex) + { + Log.Error(ex, "Error Getting New Mortgage Sales From DNA"); + throw; + } + } + + public async Task> GetNewVehicleSaleActivities(DateTime activityDt) + { + try + { + var url = _coreApiWrapperConfiguration.GetNewVehicleSaleActivityUrl + $"?newerThanDateTime={DateTimeToUrlString(activityDt)}"; + + var coreResponse = await _getRequest.AsyncWithNewton>(url, null, 0, 300); + return coreResponse.Result; + } + catch (Exception ex) + { + Log.Error(ex, "Error Getting New Vehicle Sales from DNA"); + throw; + } + } + + public async Task> GetNewDpEligibleSaleActivities(DateTime activityDt) + { + try + { + var url = _coreApiWrapperConfiguration.GetNewDPEligibleAccountsActivityUrl + $"?newerThanDateTime={DateTimeToUrlString(activityDt)}"; + + var coreResponse = await _getRequest.AsyncWithNewton>(url, null, 0, 300); + return coreResponse.Result; + } + catch (Exception ex) + { + Log.Error(ex, "Error Getting New DP Eligible Sales and Activities"); + throw; + } + } + + public async Task> GetUpdatedAccountInformation(List accountNumbers) + { + try + { + var path = _coreApiWrapperConfiguration.GetAccountActivityAccountDetailsUrl; + var uri = new Uri(path); + var coreResponse = await _postRequest.Async>(uri, new ActivityAccountDetailsRequest() { AccountNumbers = accountNumbers }, 0, 300); + return coreResponse; + } + catch (Exception ex) + { + Log.Error(ex, "Error Getting Updated Account Information"); + throw; + } + } + + public async Task> GetActivityManagerActivities(ActivityQueryRequest request) + { + try + { + var path = _coreApiWrapperConfiguration.QueryActivityManagerActivityUrl; + var uri = new Uri(path); + var coreResponse = await _postRequest.Async>(uri, new ActivityQueryRequest() { ActivityIds = request.ActivityIds }, 0, 300); + return coreResponse.Result; + } + catch (Exception ex) + { + Log.Error(ex, "Error Getting Updated Account Information"); + throw; + } + } + + public async Task> GetTransactionHistory(string accountNumber, DateTime startDate, DateTime endDate, List transactionCodes) + { + try + { + var transactionCodesString = transactionCodes == null ? "" : string.Join(",", transactionCodes); + + var url = _coreApiWrapperConfiguration.GetTransactionHistoryUrl + + $"?startDateRange={DateTimeToUrlString(startDate)}&endDateRange={DateTimeToUrlString(endDate)}" + + $"&accountNumber={accountNumber}&transactionTypeCodes={transactionCodesString}"; + + var httpStatusCodes = new List() { HttpStatusCode.NoContent, HttpStatusCode.NotFound }; + var responseMessage = await _getRequest.Async>(url, httpStatusCodes, 0, 300); + + var json = JsonConvert.SerializeObject(responseMessage); + var result = JsonConvert.DeserializeObject>(json); + return result ?? new List(); + } + catch (Exception ex) + { + Log.Error(ex, $"Error Getting Transaction History for AccountNumber: {accountNumber}, TransactionCodes: {transactionCodes}, from {startDate.ToString(CultureInfo.InvariantCulture)} to {endDate.ToString(CultureInfo.InvariantCulture)}"); + throw; + } + } + + public async Task> GetEStatmentDeliveryMethods(DateTime activityDt) + { + try + { + var url = _coreApiWrapperConfiguration.GetEStatementDeliveryMethodsUrl + $"?newerThanDateTime={DateTimeToUrlString(activityDt)}"; + + var coreResponse = await _getRequest.AsyncWithNewton>(url, null, 0, 300); + return coreResponse.Result; + } + catch (Exception ex) + { + Log.Error(ex, "Error Getting EStatement Delivery Methods from DNA"); + throw; + } + } + + public string DateTimeToUrlString(DateTime dateTime) + { + var dtstring = dateTime.ToString("yyyy-MM-ddTHH:mm:ss"); + return HttpUtility.UrlEncode(dtstring); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/DataAccess/FileOperationsApiDao.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/DataAccess/FileOperationsApiDao.cs new file mode 100644 index 0000000..b43ae2c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/DataAccess/FileOperationsApiDao.cs @@ -0,0 +1,32 @@ +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.Library.Internal.FileOperationsModels.Models; +using LCU.Library.Internal.LcuHttpClient.HttpRequest; + +namespace LCU.API.Internal.Operations.IncentiveApi.DataAccess; + +public class FileOperationsApiDao : IFileOperationsApiDao +{ + private readonly FileOperationsApiConfiguration _fileOperationApiConfiguration; + private readonly IGetRequest _getRequest; + private readonly IPostRequest _postRequest; + + public FileOperationsApiDao(FileOperationsApiConfiguration fileOperationApiConfiguration, IGetRequest getRequest, IPostRequest postRequest) + { + _fileOperationApiConfiguration = fileOperationApiConfiguration; + _getRequest = getRequest; + _postRequest = postRequest; + } + + public async Task BuildDynamicFileList(DynamicFileListRequest dynamicFileRequest) + { + var url = _fileOperationApiConfiguration.BuildDynamicFileListUri; + return await _postRequest.AsyncWithNewton(url, dynamicFileRequest, _fileOperationApiConfiguration.RetryCount); + } + + public async Task GetFileContent(string filePath) + { + var finalFilePath = filePath.Replace("\\", "%5C"); + var url = new Uri($"{_fileOperationApiConfiguration.GetFileContentUri}/{finalFilePath}"); + return await _getRequest.StringResponseAsync(url, _fileOperationApiConfiguration.RetryCount); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/DataAccess/ICoreApiWrapperDao.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/DataAccess/ICoreApiWrapperDao.cs new file mode 100644 index 0000000..244a2d3 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/DataAccess/ICoreApiWrapperDao.cs @@ -0,0 +1,25 @@ +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Accounts; +using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Activity; +using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Transaction; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using NewAtmCheckCardActivity = LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities.NewAtmCheckCardActivity; +using NewCheckingAccountActivity = LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities.NewCheckingAccountActivity; +using NewLoanSaleActivity = LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities.NewLoanSaleActivity; + +namespace LCU.API.Internal.Operations.IncentiveApi.DataAccess; + +public interface ICoreApiWrapperDao +{ + public Task> GetEStatmentDeliveryMethods(DateTime activityDt); + public Task> GetNewCheckingDigitalBankingAccountSalesActivity(DateTime activityDt); + public Task> GetNewCheckingAccountSalesActivity(DateTime activityDt); + public Task> GetNewAtmCheckCardSalesActivity(DateTime activityDt); + public Task> GetNewActivityManagerReferralsActivity(DateTime activityDt); + public Task> GetNewMortgageSaleActivities(DateTime activityDt); + public Task> GetNewVehicleSaleActivities(DateTime activityDt); + Task> GetNewDpEligibleSaleActivities(DateTime activityDt); + Task> GetUpdatedAccountInformation(List activityIds); + Task> GetActivityManagerActivities(ActivityQueryRequest request); + Task> GetTransactionHistory(string accountNumber, DateTime startDate, DateTime endDate, List transactionCodes); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/DataAccess/IFileOperationsApiDao.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/DataAccess/IFileOperationsApiDao.cs new file mode 100644 index 0000000..725b0a5 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/DataAccess/IFileOperationsApiDao.cs @@ -0,0 +1,9 @@ +using LCU.Library.Internal.FileOperationsModels.Models; + +namespace LCU.API.Internal.Operations.IncentiveApi.DataAccess; + +public interface IFileOperationsApiDao +{ + Task BuildDynamicFileList(DynamicFileListRequest dynamicFileRequest); + Task GetFileContent(string filePath); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/DataAccess/INotificationApiDao.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/DataAccess/INotificationApiDao.cs new file mode 100644 index 0000000..dd256a1 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/DataAccess/INotificationApiDao.cs @@ -0,0 +1,8 @@ +using LCU.API.Internal.NotificationsApiLib.Models; + +namespace LCU.API.Internal.Operations.IncentiveApi.DataAccess; + +public interface INotificationApiDao +{ + public Task SendMessageAsync(EmailRequest request); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/DataAccess/NotificationApiDao.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/DataAccess/NotificationApiDao.cs new file mode 100644 index 0000000..7203d18 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/DataAccess/NotificationApiDao.cs @@ -0,0 +1,31 @@ +using LCU.Library.Internal.LcuHttpClient.HttpRequest; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.API.Internal.NotificationsApiLib.Models; + +namespace LCU.API.Internal.Operations.IncentiveApi.DataAccess; + +public class NotificationApiDao : INotificationApiDao +{ + private readonly NotificationApiConfiguration _notificationApiConfiguration; + private readonly IPostRequest _postRequest; + + public NotificationApiDao(NotificationApiConfiguration notificationApiConfiguration, IPostRequest postRequest) + { + _notificationApiConfiguration = notificationApiConfiguration; + _postRequest = postRequest; + } + + public async Task SendMessageAsync(EmailRequest request) + { + try + { + await _postRequest.Async(new Uri(_notificationApiConfiguration.EmailUrl), request, _notificationApiConfiguration.RetryCount); + } + catch (Exception ex) + { + Log.Error(ex, "An exception was caught trying to send an email via Notifications API."); + throw; + } + } + +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/BadActivityStateChangeException.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/BadActivityStateChangeException.cs new file mode 100644 index 0000000..5ec9b9e --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/BadActivityStateChangeException.cs @@ -0,0 +1,8 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Exceptions; + +public class BadActivityStateChangeException : ApplicationException +{ + public BadActivityStateChangeException(string message) : base(message) + { + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/BadActivityTransferActionException.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/BadActivityTransferActionException.cs new file mode 100644 index 0000000..ae25438 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/BadActivityTransferActionException.cs @@ -0,0 +1,8 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Exceptions; + +public class BadActivityTransferActionException : ApplicationException +{ + public BadActivityTransferActionException(string message) : base(message) + { + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/BadDepartmentAssignmentException.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/BadDepartmentAssignmentException.cs new file mode 100644 index 0000000..2d921b7 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/BadDepartmentAssignmentException.cs @@ -0,0 +1,8 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Exceptions; + +public class BadDepartmentAssignmentException : ApplicationException +{ + public BadDepartmentAssignmentException(string message) : base(message) + { + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/BadUserAssignmentException.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/BadUserAssignmentException.cs new file mode 100644 index 0000000..37c5e57 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/BadUserAssignmentException.cs @@ -0,0 +1,8 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Exceptions; + +public class BadUserAssignmentException : ApplicationException +{ + public BadUserAssignmentException(string message) : base(message) + { + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/CommissionExistsException.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/CommissionExistsException.cs new file mode 100644 index 0000000..ef3aaca --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/CommissionExistsException.cs @@ -0,0 +1,8 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Exceptions; + +public class CommissionExistsException : ApplicationException +{ + public CommissionExistsException(string message) : base(message) + { + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/DataIntegrityException.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/DataIntegrityException.cs new file mode 100644 index 0000000..534eae7 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/DataIntegrityException.cs @@ -0,0 +1,9 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Exceptions; + +public class DataIntegrityException : ApplicationException +{ + public DataIntegrityException(string message) : base(message) + { + } +} + diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/DataIntegrityFailureException.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/DataIntegrityFailureException.cs new file mode 100644 index 0000000..2524c5e --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/DataIntegrityFailureException.cs @@ -0,0 +1,8 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Exceptions; + +public class DataIntegrityFailureException : ApplicationException +{ + public DataIntegrityFailureException(string message) : base(message) + { + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/DeadActivityException.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/DeadActivityException.cs new file mode 100644 index 0000000..c51a5b8 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/DeadActivityException.cs @@ -0,0 +1,8 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Exceptions; + +public class DeadActivityException: ApplicationException +{ + public DeadActivityException(string message) : base(message) + { + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/DuplicateDataException.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/DuplicateDataException.cs new file mode 100644 index 0000000..3caa48d --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/DuplicateDataException.cs @@ -0,0 +1,9 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Exceptions; + +public class DuplicateDataException : ApplicationException +{ + public DuplicateDataException(string message) : base(message) + { + } +} + diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/FailedParsingHeadersException.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/FailedParsingHeadersException.cs new file mode 100644 index 0000000..84762fa --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/FailedParsingHeadersException.cs @@ -0,0 +1,6 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Exceptions; + +public class FailedParsingHeadersException : System.Exception +{ + public FailedParsingHeadersException() : base("Missing Required Headers") {} +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/MissingProcessorRunHistoryException.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/MissingProcessorRunHistoryException.cs new file mode 100644 index 0000000..edcc6ed --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/MissingProcessorRunHistoryException.cs @@ -0,0 +1,13 @@ +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Exceptions; + +public class MissingProcessorRunHistoryException : ApplicationException +{ + public MissingProcessorRunHistoryException(Process process) : base( + $"{process.ToString()} process does not have a ProcessorHistory entry and needs to be manually seeded.") + { + Log.Fatal( + $"{process.ToString()} does not have a ProcessorHistory entry and needs to be manually seeded. New Activities of this type will not be imported until a record is seeded."); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/NoAvailableDecisionStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/NoAvailableDecisionStrategy.cs new file mode 100644 index 0000000..a49f0b3 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/NoAvailableDecisionStrategy.cs @@ -0,0 +1,8 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Exceptions; + +public class NoAvailableDecisionStrategy : ApplicationException +{ + public NoAvailableDecisionStrategy(string message) : base(message) + { + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/NoAvailableStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/NoAvailableStrategy.cs new file mode 100644 index 0000000..9bc2732 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/NoAvailableStrategy.cs @@ -0,0 +1,8 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Exceptions; + +public class NoAvailableStrategy : ApplicationException +{ + public NoAvailableStrategy(string message) : base(message) + { + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/NotAuthorizedException.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/NotAuthorizedException.cs new file mode 100644 index 0000000..6e07029 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/NotAuthorizedException.cs @@ -0,0 +1,8 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Exceptions; + +public class NotAuthorizedException : ApplicationException +{ + public NotAuthorizedException(string message) : base(message) + { + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/NotFoundException.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/NotFoundException.cs new file mode 100644 index 0000000..5631109 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/NotFoundException.cs @@ -0,0 +1,8 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Exceptions; + +public class NotFoundException : ApplicationException +{ + public NotFoundException(string message) : base(message) + { + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/PayoutIneligibleException.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/PayoutIneligibleException.cs new file mode 100644 index 0000000..d5c1b19 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/PayoutIneligibleException.cs @@ -0,0 +1,8 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Exceptions; + +public class PayoutIneligibleException : ApplicationException +{ + public PayoutIneligibleException(string message) : base(message) + { + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/ServerErrorException.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/ServerErrorException.cs new file mode 100644 index 0000000..3c2d61a --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/ServerErrorException.cs @@ -0,0 +1,6 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Exceptions; + +public class ServerErrorException : Exception +{ + public ServerErrorException(): base("A server error occured. See associated logs.") { } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/UnknownProductException.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/UnknownProductException.cs new file mode 100644 index 0000000..ac20cf8 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Exceptions/UnknownProductException.cs @@ -0,0 +1,9 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Exceptions; + +public class UnknownProductException : ApplicationException +{ + public UnknownProductException() : base( + "Encountered a product string in an incoming activity Incentive Manager could not pars ") + { + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Extensions/DateTimeExtensions.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Extensions/DateTimeExtensions.cs new file mode 100644 index 0000000..f01b241 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Extensions/DateTimeExtensions.cs @@ -0,0 +1,14 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Extensions; + +public static class DateTimeExtensions +{ + public static DateTime StartOfMonth(this DateTime date) + { + return DateTime.SpecifyKind(new DateTime(date.Year, date.Month, 1), date.Kind); + } + + public static DateTime EndOfMonth(this DateTime date) + { + return DateTime.SpecifyKind(date.StartOfMonth().AddMonths(1).AddSeconds(-1), date.Kind); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Extensions/ModelStateMessages.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Extensions/ModelStateMessages.cs new file mode 100644 index 0000000..75ffd46 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Extensions/ModelStateMessages.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace LCU.API.Internal.Operations.IncentiveApi.Extensions +{ + public static class ModelStateMessages + { + public static List GetErrorMessages(this ModelStateDictionary dictionary) + { + if (dictionary == null) + { + return new List(); + } + + return dictionary.SelectMany(m => m.Value.Errors) + .Select(m => m.ErrorMessage) + .ToList(); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/HostedServices/ApplicationDbIntegrityHostedService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/HostedServices/ApplicationDbIntegrityHostedService.cs new file mode 100644 index 0000000..5982358 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/HostedServices/ApplicationDbIntegrityHostedService.cs @@ -0,0 +1,63 @@ +using Microsoft.Extensions.DependencyInjection; +using System.Threading; +using NCrontab; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.Library.Internal.LCUHostedService.LCUBackgroundService; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.API.Internal.Operations.IncentiveApi.Utilities; + +namespace LCU.API.Internal.Operations.IncentiveApi.HostedServices; + +public class ApplicationDbIntegrityHostedService : LCUBackgroundService, IApplicationDbIntegrityHostedService +{ + private readonly ApiConfiguration _apiConfiguration; + private readonly IServiceScopeFactory _scopeFactory; + private readonly CrontabSchedule _applicationDbIntegritySchedule; + private DateTime _nextScheduleRun; + public const string DefaultErrorMonitoringCronPattern = "0 0 * * *"; + private CancellationToken _cancellationToken; + + public ApplicationDbIntegrityHostedService(IServiceScopeFactory scopeFactory, ApiConfiguration apiConfiguration) + { + _scopeFactory = scopeFactory; + _apiConfiguration = apiConfiguration; + _applicationDbIntegritySchedule = CronHelpers.SetCronSchedule(_apiConfiguration.ApplicationDbIntegrityCronSchedule, + DefaultErrorMonitoringCronPattern); + + _nextScheduleRun = _applicationDbIntegritySchedule.GetNextOccurrence(DateTime.Now); + _cancellationToken = new CancellationToken(); + } + + protected override async Task ExecuteAsync(CancellationToken cancellationToken) + { + Log.Debug("ApplicationDbIntegrity Hosted Service is starting."); + try + { + while (!cancellationToken.IsCancellationRequested) + { + await Task.Delay(UntilNextExecution(), cancellationToken); + + using (var scope = _scopeFactory.CreateScope()) + { + Log.Debug("Running ApplicationDbIntegrity Service"); + await scope.ServiceProvider.GetRequiredService().AppUserIdIntegrityMaintenanceSucceeded(_cancellationToken); + } + + _nextScheduleRun = _applicationDbIntegritySchedule.GetNextOccurrence(DateTime.Now); + } + } + catch (OperationCanceledException taskCanceledEx) + { + Log.Error(taskCanceledEx, $"Manual start/stop of ApplicationDbIntegrity Hosted Service was initiated."); + } + catch (Exception exception) + { + Log.Error(exception, + "Error trying to startup main thread for ApplicationDbIntegrity Hosted Service"); + } + + Log.Debug("ApplicationDbIntegrity Hosted Service is stopping."); + } + + private int UntilNextExecution() => Math.Max(0, (int)_nextScheduleRun.Subtract(DateTime.Now).TotalMilliseconds); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/HostedServices/IApplicationDbIntegrityHostedService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/HostedServices/IApplicationDbIntegrityHostedService.cs new file mode 100644 index 0000000..a68b04d --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/HostedServices/IApplicationDbIntegrityHostedService.cs @@ -0,0 +1,8 @@ +using LCU.Library.Internal.LCUHostedService.LCUBackgroundService; + +namespace LCU.API.Internal.Operations.IncentiveApi.HostedServices +{ + public interface IApplicationDbIntegrityHostedService : ILCUBackgroundService + { + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/LCU.API.Internal.Operations.IncentiveApi.csproj b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/LCU.API.Internal.Operations.IncentiveApi.csproj new file mode 100644 index 0000000..c2a7645 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/LCU.API.Internal.Operations.IncentiveApi.csproj @@ -0,0 +1,60 @@ + + + + net8.0 + 1.2.3 + 1.2.3 + + + + C:\Projects\LCU.API.Operations.IncentiveAPI\LCU.API.Internal.Operations.IncentiveApi\bin\Debug\LCU.API.Internal.Operations.IncentiveApi.xml + 1701;1702;1591 + + + + bin\Release\LCU.API.Internal.Operations.IncentiveApi.xml + 1701;1702;1591 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Action/ActionCreditCardBusinessRow.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Action/ActionCreditCardBusinessRow.cs new file mode 100644 index 0000000..419ac4e --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Action/ActionCreditCardBusinessRow.cs @@ -0,0 +1,86 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.Action; + +public class ActionCreditCardBusinessRow : IActionFileObject +{ + public string BookedDate { get; set; } + public string BusinessTradeName { get; set; } + public string BusinessLegalName { get; set; } + public string AppID { get; set; } + public string Principal { get; set; } + public string ProdDetailFirstName { get; set; } + public string ProdDetailLastName { get; set; } + public string ProdDetailEmail { get; set; } + public string ProdDetailEmployeeNum { get; set; } + public string IncentiveEarnedEmail { get; set; } + public string ProductShortName { get; set; } + + public Activity CreateActivity(AppUser appUser) + { + var homeDept = appUser.FindHomeDepartment(); + return new Activity() + { + Product = Product.CreditCard, + Type = ActivityType.Sale, + State = ActivityState.Accepted, + SourceSystem = SystemType.Action, + SourceCreated = DateTime.Parse(BookedDate), + Accepted = DateTime.Parse(BookedDate).Date.AddHours(12), + SourceId = AppID, + Detail = new ActivityDetail() + { + MemberName = BusinessLegalName, + AccountBalance = IActionFileObject.ParseMoneyDouble(Principal), + AccountStatus = ProductShortName + }, + AppUserId = appUser.Id, + EmployeeEmail = appUser.AdEmail, + EmployeeId = appUser.EmployeeId, + DepartmentId = homeDept.DepartmentId + }; + } + + public DeadActivity CreateDeadActivity(DeadActivityReason reasonCode, string note, string employeeEmail = null, string employeeId = null) + { + return new DeadActivity(AppID, SystemType.Action, reasonCode, note) + { + Product = Product.CreditCard, + Type = ActivityType.Sale, + State = ActivityState.Accepted, + SourceSystem = SystemType.Action, + SourceCreated = DateTime.Parse(BookedDate), + Accepted = DateTime.Parse(BookedDate).Date.AddHours(12), + SourceId = AppID, + DeadDetail = new DeadActivityDetail() + { + MemberName = BusinessLegalName, + AccountBalance = IActionFileObject.ParseMoneyDouble(Principal), + AccountStatus = ProductShortName + }, + EmployeeEmail = employeeEmail, + EmployeeId = employeeId, + }; + } + + public string GetEmployeeEmail() + { + return string.IsNullOrWhiteSpace(IncentiveEarnedEmail) ? null : IncentiveEarnedEmail.Trim(); + } + + public string GetLoanOfficerEmail() + { + return string.IsNullOrWhiteSpace(ProdDetailEmail) ? null : ProdDetailEmail.Trim(); + } + + public string GetLoanOfficerEmailByName() + { + if (string.IsNullOrWhiteSpace(ProdDetailFirstName) || string.IsNullOrWhiteSpace(ProdDetailLastName)) + { + return null; + } + + return $"{ProdDetailFirstName.Trim()}{ProdDetailLastName.Trim()}@landmarkcu.com"; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Action/ActionCreditCardPersonalRow.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Action/ActionCreditCardPersonalRow.cs new file mode 100644 index 0000000..d0242ff --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Action/ActionCreditCardPersonalRow.cs @@ -0,0 +1,86 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.Action; + +public class ActionCreditCardPersonalRow : IActionFileObject +{ + public string BookedDate { get; set; } + public string MemberFirstName { get; set; } + public string MemberLastName { get; set; } + public string AppID { get; set; } + public string Principal { get; set; } + public string ProdDetailFirstName { get; set; } + public string ProdDetailLastName { get; set; } + public string ProdDetailEmail { get; set; } + public string ProdDetailEmployeeNum { get; set; } + public string IncentiveEarnedEmail { get; set; } + public string ProductShortName { get; set; } + + public Activity CreateActivity(AppUser appUser) + { + var homeDept = appUser.FindHomeDepartment(); + return new Activity() + { + Product = Product.CreditCard, + Type = ActivityType.Sale, + State = ActivityState.Accepted, + SourceSystem = SystemType.Action, + SourceCreated = DateTime.Parse(BookedDate), + Accepted = DateTime.Parse(BookedDate).Date.AddHours(12), + SourceId = AppID, + Detail = new ActivityDetail() + { + MemberName = MemberFirstName + " " + MemberLastName, + AccountBalance = IActionFileObject.ParseMoneyDouble(Principal), + AccountStatus = ProductShortName + }, + AppUserId = appUser.Id, + EmployeeEmail = appUser.AdEmail, + EmployeeId = appUser.EmployeeId, + DepartmentId = homeDept.DepartmentId + }; + } + + public DeadActivity CreateDeadActivity(DeadActivityReason reasonCode, string note, string employeeEmail = null, string employeeId = null) + { + return new DeadActivity(AppID, SystemType.Action, reasonCode, note) + { + Product = Product.CreditCard, + Type = ActivityType.Sale, + State = ActivityState.Accepted, + SourceSystem = SystemType.Action, + SourceCreated = DateTime.Parse(BookedDate), + Accepted = DateTime.Parse(BookedDate).Date.AddHours(12), + SourceId = AppID, + DeadDetail = new DeadActivityDetail() + { + MemberName = MemberFirstName + " " + MemberLastName, + AccountBalance = IActionFileObject.ParseMoneyDouble(Principal), + AccountStatus = ProductShortName + }, + EmployeeEmail = employeeEmail, + EmployeeId = employeeId, + }; + } + + public string GetEmployeeEmail() + { + return string.IsNullOrWhiteSpace(IncentiveEarnedEmail) ? null : IncentiveEarnedEmail.Trim(); + } + + public string GetLoanOfficerEmail() + { + return string.IsNullOrWhiteSpace(ProdDetailEmail) ? null : ProdDetailEmail.Trim(); + } + + public string GetLoanOfficerEmailByName() + { + if (string.IsNullOrWhiteSpace(ProdDetailFirstName) || string.IsNullOrWhiteSpace(ProdDetailLastName)) + { + return null; + } + + return $"{ProdDetailFirstName.Trim()}{ProdDetailLastName.Trim()}@landmarkcu.com"; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Action/ActionSaleRow.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Action/ActionSaleRow.cs new file mode 100644 index 0000000..db985dc --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Action/ActionSaleRow.cs @@ -0,0 +1,32 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Models.Action; + +public class ActionSaleRow +{ + public string EmailAddress { get; set; } + public string Member_Number { get; set; } + public string Account_Number { get; set; } + public string Borrower_Name { get; set; } + public string APP_START_DATE { get; set; } + public string OPEN_DATE { get; set; } + public string PBorrowerSSN_Ind { get; set; } + public string CBorrowerName_Ind { get; set; } + public string CBorrowerSSN_Ind { get; set; } + public string SBorrowerName_Ind { get; set; } + public string SBorrowerSSN_Ind { get; set; } + public string IndAppID { get; set; } + public string IndProduct { get; set; } + public string IndAmtFinanced { get; set; } + public string IndCreationDate { get; set; } + public string IndAccountNumber { get; set; } + public string PBorrowerName_Veh { get; set; } + public string PBorrowerSSN_Veh { get; set; } + public string CBorrowerName_Veh { get; set; } + public string CBorrowerSSN_Veh { get; set; } + public string SBorrowerName_Veh { get; set; } + public string SBorrowerSSN_Veh { get; set; } + public string VehAppID { get; set; } + public string VehProduct { get; set; } + public string VehAmtFinanced { get; set; } + public string DistributionChannel { get; set; } + public string DistributionChannelCode { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Action/IActionFileObject.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Action/IActionFileObject.cs new file mode 100644 index 0000000..e0bb8cb --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Action/IActionFileObject.cs @@ -0,0 +1,20 @@ +using System.Globalization; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.Action; + +public interface IActionFileObject +{ + static readonly CultureInfo USEnglish = new CultureInfo("en-US"); + public Activity CreateActivity(AppUser appUser); + public string GetEmployeeEmail(); + public string GetLoanOfficerEmail(); + public string GetLoanOfficerEmailByName(); + public DeadActivity CreateDeadActivity(DeadActivityReason reasonCode, string note, string employeeEmail = null, string employeeId = null); + public static double? ParseMoneyDouble(string indAmtFinanced) + { + var parsed = decimal.Parse(indAmtFinanced, NumberStyles.Currency, USEnglish); + return (double)parsed; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ActivityDetailUpdateOrder.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ActivityDetailUpdateOrder.cs new file mode 100644 index 0000000..0c2fccc --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ActivityDetailUpdateOrder.cs @@ -0,0 +1,10 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Accounts; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models; + +public class ActivityDetailUpdateOrder +{ + public Activity ExistingActivity { get; set; } + public List DetailUpdates { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ActivityProcessingOrder.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ActivityProcessingOrder.cs new file mode 100644 index 0000000..25af587 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ActivityProcessingOrder.cs @@ -0,0 +1,11 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models; + +public class ActivityProcessingOrder +{ + public Activity ExistingActivity { get; set; } + public List ActivityUpdateItems { get; set; } + public bool ProcessingComplete { get; set; } + public int UpdateCount { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Allied/AlliedSaleRow.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Allied/AlliedSaleRow.cs new file mode 100644 index 0000000..0a27d19 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Allied/AlliedSaleRow.cs @@ -0,0 +1,81 @@ +using CsvHelper.Configuration.Attributes; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.Allied; + +public class AlliedSaleRow +{ + [Name("Quoter")] + public string Quoter { get; set; } + [Name("Issuer")] + public string Issuer { get; set; } + [Name("Origin")] + public string Origin { get; set; } + [Name("Product_Type")] + public string ProductType { get; set; } + [Name("Status")] + public string Status { get; set; } + [Name("Sale_Date")] + public string SaleDate { get; set; } + [Name("Sale_Effective_Date")] + public string SaleEffectiveDate { get; set; } + [Name("Cancellation_Date")] + public string CancellationDate { get; set; } + [Name("Cancellation_Reason")] + public string CancellationReason { get; set; } + [Name("Cancelled_By")] + public string CancelledBy { get; set; } + [Name("Sales_Contract_Number")] + public string SalesContractNumber { get; set; } + [Name("Buyer")] + public string Buyer { get; set; } + [Name("Account_Number")] + public string AccountNumber { get; set; } + [Name("LOS_LOAN_APP_ID_TX")] + public string LosLoanAppIdTx { get; set; } + [Name("Program_Type")] + public string ProgramType { get; set; } + [Name("Buyer_Cost")] + public string BuyerCost { get; set; } + [Name("Flagged_Reasons")] + public string FlaggedReasons { get; set; } + + public string DetermineAccountNumber() + { + var accountNumber = ""; + if (!string.IsNullOrEmpty(AccountNumber)) + { + accountNumber = AccountNumber; + } + else + { + accountNumber = SalesContractNumber ?? ""; + } + + if (accountNumber.Contains("-")) + { + var transform = accountNumber.Split("-"); + if (transform?.Length == 2) + { + return transform[0].Trim(); + } + } + + return accountNumber; + } + + public Product DetermineAlliedProduct() + { + if (string.Equals(ProductType.Trim(), "mbp", StringComparison.CurrentCultureIgnoreCase)) + { + return Product.MechanicalBreakdownProtection; + } + + if (string.Equals(ProductType.Trim(), "gap", StringComparison.CurrentCultureIgnoreCase)) + { + return Product.GAPInsurance; + } + + throw new Exception($"Allied should only pass MBP or GAP for product, but received {ProductType}"); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/AuthorizedActivityCommands.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/AuthorizedActivityCommands.cs new file mode 100644 index 0000000..0185fbf --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/AuthorizedActivityCommands.cs @@ -0,0 +1,17 @@ +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models; + +public class AuthorizedActivityCommands +{ + public AuthorizedActivityCommands(ActivityState[] allowedChanges, + bool canApproveTransferFrom = false, bool canApproveTransferTo = false) + { + AllowedChanges = allowedChanges; + CanApproveTransferFrom = canApproveTransferFrom; + CanApproveTransferTo = canApproveTransferTo; + } + public ActivityState[] AllowedChanges { get; } + public bool CanApproveTransferFrom { get; } + public bool CanApproveTransferTo { get; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/BusinessLineProgress.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/BusinessLineProgress.cs new file mode 100644 index 0000000..48b6972 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/BusinessLineProgress.cs @@ -0,0 +1,27 @@ +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models; + +public class BusinessLineProgress: IViewTransformable +{ + public BusinessLineProgress(BusinessLine businessLine, MonthRange monthRange) + { + BusinessLine = businessLine; + MonthRange = monthRange; + Departments = new List(); + } + + public BusinessLine BusinessLine { get; } + public MonthRange MonthRange{ get; } + public IList Departments { get; } + + public BusinessLineProgress TransformForView() + { + foreach(var department in Departments) + { + department?.TransformForView(); + } + return this; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Constants/SystemAppUserConstants.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Constants/SystemAppUserConstants.cs new file mode 100644 index 0000000..c649031 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Constants/SystemAppUserConstants.cs @@ -0,0 +1,7 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Models.Constants; + +public static class SystemAppUserConstants +{ + public const string DigitalBankingEmail = "DIGITALBANKING@LANDMARKCU.COM"; + public const string FirstMortgageEmail = "FIRSTMORTGAGE@LANDMARKCU.COM"; +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Constants/TemenosImportConstants.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Constants/TemenosImportConstants.cs new file mode 100644 index 0000000..83c83b9 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Constants/TemenosImportConstants.cs @@ -0,0 +1,6 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Models.Constants; + +public static class TemenosImportConstants +{ + public const string PersonalCardSubProduct = "Credit Card"; +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Core/CoreResponse.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Core/CoreResponse.cs new file mode 100644 index 0000000..7550566 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Core/CoreResponse.cs @@ -0,0 +1,15 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Models.Core; + +public class CoreResponse +{ + public List Result {get; set;} + public int Id {get; set;} + public string Exception {get; set;} + public string Status {get; set;} + public bool IsCanceled {get; set;} + public bool IsCompleted {get; set;} + public bool IsCompletedSuccessfully {get; set;} + public string CreationOptions {get; set;} + public string AsyncState {get; set;} + public bool IsFaulted {get; set;} +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DataTransfer/ActivityTransferApprovalRequest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DataTransfer/ActivityTransferApprovalRequest.cs new file mode 100644 index 0000000..7ea2fa7 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DataTransfer/ActivityTransferApprovalRequest.cs @@ -0,0 +1,9 @@ +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; + +public class ActivityTransferApprovalRequest +{ + public int Id { get; set; } + public ActivityTransferState ApprovalState { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DataTransfer/ActivityTransferRequest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DataTransfer/ActivityTransferRequest.cs new file mode 100644 index 0000000..c7794c2 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DataTransfer/ActivityTransferRequest.cs @@ -0,0 +1,12 @@ +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; + +public class ActivityTransferRequest +{ + public int? Id { get; set; } + public ActivityState ActivityStateBeforeTransfer { get; set; } + public int RequestedActivityId { get; set; } + public int NewDepartmentId { get; set; } + public string NewEmployeeId { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DataTransfer/AdditionalPermissionRequest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DataTransfer/AdditionalPermissionRequest.cs new file mode 100644 index 0000000..016eaa2 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DataTransfer/AdditionalPermissionRequest.cs @@ -0,0 +1,7 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; + +public class AdditionalPermissionRequest +{ + public List AddPermCodes { get; set; } + public List RemovePermCodes { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DataTransfer/DepartmentAssignmentRequest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DataTransfer/DepartmentAssignmentRequest.cs new file mode 100644 index 0000000..7b8b54f --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DataTransfer/DepartmentAssignmentRequest.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; + +public class DepartmentAssignmentRequest +{ + [Required] + public int[] DepartmentId { get; set; } + [Required] + public DateTime StartDate { get; set; } + public DateTime? EndDate { get; set; } + [StringLength(255)] + public string? Note { get; set; } + + [Required] + [Range(0,100)] + public decimal SlidingPayout { get; set; } + + [Required] + public string Role { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DataTransfer/ExtrapolateGoalDispersalRequest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DataTransfer/ExtrapolateGoalDispersalRequest.cs new file mode 100644 index 0000000..4ae4f56 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DataTransfer/ExtrapolateGoalDispersalRequest.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; + +public class ExtrapolateGoalDispersalRequest +{ + [Range(1,12)] + public int Month { get; set; } + public int Year { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DataTransfer/RoleMappingUpdateRequest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DataTransfer/RoleMappingUpdateRequest.cs new file mode 100644 index 0000000..903daf3 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DataTransfer/RoleMappingUpdateRequest.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; + +public class RoleMappingUpdateRequest +{ + [Required] + public int Id { get; set; } + [Required] + public string ApplicationRole { get; set; } + [Required] + public string BusinessRole { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DataTransfer/UserAssignmentRequest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DataTransfer/UserAssignmentRequest.cs new file mode 100644 index 0000000..b33a7b1 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DataTransfer/UserAssignmentRequest.cs @@ -0,0 +1,18 @@ +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System.ComponentModel.DataAnnotations; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; + +public class UserAssignmentRequest +{ + [Required] + public string EmployeeId { get; set; } + + [JsonConverter(typeof(StringEnumConverter))] + public AssignmentType? Type { get; set; } + + [Required] + public string Key { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DataTransfer/UserNameSearchRequest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DataTransfer/UserNameSearchRequest.cs new file mode 100644 index 0000000..396725c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DataTransfer/UserNameSearchRequest.cs @@ -0,0 +1,9 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; + +public class UserNameSearchRequest +{ + public string FirstName { get; set; } + public string LastName { get; set; } + public string MiddleInit { get; set; } + public string DnaOrgNbr { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/ActiveEmployeeReceipt.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/ActiveEmployeeReceipt.cs new file mode 100644 index 0000000..3836c19 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/ActiveEmployeeReceipt.cs @@ -0,0 +1,18 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Results; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; + +public class ActiveEmployeeReceipt +{ + public ActiveEmployeeReceipt(IDictionary reportRow) + { + ReportRow = reportRow; + Status = ActiveEmployeeReceiptStatus.NOT_PROCESSED; + } + + public AppUser AppUser { get; set; } + public IDictionary ReportRow { get; } + public ActiveEmployeeResult EmployeeRow { get; set; } + public ActiveEmployeeReceiptStatus Status { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/ActiveEmployeeReceiptStatus.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/ActiveEmployeeReceiptStatus.cs new file mode 100644 index 0000000..fe8c266 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/ActiveEmployeeReceiptStatus.cs @@ -0,0 +1,10 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; + +public enum ActiveEmployeeReceiptStatus +{ + NOT_PROCESSED, + SUCCESS, + MISSING_ROLE, + MISSING_DATA, + EXCEPTION +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/EmployeeDetailsUpdate.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/EmployeeDetailsUpdate.cs new file mode 100644 index 0000000..3f123be --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/EmployeeDetailsUpdate.cs @@ -0,0 +1,55 @@ +using System.ComponentModel.DataAnnotations; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; + +public class EmployeeDetailsUpdate +{ + public string OldEmail { get; set; } + public string OldEmployeeId { get; set; } + public string OldFullName { get; set; } + public string NewEmail { get; set; } + public string NewEmployeeId { get; set; } + public string NewFullName { get; set; } + + public bool NewDetailsMatchOld() + { + return OldEmail.Equals(NewEmail, StringComparison.InvariantCultureIgnoreCase) + && OldEmployeeId.Equals(NewEmployeeId, StringComparison.InvariantCultureIgnoreCase) + && OldFullName.Equals(NewFullName, StringComparison.InvariantCultureIgnoreCase); + } + + public string InvalidUpdateData() + { + if (NewDetailsMatchOld()) + { + return $"Invalid Update Data: Old Details Match Updated Details"; + } + + if (string.IsNullOrWhiteSpace(NewEmail)) + { + return $"Invalid Update Data: NewEmail: {NewEmail}"; + } + + if (!new EmailAddressAttribute().IsValid(NewEmail)) + { + return $"Invalid Update Data: NewEmail Is Not A Valid Email: {NewEmail}"; + } + + if (!NewEmail.Contains("@landmarkcu.com", StringComparison.InvariantCultureIgnoreCase)) + { + return $"Invalid Update Data: NewEmail Is Not A Valid Internal Email: {NewEmail}"; + } + + if (string.IsNullOrWhiteSpace(NewEmployeeId)) + { + return $"Invalid Update Data: NewEmployeeId: {NewEmployeeId}"; + } + + if (string.IsNullOrWhiteSpace(NewFullName)) + { + return $"Invalid Update Data: NewFullName: {NewFullName}"; + } + + return ""; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/EmployeeDetailsUpdateResult.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/EmployeeDetailsUpdateResult.cs new file mode 100644 index 0000000..12347de --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/EmployeeDetailsUpdateResult.cs @@ -0,0 +1,10 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; + +public class EmployeeDetailsUpdateResult +{ + public AppUser AppUser { get; set; } + public string UpdateMessage { get; set; } + public bool UpdatedSuccessfully { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/Paging.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/Paging.cs new file mode 100644 index 0000000..5ef6f51 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/Paging.cs @@ -0,0 +1,25 @@ +using System.Runtime.Serialization; +using System.Text; +using Newtonsoft.Json; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; + +[DataContract] +public class Paging +{ + [DataMember(Name = "Next", EmitDefaultValue = false)] + [JsonProperty(PropertyName = "Next")] + public string Next { get; set; } + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append("class Paging {\n"); + sb.Append(" Next: ").Append(Next).Append("\n"); + sb.Append("}\n"); + return sb.ToString(); + } + public string ToJson() + { + return JsonConvert.SerializeObject(this, Formatting.Indented); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/Payload.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/Payload.cs new file mode 100644 index 0000000..f1d2ed7 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/Payload.cs @@ -0,0 +1,13 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; + +public class Payload +{ + public string RequestId { get; set; } + public List ProcessResults { get; set; } +} + +public class Payload : Payload +{ + public T Data { get; set; } + public Paging Paging { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/PayoutEntry.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/PayoutEntry.cs new file mode 100644 index 0000000..49a1c17 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/PayoutEntry.cs @@ -0,0 +1,9 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; + +public class PayoutEntry +{ + public string Code { get; set; } + public string EmployeeName { get; set; } + public string EmployeeId { get; set; } + public decimal PayoutTotal { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/ProcessResult.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/ProcessResult.cs new file mode 100644 index 0000000..2de7ae2 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/ProcessResult.cs @@ -0,0 +1,42 @@ +using System.Runtime.Serialization; +using System.Text; +using Newtonsoft.Json; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; + +[DataContract] +public class ProcessResult +{ + [DataMember(Name = "Code", EmitDefaultValue = false)] + [JsonProperty(PropertyName = "Code")] + public string Code { get; set; } + + [DataMember(Name = "Context", EmitDefaultValue = false)] + [JsonProperty(PropertyName = "Context")] + public string Context { get; set; } + + [DataMember(Name = "Level", EmitDefaultValue = false)] + [JsonProperty(PropertyName = "Level")] + public string Level { get; set; } + + [DataMember(Name = "Message", EmitDefaultValue = false)] + [JsonProperty(PropertyName = "Message")] + public string Message { get; set; } + + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append("class ProcessResult {\n"); + sb.Append(" Code: ").Append(Code).Append("\n"); + sb.Append(" Context: ").Append(Context).Append("\n"); + sb.Append(" Level: ").Append(Level).Append("\n"); + sb.Append(" Message: ").Append(Message).Append("\n"); + sb.Append("}\n"); + return sb.ToString(); + } + + public string ToJson() + { + return JsonConvert.SerializeObject(this, Formatting.Indented); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/Report.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/Report.cs new file mode 100644 index 0000000..b9cd387 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/Report.cs @@ -0,0 +1,33 @@ +using System.Runtime.Serialization; +using System.Text; +using Newtonsoft.Json; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; + +[DataContract] +public class Report +{ + + [DataMember(Name = "XRefCode", EmitDefaultValue = false)] + [JsonProperty(PropertyName = "XRefCode")] + public string XRefCode { get; set; } + + [DataMember(Name = "Rows", EmitDefaultValue = false)] + [JsonProperty(PropertyName = "Rows")] + public List> Rows { get; set; } + + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append("class Report {\n"); + sb.Append(" XRefCode: ").Append(XRefCode).Append("\n"); + sb.Append(" Rows: ").Append(Rows).Append("\n"); + sb.Append("}\n"); + return sb.ToString(); + } + + public string ToJson() + { + return JsonConvert.SerializeObject(this, Formatting.Indented); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/TerminatedEmployeeReceipt.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/TerminatedEmployeeReceipt.cs new file mode 100644 index 0000000..fde2608 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/TerminatedEmployeeReceipt.cs @@ -0,0 +1,19 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; + +public class TerminatedEmployeeReceipt +{ + public TerminatedEmployeeReceipt(AppUser terminated, PayoutEntry lastMonthPayout, PayoutEntry thisMonthPayout, AppUser manager) + { + Terminated = terminated; + LastMonthPayout = lastMonthPayout; + ThisMonthPayout = thisMonthPayout; + Manager = manager; + } + + public AppUser Terminated { get; } + public AppUser Manager { get; } + public PayoutEntry LastMonthPayout { get; } + public PayoutEntry ThisMonthPayout { get; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/TerminatedEmployeeResult.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/TerminatedEmployeeResult.cs new file mode 100644 index 0000000..3828925 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Dayforce/TerminatedEmployeeResult.cs @@ -0,0 +1,25 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; + +public class TerminatedEmployeeResult +{ + public TerminatedEmployeeResult(IReadOnlyDictionary row) + { + DisplayName = row["Employee_DisplayName"]; + EmployeeNumber = row["EmployeeEmploymentStatus_EmployeeNumber"]; + LoginName = row["IdLibrary_LoginName"]; + + if (row.ContainsKey("EmployeeManager_ManagerEmployeeNumber")) + { + ManagerKey = row["EmployeeManager_ManagerEmployeeNumber"]; + } + + var termDate = row["Employee_TerminationDate"]; + TerminationDate = string.IsNullOrWhiteSpace(termDate) ? DateTime.Now : DateTime.Parse(termDate); + } + + public string DisplayName { get; } + public string EmployeeNumber { get; } + public string LoginName { get; } + public string ManagerKey { get; } + public DateTime TerminationDate { get; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DepartmentDetails.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DepartmentDetails.cs new file mode 100644 index 0000000..0e92a47 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DepartmentDetails.cs @@ -0,0 +1,10 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models; + +public class DepartmentDetails +{ + public Department Department { get; set; } + public IReadOnlyCollection Assignments { get; set; } + public AppUser RegionalManager { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DepartmentGoals.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DepartmentGoals.cs new file mode 100644 index 0000000..37cdbb7 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DepartmentGoals.cs @@ -0,0 +1,30 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models; + +public class DepartmentGoals +{ + public DepartmentGoals(int departmentId, int year) + { + DepartmentId = departmentId; + Year = year; + + MonthlyGoals = new PerProductGoal[12]; + for (var month = 0; month < 12; month++) + { + MonthlyGoals[month] = new PerProductGoal(); + } + } + + public int DepartmentId { get; } + public int Year { get; } + public PerProductGoal[] MonthlyGoals { get; } + + public void ApplyAll(ICollection findDepartmentGoals) + { + foreach (var goal in findDepartmentGoals) + { + MonthlyGoals[goal.StartDate.Month-1].ProductGoals[goal.Product] = goal.Value; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DepartmentGoalsForRange.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DepartmentGoalsForRange.cs new file mode 100644 index 0000000..d36fbc4 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DepartmentGoalsForRange.cs @@ -0,0 +1,29 @@ +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models; + +public class DepartmentGoalsForRange +{ + public DepartmentGoalsForRange(int departmentId, MonthRange monthRange) + { + DepartmentId = departmentId; + MonthRange = monthRange; + Goals = new PerProductGoal(); + GoalAudit = new List(); + } + + public int DepartmentId { get; set; } + public MonthRange MonthRange { get; set; } + public PerProductGoal Goals { get; } + public IEnumerable GoalAudit { get; set; } + + public void ApplyAll(ICollection goals) + { + GoalAudit = GoalAudit.Concat(goals); + foreach (var goal in goals) + { + Goals.ProductGoals[goal.Product] += goal.Value; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DepartmentProgress.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DepartmentProgress.cs new file mode 100644 index 0000000..25d178e --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/DepartmentProgress.cs @@ -0,0 +1,155 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models; + +public class DepartmentProgress : IViewTransformable +{ + private IReadOnlyCollection DepartmentRules { get; } + private IReadOnlyCollection PersonalRules { get; } + + public DepartmentProgress(int departmentId, MonthRange monthRange) + { + DepartmentId = departmentId; + MonthRange = monthRange; + DepartmentRules = new List(); + PersonalRules = new List(); + Progress = new PerProductActivity(); + ActivityAudit = new List(); + GoalAudit = new List(); + Goals = new PerProductGoal(); + EmployeeProgress = new Dictionary(); + EmployeeGoals = new Dictionary(); + } + + public DepartmentProgress( + int deptDepartmentId, + MonthRange monthRangeOfAssignment, + List employeeList, + IReadOnlyCollection departmentRules, + IReadOnlyCollection personalRules + ) : this(deptDepartmentId, monthRangeOfAssignment) + { + EmployeeList = employeeList; + DepartmentRules = departmentRules; + PersonalRules = personalRules; + foreach (var employeeId in employeeList) + { + EmployeeGoals[employeeId] = new PerProductGoal(); + EmployeeProgress[employeeId] = new PerProductActivity(); + } + } + + public int DepartmentId { get; set; } + public MonthRange MonthRange { get; } + public PerProductActivity Progress { get; } + public PerProductGoal Goals { get; } + public IEnumerable ActivityAudit { get; set; } + public IEnumerable GoalAudit { get; set; } + public IReadOnlyCollection EmployeeList { get; } + public IDictionary EmployeeProgress { get; } + public IDictionary EmployeeGoals { get; } + public int GapInsuranceEligibleActivityCount { get; private set; } + + public void ApplyActivity(ICollection activities) + { + ActivityAudit = ActivityAudit.Concat(activities); + + foreach (var product in ProductDetail.ProductDetails.Select(productDetail => productDetail.Product)) + { + foreach (var activity in activities) + { + var employeeId = activity.EmployeeId; + + if (product == Product.Vehicle && activity.Product == Product.Vehicle) + { + GapInsuranceEligibleActivityCount += activity.CalculatedValueTowardsGapInsurance(); + if (!EmployeeGoals.ContainsKey(employeeId)) + { + EmployeeGoals[activity.EmployeeId] = new PerProductGoal(); + }; + EmployeeGoals[activity.EmployeeId].ProductGoals[Product.GAPInsurance] += activity.CalculatedValueTowardsGapInsurance(); + }; + + if (!EmployeeProgress.ContainsKey(employeeId)) + { + EmployeeProgress[employeeId] = new PerProductActivity(); + } + + var calculatedValueTowardsOther = activity.CalculatedValueTowardsOther(product); + EmployeeProgress[employeeId].ProductOther[product] += calculatedValueTowardsOther; + Progress.ProductOther[product] += calculatedValueTowardsOther; + var isEmployeeRestrictedByPayoutRule = PersonalRules.Any(payoutRule => payoutRule.RestrictedByPayoutRule(activity, product)); + var isDepartmentRestrictedByPayoutRule = DepartmentRules.Any(payoutRule => payoutRule.RestrictedByPayoutRule(activity, product)); + + if (isEmployeeRestrictedByPayoutRule && isDepartmentRestrictedByPayoutRule) + { + continue; + } + + var calculatedValueTowardsGoal = activity.CalculatedValueTowardsGoal(product); + + if (!isEmployeeRestrictedByPayoutRule) + { + EmployeeProgress[employeeId].ProductActivity[product] += calculatedValueTowardsGoal; + } + + if (!isDepartmentRestrictedByPayoutRule) + { + Progress.ProductActivity[product] += calculatedValueTowardsGoal; + } + } + } + } + + public void ApplyGoals(ICollection goals) + { + GoalAudit = GoalAudit.Concat(goals); + + foreach (var goal in goals) + { + var product = goal.Product; + var employeeId = goal.EmployeeId; + var value = goal.Value; + + if (employeeId is null) + { + Goals.ProductGoals[product] += value; + continue; + } + + if (!EmployeeGoals.ContainsKey(employeeId)) + { + EmployeeGoals[employeeId] = new PerProductGoal(); + } + + EmployeeGoals[employeeId].ProductGoals[product] += value; + } + + if (GapInsuranceEligibleActivityCount == 0) + { + return; + } + + var gapInsurance = Product.GAPInsurance; + + if (!Goals.ProductGoals.ContainsKey(gapInsurance)) + { + Goals.ProductGoals.Add(gapInsurance, GapInsuranceEligibleActivityCount); + return; + } + + Goals.ProductGoals[gapInsurance] = GapInsuranceEligibleActivityCount; + } + + public DepartmentProgress TransformForView() + { + foreach (var activity in ActivityAudit) + { + activity?.TransformForView(); + } + + return this; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/EmployeeGoals.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/EmployeeGoals.cs new file mode 100644 index 0000000..15b2d8c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/EmployeeGoals.cs @@ -0,0 +1,41 @@ +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models; + +public class EmployeeGoals +{ + public EmployeeGoals(AppUser employee, MonthRange monthRange) + { + Employee = employee; + MonthRange = monthRange; + Goals = new Dictionary(); + Departments = employee.Departments.Where(dept => dept.StartDate.Date < monthRange.EndTime.Date && + (dept.EndDate == null || monthRange.StartTime.Date < dept.EndDate.Value.Date)).ToList(); + foreach (var department in Departments) + { + Goals[department.DepartmentId] = new PerProductGoal(); + } + + GoalAudit = new List(); + } + + public AppUser Employee { get; } + public MonthRange MonthRange { get; } + public ICollection Departments { get; } + public IDictionary Goals { get; } + public IEnumerable GoalAudit { get; set; } + + public void ApplyAll(ICollection goals) + { + GoalAudit = GoalAudit.Concat(goals); + foreach (var goal in goals) + { + if (Goals.Keys.Contains(goal.DepartmentId)) + { + Goals[goal.DepartmentId].ProductGoals[goal.Product] += goal.Value; + } + } + } + +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/EmployeeProgress.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/EmployeeProgress.cs new file mode 100644 index 0000000..0a8e1d9 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/EmployeeProgress.cs @@ -0,0 +1,95 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models; + +public class EmployeeProgress : IViewTransformable +{ + public EmployeeProgress(AppUser employee, MonthRange monthRange, IReadOnlyCollection personalRules) + { + Employee = employee; + MonthRange = monthRange; + PersonalRules = personalRules; + Progress = new Dictionary(); + Goals = new Dictionary(); + Departments = employee.Departments.Where(dept => dept.StartDate.Date < monthRange.EndTime.Date && + (dept.EndDate == null || monthRange.StartTime.Date < + dept.EndDate.Value.Date)).ToList(); + foreach (var department in Departments) + { + Progress[department.DepartmentId] = new PerProductActivity(); + Goals[department.DepartmentId] = new PerProductGoal(); + } + + ActivityAudit = new List(); + GoalAudit = new List(); + } + + public AppUser Employee { get; } + public MonthRange MonthRange { get; } + public ICollection Departments { get; } + public IDictionary Progress { get; } + public IDictionary Goals { get; } + public IEnumerable ActivityAudit { get; set; } + public IEnumerable GoalAudit { get; set; } + private IReadOnlyCollection PersonalRules { get; } + + public void ApplyAll(ICollection activities) + { + ActivityAudit = ActivityAudit.Concat(activities); + foreach (var product in ProductDetail.ProductDetails) + { + foreach (var activity in activities) + { + if (Progress.Keys.Contains(activity.DepartmentId) == false) + { + Progress[activity.DepartmentId] = new PerProductActivity(); + } + + if (!PersonalRules.Any(x => x.RestrictedByPayoutRule(activity, product.Product))) + { + Progress[activity.DepartmentId].ProductActivity[product.Product] += + activity.CalculatedValueTowardsGoal(product.Product); + } + + Progress[activity.DepartmentId].ProductOther[product.Product] += + activity.CalculatedValueTowardsOther(product.Product); + + if (product.Product == Product.Vehicle && activity.Product == Product.Vehicle) + { + if (!Goals.ContainsKey(activity.DepartmentId)) + { + Goals[activity.DepartmentId] = new PerProductGoal(); + } + + Goals[activity.DepartmentId].ProductGoals[Product.GAPInsurance] += activity.CalculatedValueTowardsGapInsurance(); + } + } + } + } + + public void ApplyGoals(ICollection findDepartmentGoals) + { + GoalAudit = GoalAudit.Concat(findDepartmentGoals); + foreach (var goal in findDepartmentGoals) + { + if (Goals.Keys.Contains(goal.DepartmentId) == false) + { + Goals[goal.DepartmentId] = new PerProductGoal(); + } + + Goals[goal.DepartmentId].ProductGoals[goal.Product] += goal.Value; + } + } + + public EmployeeProgress TransformForView() + { + foreach (var activity in ActivityAudit) + { + activity?.TransformForView(); + } + + return this; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ExternalActivities/IExternalActivity.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ExternalActivities/IExternalActivity.cs new file mode 100644 index 0000000..eaed6a6 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ExternalActivities/IExternalActivity.cs @@ -0,0 +1,13 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; + +public interface IExternalActivity +{ + public ActivityType GetActivityType(); + public Product GetProductType(); + public string GetSourceId(); + public SystemType GetSystemType(); + public Activity MapToActivity(); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ExternalActivities/NewActivityManagerReferral.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ExternalActivities/NewActivityManagerReferral.cs new file mode 100644 index 0000000..6e972e0 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ExternalActivities/NewActivityManagerReferral.cs @@ -0,0 +1,157 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; + +public class NewActivityManagerReferral : IExternalActivity +{ + public long ActivityId { get; set; } + public string OriginatorUserId { get; set; } + public long OriginatorPersNbr { get; set; } + public string OriginatorFirstName { get; set; } + public string OriginatorLastName { get; set; } + public string OriginatorMiddleInitial { get; set; } + public long? LandmarkCreditCardNumber { get; set; } + public long? DirectDepositAccountNumber { get; set; } + public string EmployeeEmail { get; set; } + public DateTime LastUpdatedDateTime { get; set; } + public string Campaign { get; set; } + public string Category { get; set; } + public string ReferralStation { get; set; } + public int IsInitialStation { get; set; } + public int IsFinalStation { get; set; } + public DateTime OriginationDateTime { get; set; } + public DateTime StationDateTime { get; set; } + public string OriginatingBranchName { get; set; } + public string ReferredPersonNbr { get; set; } + public string ReferredOrgNbr { get; set; } + public string ReferredPersOrgNbr { get; set; } + public string ReferredPersonName { get; set; } + public string ReferredOrgName { get; set; } + public string PreferredPhone { get; set; } + public string Comm1 { get; set; } + public string Comm2 { get; set; } + public string Comm3 { get; set; } + public string Comm4 { get; set; } + public string Comm5 { get; set; } + public string Comm6 { get; set; } + public string Comm7 { get; set; } + + private static readonly TimeZoneInfo AssumedSourceTimezone = TimeZoneConverter.TZConvert.GetTimeZoneInfo("America/Chicago"); + private readonly int IsInFinalStation = 1; + + public Activity MapToActivity() + { + return new Activity() + { + SourceId = GetSourceId(), + Product = GetProductType(), + Accepted = GetActivityDate(), + Type = ActivityType.Referral, + SourceSystem = GetSystemType(), + SourceCreated = TimeZoneInfo.ConvertTime(OriginationDateTime, AssumedSourceTimezone, TimeZoneInfo.Utc), + Detail = new ActivityDetail() + { + MemberName = ReferredOrgName ?? ReferredPersonName, + DnaPersonNbr = OriginatorPersNbr.ToString(), + SubjectPersonNbr = ReferredPersonNbr, + SubjectOrgNbr = ReferredOrgNbr, + StationName = ReferralStation, + BranchOrgName = OriginatingBranchName, + AccountNumber = GetAccountNumber() + }, + MetaData = DataParsingService.MapActivityMetaData(new List { PreferredPhone, Comm1, Comm2, Comm3, Comm4, Comm5, Comm6, Comm7 }), + DepartmentId = -1, + EmployeeId = null, + State = ActivityState.Created, + EmployeeEmail = EmployeeEmail + }; + } + + private DateTime GetActivityDate() + { + var isLicReferral = Campaign.Equals("LIC Referral", StringComparison.InvariantCultureIgnoreCase); + if (IsFinalStation == IsInFinalStation && isLicReferral) + { + return StationDateTime = StationDateTime.Date.AddHours(12); + } + + return DateTime.UtcNow.Date.AddHours(12); + } + + public ActivityType GetActivityType() + { + return ActivityType.Referral; + } + + public string GetSourceId() + { + return ActivityId.ToString(); + } + + public Product GetProductType() + { + if (!string.IsNullOrWhiteSpace(Campaign)) + { + if (Campaign.Equals("ATM Card", StringComparison.InvariantCultureIgnoreCase)) + { + return Product.ATMCard; + } + + if (Campaign.Equals("Check Card Referral", StringComparison.InvariantCultureIgnoreCase)) + { + return Product.CheckCard; + } + + if (Campaign.Equals("Checking Referral", StringComparison.InvariantCultureIgnoreCase)) + { + return Product.NewChecking; + } + + if (Campaign.Equals("Mortgage Referral", StringComparison.InvariantCultureIgnoreCase)) + { + return Product.Mortgage; + } + + if (Campaign.Equals("Direct Deposit", StringComparison.InvariantCultureIgnoreCase)) + { + return Product.DirectDeposit; + } + + if (Campaign.Equals("CC Balance Transfer", StringComparison.InvariantCultureIgnoreCase)) + { + return Product.CreditCardBalanceTransfer; + } + + if (Campaign.Equals("LIC Referral", StringComparison.InvariantCultureIgnoreCase)) + { + return Product.LIC; + } + + if (Campaign.Equals("First Mortgage Referral", StringComparison.InvariantCultureIgnoreCase)) + { + return Product.FirstMortgageReferral; + } + + if (Campaign.Equals("eStatement Referral", StringComparison.InvariantCultureIgnoreCase)) + { + return Product.eStatement; + } + } + + Log.Error($"Could not parse product type: {Campaign}for an incoming referral with id:{ActivityId} originating from person :{OriginatorPersNbr}"); + throw new Exception("Could not parse product type for incoming referral. See logs."); + } + + private string GetAccountNumber() + { + var accountNumber = (DirectDepositAccountNumber ?? LandmarkCreditCardNumber); + return accountNumber != null ? accountNumber.ToString() : null; + } + + public SystemType GetSystemType() + { + return SystemType.ActivityManager; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ExternalActivities/NewAtmCheckCardActivity.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ExternalActivities/NewAtmCheckCardActivity.cs new file mode 100644 index 0000000..aa5c1d0 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ExternalActivities/NewAtmCheckCardActivity.cs @@ -0,0 +1,102 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; + +public class NewAtmCheckCardActivity : IExternalActivity +{ + public long ActivityNbr { get; set; } + public DateTime ActivityDateTime { get; set; } + public long ResponsiblePersonNbr { get; set; } + public string ActivityTypeCde { get; set; } + public long AgreementNbr { get; set; } + public string CardState { get; set; } + public string CardType { get; set; } + public long AccountNbr { get; set; } + public string OwnerPersonNbr { get; set; } + public string OwnerOrgNbr { get; set; } + public string AcctStateAtActivity { get; set; } + public string AcctStatAtQueryTime { get; set; } + public DateTime AcctStateEffDateTime { get; set; } + public string AssociateFirst { get; set; } + public string AssociateMiddleInit { get; set; } + public string AssociateLast { get; set; } + public string EmployeeEmail { get; set; } + public string TaxOrgName { get; set; } + public string TaxPersonName { get; set; } + public string TaxPersonPhoneNumbers { get; set; } + public string TaxPersonEmailAddresses { get; set; } + + static readonly TimeZoneInfo AssumedSourceTimezone = TimeZoneConverter.TZConvert.GetTimeZoneInfo("America/Chicago"); + + public Activity MapToActivity() + { + return new Activity() + { + Product = GetProductType(), + Type = GetActivityType(), + SourceSystem = GetSystemType(), + SourceId = ActivityNbr.ToString(), + SourceCreated = TimeZoneInfo.ConvertTime(ActivityDateTime, AssumedSourceTimezone, TimeZoneInfo.Utc), + Detail = new ActivityDetail() + { + MemberName = TaxOrgName ?? TaxPersonName, + DnaPersonNbr = ResponsiblePersonNbr.ToString(), + SubjectPersonNbr = OwnerPersonNbr, + SubjectOrgNbr = OwnerOrgNbr, + AccountNumber = AccountNbr.ToString(), + AccountStatus = AcctStatAtQueryTime, + AgreementNumber = AgreementNbr.ToString() + }, + MetaData = DataParsingService.MapActivityMetaData(new List { TaxPersonPhoneNumbers, TaxPersonEmailAddresses }), + DepartmentId = -1, + EmployeeId = null, + EmployeeEmail = EmployeeEmail, + Accepted = ActivityDateTime.Date.AddHours(12), + State = ActivityState.Created + }; + } + + public ActivityType GetActivityType() + { + return ActivityType.Other; + } + + public Product GetProductType() + { + return CardType.Equals("ATM", StringComparison.InvariantCultureIgnoreCase) ? Product.ATMCard : Product.CheckCard; + } + + public string GetSourceId() + { + return ActivityNbr.ToString(); + } + + public List MapActivityMetaData() + { + var parsedMetaData = new List(); + if (!string.IsNullOrWhiteSpace(TaxPersonEmailAddresses)) + { + foreach (var item in TaxPersonEmailAddresses.Split(",", StringSplitOptions.RemoveEmptyEntries)) + { + parsedMetaData.Add(DataParsingService.DetermineActivityMetaData(item)); + } + } + + if (!string.IsNullOrWhiteSpace(TaxPersonPhoneNumbers)) + { + foreach (var item in TaxPersonPhoneNumbers.Split(",", StringSplitOptions.RemoveEmptyEntries)) + { + parsedMetaData.Add(DataParsingService.DetermineActivityMetaData(item)); + } + } + + return parsedMetaData; + } + + public SystemType GetSystemType() + { + return SystemType.Dna; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ExternalActivities/NewCheckingAccountActivity.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ExternalActivities/NewCheckingAccountActivity.cs new file mode 100644 index 0000000..95b06b0 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ExternalActivities/NewCheckingAccountActivity.cs @@ -0,0 +1,94 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApi.Models.Constants; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; + +public class NewCheckingAccountActivity : IExternalActivity +{ + public long ActivityNbr { get; set; } + public DateTime ActivityDateTime { get; set; } + public long ResponsiblePersonNbr { get; set; } + public string ActivityTypeCode { get; set; } + public long SubjectAccountNbr { get; set; } + public string MinorAccountType { get; set; } + public string AssociateFirst { get; set; } + public string AssociateMiddleInit { get; set; } + public string AssociateLast { get; set; } + public string TaxPersonNbr { get; set; } + public string TaxOrgNbr { get; set; } + public string CurrentAccountStatus { get; set; } + public long BranchOrgNbr { get; set; } + public string BranchOrgName { get; set; } + public string EmployeeEmail { get; set; } + public DateTime AccountContractDate { get; set; } + public string TaxOrgName { get; set; } + public string TaxPersonName { get; set; } + public double? OriginalBalance { get; set; } + public string TaxPersonPhoneNumbers { get; set; } + public string TaxPersonEmailAddresses { get; set; } + + public Activity MapToActivity() + { + return new Activity() + { + Product = GetProductType(), + Type = ActivityType.Sale, + SourceSystem = GetSystemType(), + SourceId = GetSourceId(), + SourceCreated = AccountContractDate, + Detail = new ActivityDetail() + { + MemberName = TaxOrgName ?? TaxPersonName, + DnaPersonNbr = ResponsiblePersonNbr.ToString(), + BranchOrgNbr = BranchOrgNbr.ToString(), + BranchOrgName = BranchOrgName, + SubjectPersonNbr = TaxPersonNbr, + SubjectOrgNbr = TaxOrgNbr, + AccountNumber = SubjectAccountNbr.ToString(), + AccountStatus = CurrentAccountStatus, + AccountBalance = OriginalBalance + }, + MetaData = DataParsingService.MapActivityMetaData(new List { TaxPersonPhoneNumbers, TaxPersonEmailAddresses }), + DepartmentId = -1, + EmployeeId = null, + EmployeeEmail = StandardizedEmail(), + Accepted = ActivityDateTime.Date.AddHours(12), + State = ActivityState.Created + }; + } + + public ActivityType GetActivityType() + { + return ActivityType.Sale; + } + + public string GetSourceId() + { + return ActivityNbr.ToString(); + } + + public Product GetProductType() + { + return Product.NewChecking; + } + + public SystemType GetSystemType() + { + if ( + !string.IsNullOrWhiteSpace(EmployeeEmail) + && string.Equals(EmployeeEmail, SystemAppUserConstants.DigitalBankingEmail, StringComparison.CurrentCultureIgnoreCase) + ) + { + return SystemType.DigitalBanking; + } + + return SystemType.Dna; + } + + public string StandardizedEmail() + { + return !string.IsNullOrWhiteSpace(EmployeeEmail) ? EmployeeEmail.ToUpper() : EmployeeEmail; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ExternalActivities/NewLoanSaleActivity.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ExternalActivities/NewLoanSaleActivity.cs new file mode 100644 index 0000000..773bc5e --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ExternalActivities/NewLoanSaleActivity.cs @@ -0,0 +1,186 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; + +public class NewLoanSaleActivity : IExternalActivity +{ + public long ActivityNbr { get; set; } + public DateTime ActivityDateTime { get; set; } + public long SubjectAccountNbr { get; set; } + public long OriginatingPersonNbr { get; set; } + public string ActivityTypeCode { get; set; } + public string MajorAccountType { get; set; } + public string MinorAccountType { get; set; } + public string AssociateFirst { get; set; } + public string AssociateMiddleInit { get; set; } + public string AssociateLast { get; set; } + public string TaxPersonNbr { get; set; } + public string TaxOrgNbr { get; set; } + public string TaxOrgName { get; set; } + public string TaxPersonName { get; set; } + public DateTime AccountContractDate { get; set; } + public string CurrentAccountStatus { get; set; } + public long BranchOrgNbr { get; set; } + public string BranchOrgName { get; set; } + public string EmployeeEmail { get; set; } + public string LoanSourceCode { get; set; } + public decimal? LoanAmount { get; set; } + public DateTime? DebtProtectionEffectiveDate { get; set; } + public DateTime? DebtProtectionInactiveDate { get; set; } + public string DebtProtectionCategory { get; set; } + public DateTime? YoungestBirthdateOnLoan { get; set; } + public int HasOrgRoles { get; set; } + public string TaxPersonPhoneNumbers { get; set; } + public string TaxPersonEmailAddresses { get; set; } + + public bool IsGapInsuranceEligible() + { + var isEligible = false; + if (!MinorAccountType.Equals("BE21", StringComparison.InvariantCultureIgnoreCase) && LoanAmount.HasValue && LoanAmount.Value <= 100000 && GetProductType() == Product.Vehicle) + { + isEligible = true; + } + Log.Information($"New Loan Sale ActivityNbr {ActivityNbr} - {GetProductType()} - {MinorAccountType} - {LoanAmount} GapEligible set: {isEligible} {DateTime.UtcNow} "); + return isEligible; + } + + public DateTime? GapInsuranceEffectiveDate { get; set; } + public DateTime? GapInsuranceInactiveDate { get; set; } + + + static readonly List DebtProtectionEligibleLoanTypes = new List() + { "DV01", "DV02", "DV03", "DV04", "DV05", "DV06", "DV07", "DV08", "SC01", "SC02", "SG01" }; + + static readonly List FirstMortgageEligibleLoanTypes = new List() + { "FNMA", "FRML", "FRMH","MPFR","MJ30","HM01","HM02","MF10","HM03","HM04","DEF3","ML01","ML03", + "MJ03","MJ05","MJ07","MF15","MF20","MF30","MC93","MA55","MS30","MS10","MS15","MS20","MS40","MC09", + "ML02","MA01","MP05","MA05","MC01","MI05","MC03","MJ55","MC91","MF40","MS05","MS07","MI03","MA03", + "MA07","MA27","ML23","ML21","MP25","MC21","MA21","MJ27","HM05","MA23","MA25","MI25","MJ23","MJ25" }; + + static readonly TimeZoneInfo AssumedSourceTimezone = TimeZoneConverter.TZConvert.GetTimeZoneInfo("America/Chicago"); + + public Product GetProductType() + { + if (string.IsNullOrWhiteSpace(MinorAccountType)) + { + Log.Error($"Null Loan Account Type: {MinorAccountType} for Activity with ActivityNbr: {ActivityNbr}"); + throw new Exception("Could Not Determine Loan Account Type"); + } + + if (MinorAccountType.Equals("HE01", StringComparison.InvariantCultureIgnoreCase) || + MinorAccountType.Equals("FE01", StringComparison.InvariantCultureIgnoreCase) || + MinorAccountType.Equals("MF01", StringComparison.InvariantCultureIgnoreCase) || + MinorAccountType.Equals("FE02", StringComparison.InvariantCultureIgnoreCase) || + MinorAccountType.Equals("FLML", StringComparison.InvariantCultureIgnoreCase)) + { + return Product.Mortgage; + } + + if (MinorAccountType.Equals("SC01", StringComparison.InvariantCultureIgnoreCase) || + MinorAccountType.Equals("SC02", StringComparison.InvariantCultureIgnoreCase) || + MinorAccountType.Equals("SG01", StringComparison.InvariantCultureIgnoreCase)) + { + return Product.DebtProtectionEligibleLoan; + } + + if (IsFirstMortgageLoan(MinorAccountType)) + { + return Product.FirstMortgageReferral; + } + + return Product.Vehicle; + } + + public bool IsFirstMortgageLoan(string minorAccountType) + { + if (!string.IsNullOrWhiteSpace(minorAccountType) && FirstMortgageEligibleLoanTypes.Contains(minorAccountType.ToUpperInvariant())) + { + return true; + } + + return false; + } + + public ActivityType GetActivityType() + { + return ActivityType.Sale; + } + + public Activity MapToActivity() + { + const int addHours = 12; + + return new Activity() + { + Product = GetProductType(), + Type = GetActivityType(), + SourceSystem = GetSystemType(), + SourceId = ActivityNbr.ToString(), + SourceCreated = AccountContractDate, + Detail = new ActivityDetail() + { + MemberName = TaxOrgName ?? TaxPersonName, + DnaPersonNbr = OriginatingPersonNbr.ToString(), + BranchOrgName = BranchOrgName, + BranchOrgNbr = BranchOrgNbr.ToString(), + SubjectPersonNbr = TaxPersonNbr, + SubjectOrgNbr = TaxOrgNbr, + AccountNumber = SubjectAccountNbr.ToString(), + AccountStatus = CurrentAccountStatus, + AccountBalance = (double?)LoanAmount, + DebtProtectionAddDate = DebtProtectionEffectiveDate?.Date.AddHours(addHours), + DebtProtectionRemoveDate = DebtProtectionInactiveDate?.Date.AddHours(addHours), + IsDebtProtectionEligible = IsEligibleForDebtProtection(), + IsGapInsuranceEligible = IsGapInsuranceEligible(), + GapInsuranceAddDate = GapInsuranceEffectiveDate?.Date.AddHours(addHours), + GapInsuranceRemoveDate = GapInsuranceInactiveDate?.Date.AddHours(addHours) + }, + MetaData = DataParsingService.MapActivityMetaData(new List { TaxPersonPhoneNumbers, TaxPersonEmailAddresses }), + DepartmentId = -1, + EmployeeId = null, + EmployeeEmail = EmployeeEmail, + Accepted = AccountContractDate.Date.AddHours(addHours), + State = ActivityState.Created + }; + } + + public string GetSourceId() + { + return ActivityNbr.ToString(); + } + + public bool IsEligibleForDebtProtection() + { + var age = AgeOfYoungestBorrower(); + if (age == null || !DebtProtectionEligibleLoanTypes.Contains(MinorAccountType)) + { + return false; + } + + return age < 69 && HasOrgRoles == 0 && TaxOrgNbr == null; + } + + public int? AgeOfYoungestBorrower() + { + if (YoungestBirthdateOnLoan == null) + { + return null; + } + + var now = TimeZoneInfo.ConvertTime(DateTime.UtcNow, AssumedSourceTimezone); + var age = now.Year - YoungestBirthdateOnLoan.Value.Year; + if (now.DayOfYear > YoungestBirthdateOnLoan.Value.DayOfYear) + { + age--; + } + + return age; + } + + public SystemType GetSystemType() + { + return SystemType.Dna; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/GoalDispersal/CustomWeightedDispersalRequest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/GoalDispersal/CustomWeightedDispersalRequest.cs new file mode 100644 index 0000000..7aab6ce --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/GoalDispersal/CustomWeightedDispersalRequest.cs @@ -0,0 +1,12 @@ +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.GoalDispersal; + +public class CustomWeightedDispersalRequest +{ + public int DepartmentId { get; set; } + public Product DispersalProduct { get; set; } + public Dictionary DispersalWeightByRole { get; set; } + public int month { get; set; } + public int year { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/GoalDispersal/DefaultWeightedGoalDispersalData.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/GoalDispersal/DefaultWeightedGoalDispersalData.cs new file mode 100644 index 0000000..d84e9b5 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/GoalDispersal/DefaultWeightedGoalDispersalData.cs @@ -0,0 +1,11 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.GoalDispersal; + +public class DefaultWeightedGoalDispersalData +{ + public int DepartmentId { get; set; } + public DateTime DispersalMonthStartDate { get; set; } + public List DispersalMonthPersonnel { get; set; } + public PerProductGoal DispersalMonthDepartmentGoals { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/GoalDispersal/DepartmentGoalChangeDispersalRequest.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/GoalDispersal/DepartmentGoalChangeDispersalRequest.cs new file mode 100644 index 0000000..ab447a8 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/GoalDispersal/DepartmentGoalChangeDispersalRequest.cs @@ -0,0 +1,13 @@ +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.GoalDispersal; + +public class DepartmentGoalExtrapolationRequest +{ + public DateTime DepartmentGoalStartDate { get; set; } + public DateTime DepartmentGoalEndDate { get; set; } + public int OriginalDepartmentGoal { get; set; } + public int NewDepartmentGoal { get; set; } + public int DepartmentId { get; set; } + public Product Product; +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/GoalDispersal/ExtrapolationGoalDispersalData.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/GoalDispersal/ExtrapolationGoalDispersalData.cs new file mode 100644 index 0000000..3121542 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/GoalDispersal/ExtrapolationGoalDispersalData.cs @@ -0,0 +1,14 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.GoalDispersal; + +public class ExtrapolationGoalDispersalData +{ + public int DepartmentId { get; set; } + public DateTime DispersalMonthStartDate { get; set; } + public PersonnelGoals DispersalMonthPersonnelGoals { get; set; } + public PerProductGoal DispersalMonthDepartmentGoals { get; set; } + public PerProductGoal ModelDepartmentGoals { get; set; } + public PersonnelGoals ModelPersonnelGoals { get; set; } + public List DispersalMonthPersonnel { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/GoalDispersal/GoalDispersalReceipt.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/GoalDispersal/GoalDispersalReceipt.cs new file mode 100644 index 0000000..7cef5ed --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/GoalDispersal/GoalDispersalReceipt.cs @@ -0,0 +1,32 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.GoalDispersal; + +public class GoalDispersalReceipt +{ + public GoalDispersalReceipt(int deptId, GoalDispersalStrategy strategy) + { + GoalChanges = new List(); + GoalDispersalErrors = new List(); + DepartmentId = deptId; + Strategy = strategy; + } + public int DepartmentId { get; set; } + [JsonIgnore] + public List GoalChanges { get; set; } + public List GoalDispersalErrors { get; set; } + [JsonConverter(typeof(StringEnumConverter))] + public GoalDispersalStrategy Strategy { get; set; } + public void AddNewDispersalError(GoalDispersalError errorType, string employeeId, string message, string employeeName = null) + { + var newError = new GoalDispersalReceiptItem(errorType, employeeId, message); + if (!String.IsNullOrEmpty(employeeName)) + { + newError.EmployeeName = employeeName; + } + GoalDispersalErrors.Add(newError); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/GoalDispersal/GoalDispersalReceiptItem.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/GoalDispersal/GoalDispersalReceiptItem.cs new file mode 100644 index 0000000..2fe626e --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/GoalDispersal/GoalDispersalReceiptItem.cs @@ -0,0 +1,20 @@ +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.GoalDispersal; + +public class GoalDispersalReceiptItem +{ + public GoalDispersalReceiptItem(GoalDispersalError errorType, string employeeId, string message) + { + ErrorType = errorType; + EmployeeId = employeeId; + Message = message; + } + [JsonConverter(typeof(StringEnumConverter))] + public GoalDispersalError ErrorType { get; set; } + public string Message { get; set; } + public string EmployeeId { get; set; } + public string EmployeeName { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/IPayoutRule.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/IPayoutRule.cs new file mode 100644 index 0000000..75eb33e --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/IPayoutRule.cs @@ -0,0 +1,9 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models; + +public interface IPayoutRule +{ + public bool RestrictedByPayoutRule(Activity activity, Product countsForProduct); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Payout/DepartmentPayout.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Payout/DepartmentPayout.cs new file mode 100644 index 0000000..a8ed81d --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Payout/DepartmentPayout.cs @@ -0,0 +1,73 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.Payout; + +public class DepartmentPayout: IViewTransformable +{ + public DepartmentPayout(AppUser employee, UserDepartment assignment, MonthRange monthRange, IReadOnlyCollection departmentRules, IReadOnlyCollection personalRules) + { + Employee = employee; + MonthRange = monthRange; + DepartmentRules = departmentRules; + PersonalRules = personalRules; + Department = assignment; + + EligibleCommissions = new List(); + EligiblePayouts = PayoutProfile.FindForRole(assignment.Role); + PersonalActivity = new List(); + PersonalGoals = new List(); + DepartmentActivity = new List(); + DepartmentGoals = new List(); + CommissionPayouts = new List(); + GoalPayouts = new List(); + } + + public AppUser Employee { get; } + public MonthRange MonthRange { get; } + public UserDepartment Department { get; } + public IEnumerable EligibleCommissions { get; set; } + public IEnumerable EligiblePayouts { get; } + public IEnumerable PersonalActivity { get; set; } + public IEnumerable PersonalGoals { get; set; } + public IEnumerable DepartmentActivity { get; set; } + public IEnumerable DepartmentGoals { get; set; } + public IEnumerable CommissionPayouts { get; set; } + public IEnumerable GoalPayouts { get; set; } + public IReadOnlyCollection DepartmentRules { get; } + public IReadOnlyCollection PersonalRules { get; } + + public bool HasEligibleIncentives() + { + return HasEligibleCommissions() || HasEligibleGoalPayouts(); + } + + public DepartmentPayout TransformForView() + { + foreach(var activity in DepartmentActivity) + { + activity?.TransformForView(); + } + foreach(var activity in PersonalActivity) + { + activity?.TransformForView(); + } + return this; + } + + public bool HasEligibleActivity() + { + return PersonalActivity != null && PersonalActivity.Any() || + DepartmentActivity != null && DepartmentActivity.Any(); + } + + public bool HasEligibleCommissions() + { + return EligibleCommissions != null && EligibleCommissions.Any(); + } + + public bool HasEligibleGoalPayouts() + { + return EligiblePayouts != null && EligiblePayouts.Any(); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Payout/PayoutStyle.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Payout/PayoutStyle.cs new file mode 100644 index 0000000..f81e63d --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Payout/PayoutStyle.cs @@ -0,0 +1,7 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Models.Payout; + +public enum PayoutStyle +{ + Commission, + Goal +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/PerProductActivity.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/PerProductActivity.cs new file mode 100644 index 0000000..4110ea0 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/PerProductActivity.cs @@ -0,0 +1,20 @@ +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models; + +public class PerProductActivity +{ + public PerProductActivity() + { + ProductActivity = new Dictionary(); + ProductOther = new Dictionary(); + foreach (Product product in Enum.GetValues(typeof(Product))) + { + ProductActivity[product] = 0; + ProductOther[product] = 0; + } + } + + public IDictionary ProductActivity { get; } + public IDictionary ProductOther { get; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/PerProductGoal.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/PerProductGoal.cs new file mode 100644 index 0000000..799d2cd --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/PerProductGoal.cs @@ -0,0 +1,16 @@ +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models; + +public class PerProductGoal +{ + public PerProductGoal() + { + ProductGoals = new Dictionary(); + foreach (Product product in Enum.GetValues(typeof(Product))) + { + ProductGoals[product] = 0; + } + } + public IDictionary ProductGoals { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/PersonnelGoals.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/PersonnelGoals.cs new file mode 100644 index 0000000..3c33736 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/PersonnelGoals.cs @@ -0,0 +1,38 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models; + +public class PersonnelGoals +{ + public PersonnelGoals(int departmentId, int year, int month, ICollection personnel) + { + DepartmentId = departmentId; + Year = year; + Month = month; + Personnel = personnel; + Goals = new Dictionary(); + foreach (var person in personnel) + { + Goals[person.EmployeeId] = new PerProductGoal(); + } + } + + public int DepartmentId { get; } + public int Year { get; } + public int Month { get; } + public ICollection Personnel { get; } + public ICollection GoalAudit { get; set; } + public IDictionary Goals { get; } + + public void ApplyAll(ICollection findDepartmentGoals) + { + GoalAudit = findDepartmentGoals; + foreach (var goal in findDepartmentGoals) + { + if (Goals.Keys.Contains(goal.EmployeeId)) + { + Goals[goal.EmployeeId].ProductGoals[goal.Product] = goal.Value; + } + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ProgressView/IProgressView.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ProgressView/IProgressView.cs new file mode 100644 index 0000000..e4f5e6d --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ProgressView/IProgressView.cs @@ -0,0 +1,9 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.ProgressView; + +public interface IProgressView +{ + void ApplyActivity(ICollection activities); + void ApplyGoals(ICollection findDepartmentGoals); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ProgressView/ProgressType.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ProgressView/ProgressType.cs new file mode 100644 index 0000000..9f43a77 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ProgressView/ProgressType.cs @@ -0,0 +1,7 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Models.ProgressView; + +public enum ProgressType +{ + Employee, + Department +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/RegionProgress.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/RegionProgress.cs new file mode 100644 index 0000000..a138d01 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/RegionProgress.cs @@ -0,0 +1,27 @@ +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models; + +public class RegionProgress: IViewTransformable +{ + public RegionProgress(Region region, MonthRange monthRange) + { + Region = region; + MonthRange = monthRange; + Departments = new List(); + } + + public Region Region { get; } + public MonthRange MonthRange{ get; } + public IList Departments { get; } + + public RegionProgress TransformForView() + { + foreach(var department in Departments) + { + department?.TransformForView(); + } + return this; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ReportSettings.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ReportSettings.cs new file mode 100644 index 0000000..1268f8c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/ReportSettings.cs @@ -0,0 +1,8 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Models; + +public class ReportSettings +{ + public string Name { get; set; } + public string RequiredPermissionCode { get; set; } + public string Url { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Settings.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Settings.cs new file mode 100644 index 0000000..dd62565 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Settings.cs @@ -0,0 +1,118 @@ +using System.Reflection; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models; + +public class Settings +{ + public Dictionary ActivityStateSet { get; set; } + public Dictionary ActivityTypeSet { get; set; } + public List AppRoles { get; set; } + public IReadOnlyCollection AuthorizedReports { get; set; } + public List BusinessLines { get; set; } + public Dictionary DepartmentStateSet { get; set; } + public Dictionary OrganizationSet { get; set; } + public List PayoutProfiles { get; set; } + public List Permissions { get; set; } + public List ProductDetails { get; } + public Product[] ProductSet { get; set; } + public List Regions { get; set; } + public Dictionary SystemTypeSet { get; set; } + public Dictionary UserTypeSet { get; set; } + public IReadOnlyCollection Departments { get; set; } + public IReadOnlyCollection Employees { get; set; } + + public DateTime NextBonusPeriod { get; set; } + + public Settings() + { + ActivityStateSet = new Dictionary(); + ActivityTypeSet = new Dictionary(); + ProductSet = (Product[]) Enum.GetValues(typeof(Product)); + ProductDetails = ProductDetail.ProductDetails; + SystemTypeSet = new Dictionary(); + UserTypeSet = new Dictionary(); + OrganizationSet = new Dictionary(); + BusinessLines = new List(); + DepartmentStateSet = new Dictionary(); + Permissions = new List(); + Regions = Region.Regions; + PayoutProfiles = PayoutProfile.ActivePayoutProfiles; + + foreach (int key in Enum.GetValues(typeof(ActivityState))) + { + ActivityStateSet[key.ToString()] = Enum.GetName(typeof(ActivityState), key); + } + + foreach (int key in Enum.GetValues(typeof(ActivityType))) + { + ActivityTypeSet[key.ToString()] = Enum.GetName(typeof(ActivityType), key); + } + + foreach (int key in Enum.GetValues(typeof(SystemType))) + { + SystemTypeSet[key.ToString()] = Enum.GetName(typeof(SystemType), key); + } + + foreach (int key in Enum.GetValues(typeof(UserType))) + { + UserTypeSet[key.ToString()] = Enum.GetName(typeof(UserType), key); + } + + foreach (int key in Enum.GetValues(typeof(DepartmentState))) + { + DepartmentStateSet[key.ToString()] = Enum.GetName(typeof(DepartmentState), key); + } + + foreach (var field in typeof(Organization).GetFields(BindingFlags.Static | BindingFlags.Public)) + { + if (field.FieldType != typeof(Organization)) + { + continue; + } + var org = field.GetValue(field.Name) as Organization; + if (org == null) + { + continue; + } + OrganizationSet[org.OrgCode] = org.Name; + } + + foreach (var field in typeof(BusinessLine).GetFields(BindingFlags.Static | BindingFlags.Public)) + { + if (field.FieldType != typeof(BusinessLine)) + { + continue; + } + var bl = field.GetValue(field.Name) as BusinessLine; + if (bl != null) + { + BusinessLines.Add(bl); + } + + } + + AppRoles = Role.GetRolesList(); + + Permissions = EnumInstances(); + } + + private List EnumInstances() where T : class + { + var foundInstances = new List(); + + foreach (var field in typeof(T).GetFields(BindingFlags.Static | BindingFlags.Public)) + { + if (field.FieldType != typeof(T)) + { + continue; + } + if (field.GetValue(field.Name) is T instance) + { + foundInstances.Add(instance); + } + } + return foundInstances; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Temenos/DirectVehicleSaleRecord.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Temenos/DirectVehicleSaleRecord.cs new file mode 100644 index 0000000..fe373c0 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Temenos/DirectVehicleSaleRecord.cs @@ -0,0 +1,13 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Models.Temenos; + +public class DirectVehicleSaleRecord +{ + public string ApplicantSSN { get; set; } + public string ApplicationID { get; set; } + public string CreatedDate { get; set; } + public string AccountNumber { get; set; } + public string ApplicantName { get; set; } + public string ReferringEmployeeEmail { get; set; } + public string AmountFinanced { get; set; } + public string SubProduct { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Temenos/IndirectVehicleSaleRecord.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Temenos/IndirectVehicleSaleRecord.cs new file mode 100644 index 0000000..ef59219 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Temenos/IndirectVehicleSaleRecord.cs @@ -0,0 +1,17 @@ +using CsvHelper.Configuration.Attributes; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.Temenos; + +public class IndirectVehicleSaleRecord +{ + public string ApplicantSSN { get; set; } + public string AccountNumber { get; set; } + public string ApplicantName { get; set; } + public string CreatedDate { get; set; } + public string DisbursedDate { get; set; } + public string ApplicationID { get; set; } + public string SubProduct { get; set; } + public string AmountFinanced { get; set; } + [Ignore] + public string ReferringEmployeeEmail { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Temenos/TemenosCreditCardSale.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Temenos/TemenosCreditCardSale.cs new file mode 100644 index 0000000..f883ea1 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Temenos/TemenosCreditCardSale.cs @@ -0,0 +1,72 @@ +using CsvHelper.Configuration.Attributes; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Models.Action; +using LCU.API.Internal.Operations.IncentiveApi.Models.Constants; + +namespace LCU.API.Internal.Operations.IncentiveApi.Models.Temenos; + +public class TemenosCreditCardSale +{ + public string ApplicationID { get; set; } + public string SubProduct { get; set; } + public string FinalLoanAmount { get; set; } + public string DisbursedDate { get; set; } + public string ApplicantFirstName { get; set; } + public string ApplicantLastName { get; set; } + public string OrganizationName { get; set; } + public string LOResponsibleEmployeeEmail { get; set; } + public string LODirectLoanOfficer { get; set; } + public string ReferringEmployeeEmail { get; set; } + [Ignore] + public bool IsPersonal { get => SubProduct.StartsWith(TemenosImportConstants.PersonalCardSubProduct); } + [Ignore] + public string MemberName { get => IsPersonal ? $"{ApplicantFirstName} {ApplicantLastName}" : OrganizationName; } + + public Activity CreateActivity(AppUser appUser) + { + var homeDept = appUser.FindHomeDepartment(); + return new Activity() + { + Product = Product.CreditCard, + Type = ActivityType.Sale, + State = ActivityState.Accepted, + SourceSystem = SystemType.Temenos, + SourceCreated = DateTime.Parse(DisbursedDate), + Accepted = DateTime.Parse(DisbursedDate).Date.AddHours(12), + SourceId = ApplicationID, + Detail = new ActivityDetail() + { + MemberName = MemberName, + AccountBalance = IActionFileObject.ParseMoneyDouble(FinalLoanAmount), + AccountStatus = SubProduct + }, + AppUserId = appUser.Id, + EmployeeEmail = appUser.AdEmail, + EmployeeId = appUser.EmployeeId, + DepartmentId = homeDept.DepartmentId + }; + } + + public DeadActivity CreateDeadActivity(DeadActivityReason reasonCode, string note, string employeeEmail = null, string employeeId = null) + { + return new DeadActivity(ApplicationID, SystemType.Action, reasonCode, note) + { + Product = Product.CreditCard, + Type = ActivityType.Sale, + State = ActivityState.Accepted, + SourceSystem = SystemType.Temenos, + SourceCreated = DateTime.Parse(DisbursedDate), + Accepted = DateTime.Parse(DisbursedDate).Date.AddHours(12), + SourceId = ApplicationID, + DeadDetail = new DeadActivityDetail() + { + MemberName = MemberName, + AccountBalance = IActionFileObject.ParseMoneyDouble(FinalLoanAmount), + AccountStatus = SubProduct + }, + EmployeeEmail = employeeEmail, + EmployeeId = employeeId, + }; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Temenos/VehicleSaleRecord.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Temenos/VehicleSaleRecord.cs new file mode 100644 index 0000000..56e3509 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Models/Temenos/VehicleSaleRecord.cs @@ -0,0 +1,12 @@ +namespace LCU.API.Internal.Operations.IncentiveApi.Models.Temenos; + +public class VehicleSaleRecord +{ + public string ApplicantSSN { get; set; } + public string AccountNumber { get; set; } + public string ApplicantName { get; set; } + public string CreatedDate { get; set; } + public string ApplicationID { get; set; } + public string SubProduct { get; set; } + public string AmountFinanced { get; set; } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Program.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Program.cs new file mode 100644 index 0000000..e846095 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Program.cs @@ -0,0 +1,553 @@ +using System.IO; +using System.Reflection; +using Microsoft.AspNetCore.Builder; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.FeatureManagement; +using Microsoft.OpenApi.Models; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json.Converters; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.API.Internal.Operations.IncentiveApi.DataAccess; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApi.Services; +using LCU.API.Internal.Operations.IncentiveApi.Services.Action; +using LCU.API.Internal.Operations.IncentiveApi.Services.Allied; +using LCU.API.Internal.Operations.IncentiveApi.Authorization; +using LCU.API.Internal.Operations.IncentiveApi.Consumers; +using LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision; +using LCU.API.Internal.Operations.IncentiveApi.Services.Dayforce; +using LCU.API.Internal.Operations.IncentiveApi.Services.DispersalStrategy; +using LCU.API.Internal.Operations.IncentiveApi.Services.DuplicateDetection; +using LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.API.Internal.Operations.IncentiveApi.Services.Payout; +using LCU.API.Internal.Operations.IncentiveApi.Services.QueueServices; +using LCU.API.Internal.Operations.IncentiveApi.Services.Rules; +using LCU.API.Internal.Operations.IncentiveApi.Services.Temenos; +using LCU.API.Internal.Operations.IncentiveApi.Services.Reporting; +using LCU.API.Internal.Operations.IncentiveApi.Services.DnaActivityServices; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.LcuHttpClient; +using IAuthorizationService = LCU.API.Internal.Operations.IncentiveApi.Services.IAuthorizationService; +using LCU.Library.Internal.LcuMassTransit; +using LCU.Library.Internal.LcuMassTransitModels; +using LCU.Library.Internal.Logging.Models; +using LCU.Library.Internal.Logging; +using LCU.Library.Internal.Logging.Middleware; +using LCU.Library.Internal.Status; +using LCU.API.Internal.Operations.IncentiveApi.Services.IncentiveProcesses; +using LCU.Library.Internal.LCUServiceFactory.Configurations; +using LCU.Library.Internal.Configuration; +using LCU.Library.Internal.LCUServiceFactory; +using LCU.API.Internal.Operations.IncentiveApi.DnaActivityServices; +using LCU.API.Internal.Operations.IncentiveApi.HostedServices; +using LCU.API.Internal.Operations.IncentiveApi.Services.NightlyIncentiveProcesses; +using LCU.API.Internal.Operations.IncentiveApi.Services.NightlyDecisionServices; +using LCU.Library.Internal.Security; +using MassTransit; + +string MyAllowSpecificOrigins = "_myAllowSpecificOrigins"; +ApiConfiguration _apiConfiguration; + +var builder = BuildBuilder(args); +BuildServicesAndConfigurations(builder); +BuildAppAndRun(builder); + +WebApplicationBuilder BuildBuilder(string[] args) +{ + var builder = WebApplication.CreateBuilder(args); + builder.Configuration.AddJsonFile("/app/config/mapped-appsettings/primary.json", true, true); + builder.Configuration.AddJsonFile("/vault/secrets/incentive-manager-api-mass-queue-password.json", optional: true, reloadOnChange: true); + builder.Configuration.AddJsonFile("/vault/secrets/incentive-manager-api-mass-queue-username.json", optional: true, reloadOnChange: true); + builder.Configuration.AddJsonFile("/vault/secrets/incentive-manager-api-dayforce-username.json", optional: true, reloadOnChange: true); + builder.Configuration.AddJsonFile("/vault/secrets/incentive-manager-api-dayforce-password.json", optional: true, reloadOnChange: true); + builder.Configuration.AddJsonFile("/vault/secrets/incentive-manager-api-seq-api-key.json", optional: true, reloadOnChange: true); + builder.Configuration.AddJsonFile("/vault/secrets/incentive-manager-api-db-connection.json", optional: true, reloadOnChange: true); + builder.Configuration.AddJsonFile("/vault/secrets/incentive-manager-api-azure-valid-audience.json", optional: true, reloadOnChange: true); + builder.Configuration.AddJsonFile("/vault/secrets/incentive-manager-api-azure-client-id.json", optional: true, reloadOnChange: true); + builder.Configuration.AddJsonFile("/vault/secrets/incentive-manager-api-azure-tenant-id.json", optional: true, reloadOnChange: true); + builder.Host.UseSerilog(); + return builder; +} + +void BuildServicesAndConfigurations(WebApplicationBuilder builder) +{ + var services = builder.Services; + var configuration = builder.Configuration; + + _apiConfiguration = configuration.Bind(services, "Primary"); + configuration.Bind(services); + configuration.Bind(services); + configuration.Bind(services); + configuration.Bind(services); + configuration.Bind(services); + configuration.Bind(services); + + var servicesFactoryConfiguration = configuration.Bind(services); + + var loggerConfiguration = new LoggingConfiguration(_apiConfiguration.AppName, + _apiConfiguration.SeqKey, + _apiConfiguration.SeqUrl); + var lcuLogging = new LcuLogging(loggerConfiguration); + lcuLogging.UseLcuLogging(); + + services + .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddAzureAdBearer(options => configuration.Bind("AzureActiveDirectory", options)); + + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo + { + Title = _apiConfiguration.AppName, + Version = "v1" + }); + + c.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Id = "Bearer", + Type = ReferenceType.SecurityScheme + } + }, + new List() + } + }); + + c.AddSecurityDefinition("Bearer", + new OpenApiSecurityScheme + { + Description = "JWT Authorization header using the Bearer scheme.", + Type = SecuritySchemeType.Http, + Scheme = "bearer" + }); + + var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); + c.IncludeXmlComments(xmlPath); + }); + + services.AddFeatureManagement(configuration.GetSection("Primary:FeatureGates")); + + services.AddScoped(); + services.AddTransient(); + services.AddSingleton(); + services.AddScoped(); + + services.AddCors(options => + { + options.AddPolicy(MyAllowSpecificOrigins, + builder => + { + builder.WithOrigins( + _apiConfiguration.CorsAllowedOrigins + ).AllowAnyHeader().AllowAnyMethod().AllowCredentials(); + }); + }); + + services.AddScoped(provider => provider.GetService()); + var sqlConnectionConfiguration = configuration.Bind(services); + + services.AddDbContext(options => + { + options.UseSqlServer + ( + sqlConnectionConfiguration.ConnectionString, + sql => sql.EnableRetryOnFailure(sqlConnectionConfiguration.MaxRetryCount, sqlConnectionConfiguration.MaxRetryDelay, null) + ); + }); + + var connectionConfiguration = MassTransitConfigurationSetUp.AddConnectionConfiguration(ref services, configuration); + + var receiveEndpointConfigurations = new ReceiveEndpointConfigurations + { + ImportCreditCardSalesEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration, "ImportCreditCardSalesEndpoint"), + ImportIndirectVehicleSalesEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration, "ImportIndirectVehicleSalesEndpoint"), + ReportingProcessEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration, "ReportingProcessEndpoint"), + GoalDispersalProcessEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration, "GoalDispersalProcessEndpoint"), + PayoutIncentiveProcessEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration, "PayoutIncentiveProcessEndpoint"), + DayforceSyncProcessEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration, "DayforceSyncProcessEndpoint"), + ActionCreditCardProcessEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration, "ActionCreditCardProcessEndpoint"), + ActionVehicleProcessEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration, "ActionVehicleProcessEndpoint"), + GapMbpProcessEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration, "GapMbpProcessEndpoint"), + IncentiveProcessesEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration, "IncentiveProcessesEndpoint"), + NightlyIncentiveProcessesEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration, "NightlyIncentiveProcessesEndpoint") + }; + + services.AddSingleton(receiveEndpointConfigurations); + AddMassTransitServices(ref services, connectionConfiguration, receiveEndpointConfigurations, _apiConfiguration); + + AddApiServices(services); + AddDataAccess(services); + AddApiRepositories(services); + + services.AddHttpContextAccessor(); + services.AddLCUStatusServicesWithApiHealthCheck(configuration); + services.AddLCUHttpServices(); + services.AddLCUServiceFactoryAndValidateServiceRegistration(servicesFactoryConfiguration); + services.AddLCUServiceFactoryAndValidateServiceRegistration(servicesFactoryConfiguration); + + services.Configure(options => + { + options.SuppressModelStateInvalidFilter = true; + }); + + services.AddControllers().AddNewtonsoftJson(options => + { + options.SerializerSettings.Converters.Add(new StringEnumConverter()); + options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore; + }); + + services.AddEndpointsApiExplorer(); +} + +void AddMassTransitServices(ref IServiceCollection services, + ConnectionConfiguration connectionConfiguration, + ReceiveEndpointConfigurations receiveEndpointConfigurations, + ApiConfiguration configuration) +{ + services.AddMassTransit(x => + { + x.AddConsumer(); + x.AddConsumer(); + x.AddConsumer(); + x.AddConsumer(); + x.AddConsumer(); + x.AddConsumer(); + x.AddConsumer(); + x.AddConsumer(); + x.AddConsumer(); + x.AddConsumer(); + x.AddConsumer(); + + x.UsingRabbitMq((context, cfg) => + { + cfg.Host(connectionConfiguration.HostUrl, "/", host => + { + host.Username(connectionConfiguration.UserName); + host.Password(connectionConfiguration.Password); + }); + + if (configuration.FeatureFlags.RunImportCreditCardSalesConsumer) + { + cfg.ReceiveEndpoint(receiveEndpointConfigurations.ImportCreditCardSalesEndpoint.ReceiveEndpoint, e => + { + e.ConfigureConsumer(context); + e.SetQuorumQueue(); + e.PrefetchCount = receiveEndpointConfigurations.ImportCreditCardSalesEndpoint.PrefetchLimit; + }); + } + + if (configuration.FeatureFlags.RunImportIndirectVehicleSalesConsumer) + { + cfg.ReceiveEndpoint(receiveEndpointConfigurations.ImportIndirectVehicleSalesEndpoint.ReceiveEndpoint, e => + { + e.ConfigureConsumer(context); + e.SetQuorumQueue(); + e.PrefetchCount = receiveEndpointConfigurations.ImportIndirectVehicleSalesEndpoint.PrefetchLimit; + }); + } + + if (configuration.FeatureFlags.RunReportingProcessConsumer) + { + cfg.ReceiveEndpoint(receiveEndpointConfigurations.ReportingProcessEndpoint.ReceiveEndpoint, e => + { + e.ConfigureConsumer(context); + e.SetQuorumQueue(); + e.PrefetchCount = receiveEndpointConfigurations.ReportingProcessEndpoint.PrefetchLimit; + }); + } + + if (configuration.FeatureFlags.RunGoalDispersalProcessConsumer) + { + cfg.ReceiveEndpoint(receiveEndpointConfigurations.GoalDispersalProcessEndpoint.ReceiveEndpoint, e => + { + e.ConfigureConsumer(context); + e.SetQuorumQueue(); + e.PrefetchCount = receiveEndpointConfigurations.GoalDispersalProcessEndpoint.PrefetchLimit; + }); + } + + if (configuration.FeatureFlags.RunPayoutIncentiveProcessConsumer) + { + cfg.ReceiveEndpoint(receiveEndpointConfigurations.PayoutIncentiveProcessEndpoint.ReceiveEndpoint, e => + { + e.ConfigureConsumer(context); + e.SetQuorumQueue(); + e.PrefetchCount = receiveEndpointConfigurations.PayoutIncentiveProcessEndpoint.PrefetchLimit; + }); + } + + if (configuration.FeatureFlags.RunDayforceSyncProcessConsumer) + { + cfg.ReceiveEndpoint(receiveEndpointConfigurations.DayforceSyncProcessEndpoint.ReceiveEndpoint, e => + { + e.ConfigureConsumer(context); + e.SetQuorumQueue(); + e.PrefetchCount = receiveEndpointConfigurations.DayforceSyncProcessEndpoint.PrefetchLimit; + }); + } + + if (configuration.FeatureFlags.RunGapMbpProcessConsumer) + { + cfg.ReceiveEndpoint(receiveEndpointConfigurations.GapMbpProcessEndpoint.ReceiveEndpoint, e => + { + e.ConfigureConsumer(context); + e.SetQuorumQueue(); + e.PrefetchCount = receiveEndpointConfigurations.GapMbpProcessEndpoint.PrefetchLimit; + }); + } + + if (configuration.FeatureFlags.RunActionCreditCardProcessConsumer) + { + cfg.ReceiveEndpoint(receiveEndpointConfigurations.ActionCreditCardProcessEndpoint.ReceiveEndpoint, e => + { + e.ConfigureConsumer(context); + e.SetQuorumQueue(); + e.PrefetchCount = receiveEndpointConfigurations.ActionCreditCardProcessEndpoint.PrefetchLimit; + }); + } + + if (configuration.FeatureFlags.RunActionVehicleProcessConsumer) + { + cfg.ReceiveEndpoint(receiveEndpointConfigurations.ActionVehicleProcessEndpoint.ReceiveEndpoint, e => + { + e.ConfigureConsumer(context); + e.SetQuorumQueue(); + e.PrefetchCount = receiveEndpointConfigurations.ActionVehicleProcessEndpoint.PrefetchLimit; + }); + } + + if (configuration.FeatureFlags.RunIncentiveProcessesConsumer) + { + cfg.ReceiveEndpoint(receiveEndpointConfigurations.IncentiveProcessesEndpoint.ReceiveEndpoint, e => + { + e.ConfigureConsumer(context); + e.SetQuorumQueue(); + e.PrefetchCount = receiveEndpointConfigurations.IncentiveProcessesEndpoint.PrefetchLimit; + }); + } + + if (configuration.FeatureFlags.RunNightlyIncentiveProcessesConsumer) + { + cfg.ReceiveEndpoint(receiveEndpointConfigurations.NightlyIncentiveProcessesEndpoint.ReceiveEndpoint, e => + { + e.ConfigureConsumer(context); + e.SetQuorumQueue(); + e.PrefetchCount = receiveEndpointConfigurations.NightlyIncentiveProcessesEndpoint.PrefetchLimit; + }); + } + }); + }); + services.AddMassTransitHostedService(); +} + +void AddApiServices(IServiceCollection services) +{ + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + AddQueueServices(services); + AddMicroServices(services); + AddSyncServices(services); + AddNightlyServices(services); + AddStrategyServices(services); + AddHostedServices(services); +} + +void AddQueueServices(IServiceCollection services) +{ + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); +} + +void AddSyncServices(IServiceCollection services) +{ + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); +} + +void AddNightlyServices(IServiceCollection services) +{ + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); +} + +void AddApiRepositories(IServiceCollection services) +{ + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); +} + +void AddDataAccess(IServiceCollection services) +{ + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); +} + +void AddMicroServices(IServiceCollection services) +{ + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); +} + +void AddHostedServices(IServiceCollection services) +{ + services.AddSingleton(); +} + +void AddStrategyServices(IServiceCollection services) +{ + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); +} + +void BuildAppAndRun(WebApplicationBuilder builder) +{ + var app = builder.Build(); + + if (app.Environment.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + if (_apiConfiguration.FeatureFlags.RunApplicationDbIntegrityHostedService) + { + var service = app.Services.GetRequiredService(); + service.StartAsync().Wait(); + } + + app.UseSwagger(c => + { + c.PreSerializeFilters.Add((swaggerDoc, request) => + { + const string prefixHeader = "X-Forwarded-Prefix"; + if (!request.Headers.ContainsKey(prefixHeader)) return; + + var serverUrl = request.Headers[prefixHeader]; + swaggerDoc.Servers = new List() + { + new() { Description = serverUrl, Url = serverUrl } + }; + }); + }); + + app.UseSwaggerUI(c => + { + c.RoutePrefix = "swagger"; + c.SwaggerEndpoint("v1/swagger.json", "LCU.API.Internal.Operations.IncentiveApi v1"); + }); + + app.UseMiddleware(); + app.UseSerilogRequestLogging(options => + { + options.EnrichDiagnosticContext = RequestResponseLoggingMiddleware.RequestDataParser.EnrichFromRequest; + }); + + app.UseRouting(); + app.UseAuthentication(); + app.UseAuthorization(); + app.UseCors(MyAllowSpecificOrigins); + app.UseHttpsRedirection(); + app.MapControllers(); + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + app.Run(); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Properties/launchSettings.json b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Properties/launchSettings.json new file mode 100644 index 0000000..ba20518 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:22532", + "sslPort": 44380 + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "LCU.API.Internal.Operations.IncentiveApi": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Local" + }, + "applicationUrl": "https://localhost:20001;http://localhost:20000" + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Local" + } + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Action/ActionCreditCardImportService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Action/ActionCreditCardImportService.cs new file mode 100644 index 0000000..1624249 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Action/ActionCreditCardImportService.cs @@ -0,0 +1,160 @@ +using System.Globalization; +using System.IO; +using CsvHelper; +using CsvHelper.Configuration; +using LCU.API.Internal.Operations.IncentiveApi.Models.Action; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.API.Internal.Operations.IncentiveApi.Services.Transformations; +using LCU.Library.Internal.ApiResponse.Services; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Results; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Action; + +public class ActionCreditCardImportService : IActionCreditCardImportService +{ + private readonly IUserRepository _userRepository; + private readonly IActivityService _activityService; + private readonly IDeadActivityService _deadActivityService; + private readonly IFileOperationsApiService _fileOperationsApiService; + private readonly ProcessResult _actionCreditCardResult; + + public ActionCreditCardImportService(IUserRepository userRepository, IFileOperationsApiService fileOperationsApiService, + IActivityService activityService, IDeadActivityService deadActivityService) + { + _userRepository = userRepository; + _fileOperationsApiService = fileOperationsApiService; + _activityService = activityService; + _deadActivityService = deadActivityService; + _actionCreditCardResult = new ProcessResult(); + } + + public async Task RunActionCreditCardProcess(ActionCreditCardRequest request) + { + try + { + var importedPersonal = await ImportTransactions("personal", request); + Log.Information($"Imported {importedPersonal.Count} credit cards - personal."); + + var importedBusiness = await ImportTransactions("business", request); + Log.Information($"Imported {importedBusiness.Count} credit cards - business."); + + _actionCreditCardResult.MessageStack.Add($"Imported {importedPersonal.Count()} personal and {importedBusiness.Count()} business credit cards."); + await QueueReturnResponse.QueueResponse(request, _actionCreditCardResult.Success, _actionCreditCardResult.MessageStack); + } + catch (Exception exception) + { + var message = $"There was an issue with handling Import Action Credit Card Sales Exception Message: {exception.Message}."; + _actionCreditCardResult.MessageStack.Add(message); + _actionCreditCardResult.Success = false; + await QueueReturnResponse.QueueResponse(request, _actionCreditCardResult.Success, _actionCreditCardResult.MessageStack); + throw new ServiceException(message); + } + } + + public async Task> ImportTransactions(string fileIdentifier, ActionCreditCardRequest request) where T : IActionFileObject + { + var dynamicFileListRequest = request.ToDynamicActionCreditCardFileListRequest(); + var files = await _fileOperationsApiService.BuildDynamicFileList(dynamicFileListRequest); + + var activity = new List(); + foreach (var fileName in files) + { + var stringContent = _fileOperationsApiService.GetFileContent($"{request.FinalBasePath}{fileName}"); + using var stringReader = new StringReader(stringContent.Result); + var config = new CsvConfiguration(CultureInfo.InvariantCulture) + { + Delimiter = ",", + HasHeaderRecord = true, + HeaderValidated = null, + MissingFieldFound = null, + IgnoreBlankLines = true + }; + + using var csv = new CsvReader(stringReader, config); + var records = csv.GetRecords().ToList(); + var allActivity = await CreateActivity(records); + await _activityService.SaveActivityRangeAsync(allActivity); + + activity.AddRange(allActivity); + } + + return activity; + } + + public async Task> CreateActivity(IEnumerable transactions) where T : IActionFileObject + { + var activities = new List(); + var deadActivities = new List(); + foreach (var actionSaleRow in transactions) + { + var email = actionSaleRow.GetEmployeeEmail(); + var employee = string.IsNullOrEmpty(email) ? null : await FindEmployee(email); + + if (employee == null) + { + email = actionSaleRow.GetLoanOfficerEmail(); + employee = string.IsNullOrEmpty(email) ? null : await FindEmployee(email); + } + + if (employee == null) + { + email = actionSaleRow.GetLoanOfficerEmailByName(); + employee = string.IsNullOrEmpty(email) ? null : await FindEmployee(email); + } + + if(employee == null){ + + Log.Warning($"Action Import, No user for {email}. Saving as Dead Activity."); + deadActivities.Add(actionSaleRow.CreateDeadActivity(DeadActivityReason.MissingRequiredField, $"Action Import, No user for {email}", email)); + continue; + } + + var homeDept = employee.FindHomeDepartment(); + if (homeDept == null) + { + Log.Warning($"User has no reliably determinable home department, {employee.EmployeeId}. Saving as Dead Activity."); + deadActivities.Add( + actionSaleRow.CreateDeadActivity(DeadActivityReason.MissingRequiredField, + $"User has no reliably determinable home department, {employee.EmployeeId}", email, employee.EmployeeId)); + continue; + } + + var activity = actionSaleRow.CreateActivity(employee); + if (activities.Any(x => x.SourceId == activity.SourceId)) + { + Log.Fatal( + $"Duplicate activity in credit card file for Incentive Manager, sourceId is {activity.SourceId}"); + continue; + } + activities.Add(activity); + } + + try + { + await _deadActivityService.SaveDeadActivityRange(deadActivities); + } + catch (Exception ex) + { + Log.Error(ex, "Failed to save dead activities from ActionCreditCardImportService. Dead Activities will be lost."); + } + return activities; + } + + private async Task FindEmployee(string employeeEmail) + { + try + { + return await _userRepository.FindUserByAdEmailAsync(employeeEmail); + } + catch(Exception ex) + { + Log.Warning(ex, "Error when finding user by email in ActionCreditCardImportService."); + return null; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Action/ActionVehicleImportService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Action/ActionVehicleImportService.cs new file mode 100644 index 0000000..cf9734b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Action/ActionVehicleImportService.cs @@ -0,0 +1,172 @@ +using System.Globalization; +using System.IO; +using CsvHelper; +using CsvHelper.Configuration; +using LCU.API.Internal.Operations.IncentiveApi.Models.Action; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.API.Internal.Operations.IncentiveApi.Services.Transformations; +using LCU.Library.Internal.IncentiveApiModels.Results; +using LCU.Library.Internal.ApiResponse.Services; +using LCU.Library.Internal.Exceptions; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Action; + +public class ActionVehicleImportService : IActionVehicleImportService +{ + private readonly IUserRepository _userRepository; + private readonly IActivityService _activityService; + private readonly IDeadActivityService _deadActivityService; + private readonly IFileOperationsApiService _fileOperationsApiService; + private readonly ProcessResult _actionVehicleProcessResult; + static readonly CultureInfo USEnglish = new CultureInfo("en-US"); + + public ActionVehicleImportService(IUserRepository userRepository, IFileOperationsApiService fileOperationsApiService, IActivityService activityService, IDeadActivityService deadActivityService) + { + _userRepository = userRepository; + _fileOperationsApiService = fileOperationsApiService; + _activityService = activityService; + _deadActivityService = deadActivityService; + _actionVehicleProcessResult = new ProcessResult(); + } + + public async Task RunActionVehicleProcess(ActionVehicleRequest request) + { + try + { + var importedVehicleSales = await ImportTransactions(request); + Log.Information($"Imported {importedVehicleSales.Count} Action Vehicle Sales."); + + _actionVehicleProcessResult.MessageStack.Add($"Imported {importedVehicleSales.Count()} Action Vehicle Sales."); + await QueueReturnResponse.QueueResponse(request, _actionVehicleProcessResult.Success, _actionVehicleProcessResult.MessageStack); + } + catch (Exception exception) + { + var message = $"There was an issue with handling Import Action Vehicle Sales Exception Message: {exception.Message}."; + _actionVehicleProcessResult.MessageStack.Add(message); + _actionVehicleProcessResult.Success = false; + await QueueReturnResponse.QueueResponse(request, _actionVehicleProcessResult.Success, _actionVehicleProcessResult.MessageStack); + throw new ServiceException(message); + } + } + + public async Task> ImportTransactions(ActionVehicleRequest request) where T : ActionSaleRow + { + var dynamicFileListRequest = request.ToDynamicActionVehicleFileListRequest(); + var files = await _fileOperationsApiService.BuildDynamicFileList(dynamicFileListRequest); + + var activity = new List(); + foreach (var fileName in files) + { + var stringContent = await _fileOperationsApiService.GetFileContent($"{request.FinalBasePath}{fileName}"); + using var stringReader = new StringReader(stringContent); + var config = new CsvConfiguration(CultureInfo.InvariantCulture) + { + Delimiter = ",", + HasHeaderRecord = true, + HeaderValidated = null, + MissingFieldFound = null, + IgnoreBlankLines = true + }; + + using var csv = new CsvReader(stringReader, config); + var records = csv.GetRecords().ToList(); + var allActivity = await CreateActivity(records); + await _activityService.SaveActivityRangeAsync(allActivity); + + activity.AddRange(allActivity); + } + + return activity; + } + + public async Task> CreateActivity(IEnumerable transactions) + { + var activities = new List(); + var deadActivities = new List(); + foreach (var actionSaleRow in transactions) + { + var activity = new Activity() + { + Product = Product.Vehicle, + Type = ActivityType.Sale, + State = ActivityState.Accepted, + SourceSystem = SystemType.Action, + SourceCreated = DateTime.Parse(actionSaleRow.OPEN_DATE), + Accepted = DateTime.Parse(actionSaleRow.OPEN_DATE).Date.AddHours(12), + SourceId = actionSaleRow.IndAppID, + Detail = new ActivityDetail() + { + AccountNumber = actionSaleRow.Account_Number, + MemberName = actionSaleRow.Borrower_Name, + AccountBalance = ParseMoneyDouble(actionSaleRow.IndAmtFinanced) + }, + EmployeeEmail = null, + EmployeeId = null, + DepartmentId = -1 + }; + + var employee = await FindEmployee(actionSaleRow.EmailAddress); + if (employee == null) + { + Log.Warning($"Action Import, No user for {actionSaleRow.EmailAddress}. Saving as Dead Activity."); + activity.EmployeeEmail = actionSaleRow.EmailAddress; + deadActivities.Add(new DeadActivity(activity, + DeadActivityReason.MissingRequiredField, + $"Action Import, No user for {actionSaleRow.EmailAddress}") + ); + continue; + } + + activity.AppUserId = employee.Id; + activity.EmployeeEmail = employee.AdEmail; + activity.EmployeeId = employee.EmployeeId; + + var homeDept = employee.FindHomeDepartment(); + if (homeDept == null) + { + Log.Warning($"User has no reliably determinable home department, {employee.EmployeeId}. Saving as Dead Activity."); + deadActivities.Add(new DeadActivity(activity, + DeadActivityReason.MissingRequiredField, + $"User has no reliably determinable home department, {employee.EmployeeId}. Saving as Dead Activity.")); + continue; + } + + activity.DepartmentId = homeDept.DepartmentId; + activities.Add(activity); + } + + try + { + await _deadActivityService.SaveDeadActivityRange(deadActivities); + } + catch (Exception ex) + { + Log.Error(ex, "Failed to save dead activities from ActionVehicleImportService. Dead Activities will be lost."); + } + + return activities; + } + + public double? ParseMoneyDouble(string indAmtFinanced) + { + var parsed = decimal.Parse(indAmtFinanced, System.Globalization.NumberStyles.Currency, USEnglish); + return (double)parsed; + } + + private async Task FindEmployee(string employeeEmail) + { + try + { + return await _userRepository.FindUserByAdEmailAsync(employeeEmail); + } + catch (Exception ex) + { + Log.Error(ex, "Error when finding user by email in ActionVehicleImportService."); + return null; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Action/IActionCreditCardImportService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Action/IActionCreditCardImportService.cs new file mode 100644 index 0000000..25d4c37 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Action/IActionCreditCardImportService.cs @@ -0,0 +1,12 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApi.Models.Action; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Action; + +public interface IActionCreditCardImportService +{ + Task RunActionCreditCardProcess(ActionCreditCardRequest request); + Task> ImportTransactions(string fileIdentifier, ActionCreditCardRequest request) where T : IActionFileObject; + Task> CreateActivity(IEnumerable transactions) where T : IActionFileObject; +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Action/IActionVehicleImportService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Action/IActionVehicleImportService.cs new file mode 100644 index 0000000..e2ad5cf --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Action/IActionVehicleImportService.cs @@ -0,0 +1,12 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApi.Models.Action; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Action; + +public interface IActionVehicleImportService +{ + Task RunActionVehicleProcess(ActionVehicleRequest request); + Task> ImportTransactions(ActionVehicleRequest request) where T : ActionSaleRow; + Task> CreateActivity(IEnumerable transactions); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ActivityDetailService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ActivityDetailService.cs new file mode 100644 index 0000000..2a4d247 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ActivityDetailService.cs @@ -0,0 +1,224 @@ +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public class ActivityDetailService : IActivityDetailService +{ + private readonly IActivityDetailUpdateRepository _activityDetailUpdateRepository; + private readonly IUnitOfWork _unitOfWork; + private readonly IDnaService _dnaService; + + public ActivityDetailService( + IActivityDetailUpdateRepository activityDetailUpdateRepository, + IUnitOfWork unitOfWork, + IDnaService dnaService + ) + { + _activityDetailUpdateRepository = activityDetailUpdateRepository; + _unitOfWork = unitOfWork; + _dnaService = dnaService; + } + + public async Task> GetAllActivityDetailUpdates(int activityDetailId) + { + return await _activityDetailUpdateRepository.GetActivityDetailUpdatesByActivityDetailId(activityDetailId); + } + + public async Task> TryUpdateActivityAccountDetails(List activities) + { + var accountDetails = await _dnaService.GetNewAccountDetailsForActivities(activities); + + var updates = new List(); + if (!activities.Any() || !accountDetails.Any()) + { + return updates; + } + + foreach (var activity in activities) + { + var updateDetails = accountDetails.Where(x => x.AccountNumber.ToString() + .Equals(activity.Detail.AccountNumber, StringComparison.InvariantCultureIgnoreCase)) + .ToList(); + + if (!updateDetails.Any()) + { + continue; + } + + foreach (var detail in updateDetails.Where(detail => + detail.CurrentAccountStatus != null && + detail.CurrentAccountStatus != activity.Detail.AccountStatus)) + { + await _activityDetailUpdateRepository.SaveActivityDetailUpdate(new ActivityDetailUpdate + { + ActivityDetailId = activity.Detail.Id, + Note = + $"The Account Status has changed from {activity.Detail.AccountStatus} to {detail.CurrentAccountStatus}" + }); + + activity.Detail.AccountStatus = detail.CurrentAccountStatus; + updates.Add(activity); + } + } + + await _unitOfWork.CompleteAsync(); + return updates; + } + + public async Task> TryUpdateLicReferralStationDetail(List activities) + { + var activityManagerReferrals = + await _dnaService.QueryActivityManagerForActivities(activities.Select(x => x.SourceId).ToList()); + + var updates = new List(); + if (!activityManagerReferrals.Any() || !activities.Any()) + { + return updates; + } + + foreach (var activity in activities) + { + var updateDetails = activityManagerReferrals.Where(x => + x.ActivityId.ToString().Equals(activity.SourceId, StringComparison.InvariantCultureIgnoreCase)) + .ToList(); + + if (!updateDetails.Any()) + { + continue; + } + + foreach (var detail in updateDetails.Where(detail => detail.ReferralStation != null && activity.Detail.StationName != detail.ReferralStation)) + { + await _activityDetailUpdateRepository.SaveActivityDetailUpdate(new ActivityDetailUpdate + { + ActivityDetailId = activity.Detail.Id, + Note = + $"The StationName has changed from {activity.Detail.StationName} to {detail.ReferralStation}" + }); + + activity.Detail.StationName = detail.ReferralStation; + + if (detail.ReferralStation == "LIC Referral-Qualified" || detail.ReferralStation == "LIC Referral- Not Qualified") { + activity.Accepted = detail.StationDateTime; + } + + updates.Add(activity); + } + } + + await _unitOfWork.CompleteAsync(); + return updates; + } + + public async Task TryUpdateDpAccessory(Activity saleActivity, Activity dpAccessoryActivity) + { + var updatedActivity = await MapAndExecuteActivityDetailUpdate( + dpAccessoryActivity, + saleActivity.Detail.DebtProtectionAddDate, + saleActivity.Detail.DebtProtectionRemoveDate + ); + + await _unitOfWork.CompleteAsync(); + return updatedActivity; + } + + public async Task> TryUpdateDpEligibleLoanDetail(List existingActivities) + { + var updateOrders = new List(); + var updateActivities = await _dnaService.GetNewAccountDetailsForActivities(existingActivities); + + var updatedActivities = new List(); + if (!existingActivities.Any() || !updateActivities.Any()) + { + return updatedActivities; + } + + foreach (var activity in existingActivities) + { + updateOrders.Add( + new ActivityDetailUpdateOrder + { + ExistingActivity = activity, + DetailUpdates = updateActivities.Where(x => x.AccountNumber.ToString() + .Equals(activity.Detail.AccountNumber, StringComparison.InvariantCultureIgnoreCase)) + .ToList() + } + ); + } + + foreach (var updateOrder in updateOrders) + { + try + { + if (updateOrder.DetailUpdates.Count == 0) + { + continue; + } + + var latestDpAdded = updateOrder.DetailUpdates.MaxBy(x => x.DebtProtectionEffectiveDate)?.DebtProtectionEffectiveDate; + var latestDpRemoved = updateOrder.DetailUpdates.OrderByDescending(x => x.DebtProtectionInactiveDate == null) + .ThenByDescending(x => x.DebtProtectionInactiveDate) + .FirstOrDefault() + ?.DebtProtectionInactiveDate; + + + if (IsDebtProtectionUpdateNeeded(updateOrder.ExistingActivity, latestDpAdded, latestDpRemoved)) + { + updatedActivities.Add(await MapAndExecuteActivityDetailUpdate(updateOrder.ExistingActivity, latestDpAdded, latestDpRemoved)); + } + } + catch (Exception ex) + { + Log.Error(ex, $"Failed to update activity details for activity ID:{updateOrder.ExistingActivity.Id}"); + } + } + + await _unitOfWork.CompleteAsync(); + return updatedActivities; + } + + private static bool IsDebtProtectionUpdateNeeded(Activity activity, DateTime? latestDpAdded, DateTime? latestDpRemoved) + { + return activity.Detail.DebtProtectionAddDate?.Date != latestDpAdded?.Date + || activity.Detail.DebtProtectionRemoveDate?.Date != latestDpRemoved?.Date; + } + + private async Task MapAndExecuteActivityDetailUpdate(Activity activity, DateTime? latestDpAdded, DateTime? latestDpRemoved) + { + try + { + if (activity.Detail.DebtProtectionAddDate?.Date != latestDpAdded?.Date) + { + activity.Detail.DebtProtectionAddDate = latestDpAdded?.Date.AddHours(12); + await _activityDetailUpdateRepository.SaveActivityDetailUpdate(new ActivityDetailUpdate + { + ActivityDetailId = activity.Detail.Id, + Note = $"DebtProtectionAddDate has changed from {activity.Detail.DebtProtectionAddDate?.Date} to {latestDpAdded?.Date}" + }); + } + + if (activity.Detail.DebtProtectionRemoveDate?.Date != latestDpRemoved?.Date) + { + activity.Detail.DebtProtectionRemoveDate = latestDpRemoved?.Date.AddHours(12); + await _activityDetailUpdateRepository.SaveActivityDetailUpdate(new ActivityDetailUpdate + { + ActivityDetailId = activity.Detail.Id, + Note = $"DebtProtectionRemoveDate has changed from {activity.Detail.DebtProtectionRemoveDate?.Date} to {latestDpRemoved?.Date}" + }); + } + } + catch (Exception ex) + { + Log.Error(ex, $"Failed to update activity details for activity ID:{activity.Id}"); + } + + return activity; + } + + public async Task SaveActivityDetailUpdate(ActivityDetailUpdate activityDetailUpdate) + { + return await _activityDetailUpdateRepository.SaveActivityDetailUpdate(activityDetailUpdate); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ActivityService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ActivityService.cs new file mode 100644 index 0000000..8156a44 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ActivityService.cs @@ -0,0 +1,748 @@ +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.API.Internal.Operations.IncentiveApi.Services.DuplicateDetection; +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; +using LCU.API.Internal.Operations.IncentiveApiLib.Extensions; +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Results; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public class ActivityService : IActivityService +{ + private const string TransferredFromString = "Activity has been transferred from Activity ID: {0}"; + private const string TransferredToString = "Activity has been transferred to Activity ID: {0}"; + private readonly IActivityRepository _activityRepository; + private readonly IActivityChangeRepository _activityChangeRepository; + private readonly IActivityTransferActionRepository _activityTransferActionRepository; + private readonly IUserService _userService; + private readonly INotificationApiService _notificationService; + private readonly IUnitOfWork _unitOfWork; + private readonly IDuplicateDetectionService _duplicateDetectionService; + private readonly IAuthorizationService _authorizationService; + private readonly ProcessResult _incentiveProcessResult; + + public ActivityService( + IActivityRepository activityRepository, + IActivityChangeRepository activityChangeRepository, + IActivityTransferActionRepository activityTransferActionRepository, + IUserService userService, + INotificationApiService notificationService, + IDuplicateDetectionService duplicateDetectionService, + IAuthorizationService authorizationService, + IUnitOfWork unitOfWork + ) + { + _activityRepository = activityRepository; + _activityChangeRepository = activityChangeRepository; + _activityTransferActionRepository = activityTransferActionRepository; + _userService = userService; + _notificationService = notificationService; + _unitOfWork = unitOfWork; + _authorizationService = authorizationService; + _duplicateDetectionService = duplicateDetectionService; + _incentiveProcessResult = new ProcessResult(); + } + + public async Task SaveActivityAsync(Activity activity) + { + if (LogGapInformation(activity)) + { + Log.Information($"Activity {activity.Id} - {activity.Detail.MemberName} " + + $"GapEligible saved as : {activity.Detail.IsGapInsuranceEligible} {DateTime.UtcNow} "); + } + + if (string.IsNullOrWhiteSpace(activity.EmployeeId)) + { + throw new DeadActivityException( + $"Attempted to save activity missing EmployeeId."); + } + + if (activity.DepartmentId < 0) + { + throw new DeadActivityException( + $"Attempted to save activity with invalid DepartmentId of {activity.DepartmentId}"); + } + var appUser = await _userService.GetActiveUserByEmployeeIdAsync(activity.EmployeeId) + ?? await _userService.GetUserByEmployeeIdAsync(activity.EmployeeId); + if (appUser == null) + { + throw new NotFoundException($"Employee Not Found For EmployeeId: {activity.EmployeeId}"); + } + + activity.AppUserId = appUser.Id; + activity.Accepted = activity.Accepted.Date.AddHours(12); + + await _activityRepository.SaveActivityAsync(activity); + await _unitOfWork.CompleteAsync(); + await MarkDuplicates(new List { activity }); + return activity.Id; + } + + public static bool LogGapInformation(Activity activity) + { + return activity.Product == Product.Vehicle + && activity.SourceSystem == SystemType.Dna + && activity.Detail != null + && activity.Detail.IsGapInsuranceEligible == false + && activity.Detail.AccountBalance.HasValue + && activity.Detail.AccountBalance.Value <= 100000 + && (!string.IsNullOrEmpty(activity.Detail?.MemberName)) + && (!activity.Detail.MemberName.ToLower().Contains(" inc") && !activity.Detail.MemberName.Contains(" llc")); + } + + public async Task CreateAndRetrieveActivity(Activity activity) + { + var newActivityId = await SaveActivityAsync(activity); + return await GetActivity(newActivityId); + } + + public async Task MarkDuplicates(IReadOnlyCollection activities) + { + foreach (var activity in activities) + { + var duplicates = await _duplicateDetectionService.TryFindDuplicates(activity); + if (duplicates == null || !duplicates.Any()) continue; + + var change = new ActivityStateChange + { + ActivityId = activity.Id, + State = ActivityState.Rejected, + ActivityMatchId = duplicates.First().Id, + Note = $"Activity rejected, duplicate of {duplicates.First().Id}." + }; + await ApplyStateChange(change); + } + } + + public async Task SaveActivityRangeAsync(IReadOnlyCollection activities) + { + await _activityRepository.SaveActivityRangeAsync(activities.ToList()); + await _unitOfWork.CompleteAsync(); + await MarkDuplicates(activities); + } + + public async Task RunReferralStatusUpdates() + { + try + { + var activityUpdates = await MatchOrExpireReferrals(); + var message = $"There were {activityUpdates.Count()} Referral Activity State Updates"; + Log.Information(message); + _incentiveProcessResult.MessageStack.Add(message); + } + catch (Exception exception) + { + var message = $"There was an issue with Activity State Updates Process. Exception Message: {exception.Message}."; + _incentiveProcessResult.MessageStack.Add(message); + _incentiveProcessResult.Success = false; + } + + return _incentiveProcessResult; + } + + public async Task> MatchOrExpireReferrals() + { + var activeReferrals = await _activityRepository.FindUnmatchedReferrals(); + var changes = new List(); + foreach (var referral in activeReferrals) + { + var matchedSale = await _activityRepository.FindUnmatchedSaleForReferral(referral); + if (matchedSale != null) + { + var acceptedStateChange = await ApplyAcceptedStateChange(referral, matchedSale); + changes.Add(acceptedStateChange); + + if (referral.EmployeeId == matchedSale.EmployeeId + && referral.Product == Product.NewChecking + && matchedSale.Product == Product.NewChecking) + { + ActivityStateChange rejectedStateChange = await ApplyRejectedStateChange(referral, matchedSale); + changes.Add(rejectedStateChange); + } + + continue; + } + + var expireRange = referral.Product != Product.FirstMortgageReferral + ? DateTime.Today.Subtract(TimeSpan.FromDays(60)) + : DateTime.Today.Subtract(TimeSpan.FromDays(120)); + + if (referral.SourceCreated < expireRange) + { + var change = new ActivityStateChange + { + ActivityId = referral.Id, + State = ActivityState.Rejected, + Note = "Referral has expired." + }; + await ApplyStateChange(change); + changes.Add(change); + } + } + + return changes; + } + + private async Task ApplyRejectedStateChange(Activity referral, Activity matchedSale) + { + var creditedEmployee = _userService.GetUserByEmployeeIdAsync(matchedSale.EmployeeId); + var rejectedStateChange = new ActivityStateChange + { + ActivityId = referral.Id, + State = ActivityState.Rejected, + ActivityMatchId = matchedSale.Id, + Note = $"Referral rejected as {creditedEmployee.Result.FullName} is also employee on {matchedSale.Id}." + }; + await ApplyStateChange(rejectedStateChange); + return rejectedStateChange; + } + + private async Task ApplyAcceptedStateChange(Activity referral, Activity matchedSale) + { + var acceptedStateChange = new ActivityStateChange + { + ActivityId = referral.Id, + State = ActivityState.Accepted, + ActivityMatchId = matchedSale.Id, + Note = $"Referral was matched to {matchedSale.Id}." + }; + await ApplyStateChange(acceptedStateChange); + return acceptedStateChange; + } + + public async Task ApplyStateChange(ActivityStateChange activityStateChange, bool commitTransaction = true) + { + var existingActivity = await _activityRepository.GetActivityById(activityStateChange.ActivityId); + if (existingActivity == null) + { + throw new NotFoundException($"No activity with Id {activityStateChange.ActivityId} could be found"); + } + + if (activityStateChange.ActivityMatchId == activityStateChange.ActivityId) + { + throw new Exception("Activity cannot change a state against itself."); + } + + if (existingActivity.State == activityStateChange.State && + (activityStateChange.AcceptedDate == null || existingActivity.Accepted == activityStateChange.AcceptedDate)) + { + await _activityChangeRepository.SaveActivityStateChange(activityStateChange); + await _unitOfWork.CompleteAsync(); + return existingActivity; + } + + existingActivity.State = activityStateChange.State; + + if (activityStateChange.AcceptedDate != null) + { + existingActivity.Accepted = (DateTime)activityStateChange.AcceptedDate?.Date.AddHours(12); + } + + if (activityStateChange.State == ActivityState.Accepted || activityStateChange.State == ActivityState.Rejected) + { + if (activityStateChange.AcceptedDate == null) + { + existingActivity.Accepted = DateTime.UtcNow.Date.AddHours(12); + } + + if (activityStateChange.ActivityMatchId > 0) + { + var matchedActivity = await _activityRepository.GetActivityById(activityStateChange.ActivityMatchId); + if (matchedActivity == null) + { + throw new NotFoundException($"No matched activity with Id {activityStateChange.ActivityMatchId} could be found"); + } + + existingActivity.MatchingActivityId = activityStateChange.ActivityMatchId; + if (activityStateChange.State == ActivityState.Accepted) + { + matchedActivity.MatchingActivityId = activityStateChange.ActivityId; + } + } + } + + await _activityChangeRepository.SaveActivityStateChange(activityStateChange); + if (commitTransaction) + { + await _unitOfWork.CompleteAsync(); + } + + return existingActivity; + } + + public async Task> GetActivitiesAsync() + { + var activities = await _activityRepository.GetActivitiesAsync(); + return activities ?? new List(); + } + + public async Task> ListByFilterAsync(ActivityFilter filter) + { + try + { + return await _activityRepository.GetPagedFilteredActivities(filter); + } + catch (Exception ex) + { + Log.Error(ex, $"Failed to retrieve filtered Actions limit: {filter.Limit}, page: {filter.Page}"); + throw new ServerErrorException(); + } + } + + public async Task> GetActivityStateChanges(int activityId) + { + return await _activityChangeRepository.GetActivityChangesByActivityId(activityId); + } + + public async Task> GetDepartmentActivityForMonthRangeAsync( + List userDepartments, + MonthRange monthRange + ) + { + var deptProgress = new List(); + foreach (var dept in userDepartments) + { + var isAssignedInMonthRange = dept.StartDate.Date < monthRange.EndTime.Date && + (dept.EndDate == null || + monthRange.StartTime.Date < dept.EndDate.Value.Date); + if (isAssignedInMonthRange) + { + var startDate = dept.StartDate.Date < monthRange.StartTime.Date + ? monthRange.StartTime.Date + : dept.StartDate.Date; + var endDate = dept.EndDate == null ? monthRange.EndTime.Date + : dept.EndDate.Value.Date > monthRange.EndTime.Date ? monthRange.EndTime.Date + : dept.EndDate.Value.Date; + var monthRangeOfAssignment = new MonthRange + ( + startDate.Year, + startDate.Month, + endDate.Year, + endDate.Month + ); + + var progress = new DepartmentProgress(dept.DepartmentId, monthRangeOfAssignment); + var departmentActivity = await _activityRepository.FindDepartmentActivityByMonthRange( + progress.DepartmentId, progress.MonthRange); + progress.ApplyActivity(departmentActivity); + deptProgress.Add(progress); + } + } + + return deptProgress; + } + + public async Task GetActivity(int activityId) + { + var activity = await _activityRepository.GetActivityIncludingPayouts(activityId); + if (activity == null) + { + throw new NotFoundException($"No activity with Id {activityId} could be found"); + } + + return activity; + } + + public async Task GetActivityForStateUpdate(Activity queryActivity) + { + var activity = await _activityRepository.FindActivityForStateUpdate(queryActivity); + if (activity == null) + { + throw new NotFoundException( + $"No activity with matching query activity could be found. AccountNumber: {queryActivity.Detail.AccountNumber} MemberName:{queryActivity.Detail.MemberName}"); + } + + return activity; + } + + public async Task GetActivityForDetailUpdate(Activity queryActivity) + { + return await _activityRepository.FindActivityForDetailUpdate(queryActivity); + } + + public async Task ValidateCanApplyActivityStateChange(AppUser appUser, ActivityStateChange activityStateChange) + { + var activity = await _activityRepository.GetActivityIncludingPayouts(activityStateChange.ActivityId); + if (activity == null) + { + throw new NotFoundException($"No activity with Id {activityStateChange.ActivityId} could be found"); + } + + if (activity.ActivityPayouts != null && activity.ActivityPayouts.Any()) + { + throw new BadActivityStateChangeException("Cannot change the state of an activity that has a payout."); + } + + if (activityStateChange.AcceptedDate != null) + { + var lastDateNotPaidOut = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1).AddMonths(-1); + if (activityStateChange.AcceptedDate < lastDateNotPaidOut || activityStateChange.AcceptedDate > DateTime.Today.AddMonths(1)) + { + throw new BadActivityStateChangeException( + "Cannot change the accepted date of an activity to a date that's already paid out, or is greater than 1 month from now."); + } + } + + var required = activity.RequiredToChangeState(activityStateChange.State); + switch (required) + { + case StateChangeRequirement.NotAllowed: + case StateChangeRequirement.SystemOnly: + throw new BadActivityStateChangeException("Activity can not be changed to target state."); + case StateChangeRequirement.AdminOnly when appUser.HasRoleAnywhere(Role.Admin): + case StateChangeRequirement.AdminOrPermission when appUser.HasRoleAnywhere(Role.Admin) + || await _authorizationService.AppUserCanMakeStateChange(appUser, activity, + activityStateChange.State): + case StateChangeRequirement.Hierarchical + when await _authorizationService.ValidateHierarchicalDepartmentPermission(appUser, Role.DepartmentManagerRoles, activity.DepartmentId) + || await _authorizationService.AppUserCanMakeStateChange(appUser, activity, activityStateChange.State): + return; + default: + throw new NotAuthorizedException("User does not have the permission to change the activity state."); + } + } + + public async Task RequestTransfer(ActivityTransferRequest activityTransferRequest) + { + var activity = + await _activityRepository.GetActivityIncludingPayouts(activityTransferRequest.RequestedActivityId); + if (activity == null) + { + throw new NotFoundException( + $"No activity with Id {activityTransferRequest.RequestedActivityId} could be found"); + } + + if (activity.ActivityPayouts.Any()) + { + throw new BadActivityTransferActionException( + "Cannot request a transfer of an activity that has a payout."); + } + + if (!ActivityTransferAction.IsEligibleForTransfer(activity.State)) + { + throw new BadActivityTransferActionException( + "Cannot request a transfer that isn't pending or approved."); + } + + var newAppUser = await _userService.GetActiveUserByEmployeeIdAsync(activityTransferRequest.NewEmployeeId); + + var activityTransferAction = new ActivityTransferAction + { + Id = 0, + ActivityStateBeforeTransfer = activity.State, + RequestedActivityId = activity.Id, + State = ActivityTransferState.Requested, + NewAppUserId = newAppUser.Id, + NewActivityDepartmentId = activityTransferRequest.NewDepartmentId, + NewActivityEmployeeId = activityTransferRequest.NewEmployeeId + }; + await _activityTransferActionRepository.SaveTransferAsync(activityTransferAction); + + var stateChange = new ActivityStateChange + { + ActivityId = activityTransferRequest.RequestedActivityId, + Note = "", + State = ActivityState.PendingTransfer + }; + await ApplyStateChange(stateChange); + + var initiatingManager = await _userService + .FindDepartmentManagers(activityTransferAction.RequestedActivity.DepartmentId); + var destinationManager = await _userService + .FindDepartmentManagers(activityTransferAction.NewActivityDepartmentId); + + var recipients = initiatingManager + .Concat(destinationManager) + .Select(manager => manager.AdEmail) + .Distinct() + .ToList(); + + await _notificationService.SendTransferRequestInitiatedNotification(activityTransferAction, recipients); + return activityTransferAction; + } + + public async Task TransferToOwnDepartment( + ActivityTransferRequest activityTransferRequest, + AppUser employee + ) + { + var existingActivity = + await _activityRepository.GetActivityIncludingPayouts(activityTransferRequest.RequestedActivityId); + if (existingActivity == null) + { + throw new NotFoundException( + $"No activity with Id {activityTransferRequest.RequestedActivityId} could be found"); + } + + if (!string.Equals(existingActivity.EmployeeId, employee.EmployeeId) || + !string.Equals(activityTransferRequest.NewEmployeeId, employee.EmployeeId)) + { + throw new NotAuthorizedException( + "This activity does not belong to the requesting user and cannot be transferred."); + } + + if (!employee.HasDepartment(activityTransferRequest.NewDepartmentId)) + { + throw new BadActivityTransferActionException( + "Cannot request a transfer of an activity to a department that the user is not assigned to."); + } + + if (existingActivity.ActivityPayouts.Any()) + { + throw new BadActivityTransferActionException( + "Cannot request a transfer of an activity that has a payout."); + } + + if (existingActivity.State != ActivityState.Accepted) + { + throw new BadActivityTransferActionException( + "Cannot request a transfer to another assigned department for an activity that isn't accepted."); + } + + var newAppUser = await _userService.GetActiveUserByEmployeeIdAsync(activityTransferRequest.NewEmployeeId); + + var activityTransferAction = new ActivityTransferAction + { + Id = 0, + ActivityStateBeforeTransfer = existingActivity.State, + RequestedActivityId = existingActivity.Id, + State = ActivityTransferState.Accepted, + NewAppUserId = newAppUser.Id, + NewActivityDepartmentId = activityTransferRequest.NewDepartmentId, + NewActivityEmployeeId = activityTransferRequest.NewEmployeeId + }; + + var newActivity = Activity.CopyActivityForTransfer(existingActivity, activityTransferAction, employee.AdEmail); + activityTransferAction.NewActivity = newActivity; + + await _activityTransferActionRepository.SaveTransferAsync(activityTransferAction); + await _unitOfWork.CompleteAsync(); + + var newStateChange = new ActivityStateChange + { + ActivityId = newActivity.Id, + ActivityMatchId = existingActivity.Id, + Note = string.Format(TransferredFromString, existingActivity.Id), + State = activityTransferAction.ActivityStateBeforeTransfer + }; + await ApplyStateChange(newStateChange); + + var stateChange = new ActivityStateChange + { + ActivityId = existingActivity.Id, + ActivityMatchId = newActivity.Id, + Note = string.Format(TransferredToString, newActivity.Id), + State = ActivityState.Transferred + }; + await ApplyStateChange(stateChange); + + return activityTransferAction; + } + + public async Task ApproveTransfer( + AppUser appUser, + ActivityTransferApprovalRequest transferApproval + ) + { + var activityTransferAction = await GetActivityTransferAction(transferApproval); + var activity = await _activityRepository.GetActivityIncludingPayouts(activityTransferAction.RequestedActivityId); + var targetStateChange = transferApproval.ApprovalState == ActivityTransferState.Accepted + ? ActivityState.Accepted + : activityTransferAction.ActivityStateBeforeTransfer; + + var canApproveTransferFrom = await _authorizationService.AppUserCanMakeStateChange(appUser, activity, targetStateChange); + var canApproveTransferTo = + await _authorizationService.AppUserCanMakeStateChange(appUser, activity, targetStateChange, activityTransferAction.NewActivityDepartmentId); + + if (!canApproveTransferFrom && !canApproveTransferTo) + { + throw new NotAuthorizedException("Employee is not allowed to approve for either department"); + } + + if (transferApproval.ApprovalState == ActivityTransferState.Rejected) + { + return await RejectTransfer(activityTransferAction); + } + + if (transferApproval.ApprovalState == ActivityTransferState.Accepted) + { + var isApprovedByRequestedDepartment = + !string.IsNullOrEmpty(activityTransferAction.RequestedDepartmentApprover); + var isApprovedByNewDepartment = !string.IsNullOrEmpty(activityTransferAction.NewDepartmentApprover); + + if (canApproveTransferFrom && !isApprovedByRequestedDepartment) + { + activityTransferAction.RequestedDepartmentApprover = appUser.FullName; + activityTransferAction.RequestedDepartmentApproved = DateTime.Now; + isApprovedByRequestedDepartment = true; + } + + if (canApproveTransferTo && !isApprovedByNewDepartment) + { + activityTransferAction.NewDepartmentApprover = appUser.FullName; + activityTransferAction.NewDepartmentApproved = DateTime.Now; + isApprovedByNewDepartment = true; + } + + if (isApprovedByRequestedDepartment && isApprovedByNewDepartment) + { + var existingActivity = await _activityRepository + .GetActivityIncludingPayouts(activityTransferAction.RequestedActivityId); + var newActivityEmployee = await _userService + .GetUserByEmployeeIdAsync(activityTransferAction.NewActivityEmployeeId); + var newActivity = Activity.CopyActivityForTransfer(existingActivity, activityTransferAction, + newActivityEmployee.AdEmail); + + activityTransferAction.NewActivity = newActivity; + activityTransferAction.State = ActivityTransferState.Accepted; + + await _unitOfWork.CompleteAsync(); + + var newStateChange = new ActivityStateChange + { + ActivityId = newActivity.Id, + ActivityMatchId = existingActivity.Id, + Note = string.Format(TransferredFromString, existingActivity.Id), + State = activityTransferAction.ActivityStateBeforeTransfer + }; + await ApplyStateChange(newStateChange); + + var stateChange = new ActivityStateChange + { + ActivityId = existingActivity.Id, + ActivityMatchId = newActivity.Id, + Note = string.Format(TransferredToString, newActivity.Id), + State = ActivityState.Transferred + }; + await ApplyStateChange(stateChange); + } + else + { + await _unitOfWork.CompleteAsync(); + } + } + + return activityTransferAction; + } + + public async Task GetRequestedTransfer(int activityId) + { + return await _activityTransferActionRepository.GetRequestedTransferActionByActivityId(activityId); + } + + private async Task GetActivityTransferAction( + ActivityTransferApprovalRequest transferApproval + ) + { + var activityTransferAction = await _activityTransferActionRepository + .GetTransferActionById(transferApproval.Id); + if (activityTransferAction == null) + { + throw new NotFoundException( + $"No activity transfer action with Id {transferApproval.Id} could be found"); + } + + if (activityTransferAction.State != ActivityTransferState.Requested) + { + throw new BadActivityTransferActionException("Approval has already been decided for this activity"); + } + + return activityTransferAction; + } + + private async Task RejectTransfer(ActivityTransferAction activityTransferAction) + { + activityTransferAction.State = ActivityTransferState.Rejected; + + var stateChange = new ActivityStateChange + { + ActivityId = activityTransferAction.RequestedActivityId, + Note = "Activity transfer has been rejected.", + State = activityTransferAction.ActivityStateBeforeTransfer + }; + await ApplyStateChange(stateChange); + + return activityTransferAction; + } + + public async Task CancelPendingTransfersBetween(MonthRange range) + { + var transfers = await _activityTransferActionRepository.GetActiveTransfersForStateAsync(ActivityState.Accepted); + foreach (var transfer in transfers) + { + if (transfer.RequestedActivity.Accepted.Date >= range.StartTime.Date && transfer.RequestedActivity.Accepted.Date <= range.EndTime.Date) + { + await RejectTransfer(transfer); + } + } + } + + public async Task> FilterExternalActivitiesByExistingSourceId( + List incomingEternalActivities + ) + { + var existingActivities = + await _activityRepository.GetActivitiesBySourceId(incomingEternalActivities.Select(x => x.GetSourceId()) + .ToList()); + + return incomingEternalActivities + .Where(newActivity => !existingActivities.Any(existingActivity => + existingActivity.Product == newActivity.GetProductType() && + existingActivity.Type == newActivity.GetActivityType() && + existingActivity.SourceId == newActivity.GetSourceId())) + .ToList(); + } + + public async Task GetActivityAuthorizedCommands(AppUser appUser, int activityId) + { + var activity = await _activityRepository.GetActivityIncludingPayouts(activityId); + if (activity == null) + { + throw new NotFoundException($"No activity with Id {activityId} could be found"); + } + + var allowedChanges = new List(); + var canApproveTransferFrom = false; + var canApproveTransferTo = false; + var activityTransferAction = await GetRequestedTransfer(activityId); + var targetDepartmentId = activityTransferAction == null || activity.State != ActivityState.PendingTransfer ? 0 : activityTransferAction.NewActivityDepartmentId; + + var hasHierarchicalPermission = await _authorizationService.ValidateHierarchicalDepartmentPermission(appUser, + Role.DepartmentManagerRoles, activity.DepartmentId); + foreach (var stateDetail in ActivityStateDetail.ActivityStateDetails) + { + var changeRequirement = activity.RequiredToChangeState(stateDetail.State); + switch (changeRequirement) + { + case StateChangeRequirement.AdminOnly when appUser.HasRoleAnywhere(Role.Admin): + case StateChangeRequirement.AdminOrPermission when appUser.HasRoleAnywhere(Role.Admin) + || await _authorizationService.AppUserCanMakeStateChange(appUser, activity, + stateDetail.State, targetDepartmentId): + case StateChangeRequirement.Hierarchical when hasHierarchicalPermission + || await _authorizationService.AppUserCanMakeStateChange(appUser, activity, + stateDetail.State, targetDepartmentId): + allowedChanges.Add(stateDetail.State); + break; + } + } + + if (activityTransferAction != null && allowedChanges.Contains(ActivityState.Transferred)) + { + canApproveTransferFrom = await _authorizationService.AppUserCanMakeStateChange(appUser, activity, ActivityState.Transferred); + canApproveTransferTo = await _authorizationService.AppUserCanMakeStateChange(appUser, activity, ActivityState.Transferred, + activityTransferAction.NewActivityDepartmentId); + } + + allowedChanges = allowedChanges.Distinct().ToList(); + + return new AuthorizedActivityCommands(allowedChanges.ToArray(), canApproveTransferFrom, canApproveTransferTo); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Allied/AlliedGapMbpTransactionImportService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Allied/AlliedGapMbpTransactionImportService.cs new file mode 100644 index 0000000..3303389 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Allied/AlliedGapMbpTransactionImportService.cs @@ -0,0 +1,395 @@ +using System.Globalization; +using System.IO; +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision; +using CsvHelper; +using CsvHelper.Configuration; +using LCU.API.Internal.Operations.IncentiveApiLib.Extensions; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.API.Internal.Operations.IncentiveApi.Models.Allied; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.API.Internal.Operations.IncentiveApi.Services.Transformations; +using LCU.Library.Internal.ApiResponse.Services; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Results; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Allied; + +public class AlliedGapMbpTransactionImportService : IAlliedGapMbpTransactionImportService +{ + private readonly IUserRepository _userRepository; + private readonly IActivityService _activityService; + private readonly IActivityDecisionService _activityDecisionService; + private readonly IActivityDetailService _activityDetailService; + private readonly IDeadActivityService _deadActivityService; + private readonly IFileOperationsApiService _fileOperationsApiService; + private readonly IGapInsuranceActivityService _gapInsuranceActivityService; + private readonly ProcessResult _alliedGapMbpProcessResult; + + public AlliedGapMbpTransactionImportService( + IUserRepository userRepository, + IGapInsuranceActivityService gapInsuranceActivityService, + IFileOperationsApiService fileOperationsApiService, + IActivityService activityService, + IActivityDecisionService activityDecisionService, + IActivityDetailService activityDetailService, + IDeadActivityService deadActivityService + ) + { + _userRepository = userRepository; + _gapInsuranceActivityService = gapInsuranceActivityService; + _fileOperationsApiService = fileOperationsApiService; + _activityService = activityService; + _activityDecisionService = activityDecisionService; + _activityDetailService = activityDetailService; + _deadActivityService = deadActivityService; + _alliedGapMbpProcessResult = new ProcessResult(); + } + + public async Task RunAlliedGapMbpProcess(AlliedGapMbpRequest request) + { + try + { + var importedAlliedSales = await ImportTransactions(request); + Log.Information($"Imported {importedAlliedSales.Count} Allied Gap/Mbp sales."); + + var matchedRemoved = await _gapInsuranceActivityService.MatchOrRemoveGapAccessories(); + Log.Information($"Matched/Removed {matchedRemoved} Allied Gap/Mbp records."); + + _alliedGapMbpProcessResult.MessageStack.Add($"Imported {importedAlliedSales.Count()} sales, Matched/Removed {matchedRemoved} records for Allied Gap/Mbp Sales processing."); + await QueueReturnResponse.QueueResponse(request, _alliedGapMbpProcessResult.Success, _alliedGapMbpProcessResult.MessageStack); + } + catch (Exception exception) + { + var message = $"There was an issue with handling Import Allied Gap/Mbp Sales Exception Message: {exception.Message}."; + _alliedGapMbpProcessResult.MessageStack.Add(message); + _alliedGapMbpProcessResult.Success = false; + await QueueReturnResponse.QueueResponse(request, _alliedGapMbpProcessResult.Success, _alliedGapMbpProcessResult.MessageStack); + throw new ServiceException(message); + } + } + + public async Task> ImportTransactions(AlliedGapMbpRequest request) where T : AlliedSaleRow + { + var dynamicFileListRequest = request.ToDynamicAlliedGapMbpFileListRequest(); + var files = await _fileOperationsApiService.BuildDynamicFileList(dynamicFileListRequest); + + var activity = new List(); + foreach (var fileName in files) + { + var stringContent = await _fileOperationsApiService.GetFileContent($"{request.FinalBasePath}{fileName}"); + using var stringReader = new StringReader(stringContent); + var config = new CsvConfiguration(CultureInfo.InvariantCulture) + { + Delimiter = ",", + HasHeaderRecord = true, + HeaderValidated = null, + MissingFieldFound = null, + IgnoreBlankLines = true + }; + + using var csv = new CsvReader(stringReader, config); + var records = csv.GetRecords().ToList(); + var allActivity = await CreateActivity(records); + + activity.AddRange(allActivity); + } + + return activity; + } + + public async Task> CreateActivity(IEnumerable transactions) + { + var activities = new List(); + var deadActivities = new List(); + + try + { + foreach (var alliedSaleRow in transactions) + { + var activity = new Activity() + { + Accepted = DateTime.Parse(alliedSaleRow.SaleDate).Date.AddHours(12), + DepartmentId = -1, + Detail = new ActivityDetail() + { + AccountNumber = alliedSaleRow.DetermineAccountNumber(), + AccountStatus = alliedSaleRow.Status, + MemberName = alliedSaleRow.Buyer, + SubAccountNumber = alliedSaleRow.SalesContractNumber + }, + EmployeeEmail = null, + EmployeeId = null, + Product = alliedSaleRow.DetermineAlliedProduct(), + SourceCreated = DateTime.Parse(alliedSaleRow.SaleDate).Date.AddHours(12), + SourceId = alliedSaleRow.DetermineAccountNumber(), + SourceSystem = SystemType.Allied, + State = ActivityState.Created, + Type = ActivityType.Sale + }; + + var employee = await FindEmployee(alliedSaleRow.Issuer.Trim()); + if (employee == null) + { + Log.Warning($"Allied Import, No user for {alliedSaleRow.Issuer}. Saving as Dead Activity."); + deadActivities.Add(new DeadActivity(activity, + DeadActivityReason.MissingRequiredField, + $"Allied Import, No user for {alliedSaleRow.Issuer}")); + continue; + } + + activity.AppUserId = employee.Id; + activity.EmployeeEmail = employee.AdEmail; + activity.EmployeeId = employee.EmployeeId; + + var homeDept = employee.FindHomeDepartment(); + if (homeDept == null) + { + Log.Warning($"User has no reliably determinable home department, {employee.EmployeeId}. Saving as Dead Activity."); + deadActivities.Add(new DeadActivity(activity, DeadActivityReason.MissingRequiredField, + $"User has no reliably determinable home department, {employee.EmployeeId}")); + continue; + } + + activity.DepartmentId = homeDept.DepartmentId; + + activities.Add(activity); + } + + foreach (var activity in activities) + { + var activityStateUpdate = await _activityDecisionService.TryDecisionActivity(activity); + activity.State = activityStateUpdate.State; + } + + var activitySets = activities.GroupBy(x => new + { + x.DepartmentId, + x.EmployeeEmail, + x.EmployeeId, + x.Product, + x.SourceSystem, + x.Type, + x.Detail.AccountNumber, + x.Detail.SubAccountNumber + }); + + var updateOrders = new List(); + + foreach (var activitySet in activitySets) + { + var existingActivity = await _activityService.GetActivityForDetailUpdate(activitySet.FirstOrDefault()); + if (existingActivity?.State != ActivityState.Transferred + && existingActivity?.State != ActivityState.PendingTransfer + && existingActivity?.State != ActivityState.Rejected) + { + updateOrders.Add( + new ActivityProcessingOrder + { + ExistingActivity = existingActivity, + ActivityUpdateItems = activitySet.ToList() + } + ); + } + } + + try + { + await _deadActivityService.SaveDeadActivityRange(deadActivities); + } + catch (Exception ex) + { + Log.Error(ex, "Failed to save dead activities from AlliedTransactionImportService. Dead Activities will be lost"); + } + + return TryProcessUpdates(updateOrders).GetAwaiter().GetResult().ToList(); + } + catch (Exception ex) + { + Log.Error(ex, ex.Message); + } + + return new List(); + } + + private async Task> TryProcessUpdates(IEnumerable activityDetailUpdateOrders) + { + var returnItems = new List(); + + foreach (var activityDetailUpdateOrder in activityDetailUpdateOrders) + { + try + { + var activityProcessingResult = await HandleActivityProcessingOrder(activityDetailUpdateOrder); + if (activityProcessingResult.UpdateCount <= 0) + { + continue; + } + + Log.Debug($"{activityProcessingResult.UpdateCount} updates performed on Allied SourceId: {activityProcessingResult.ExistingActivity.SourceId}"); + returnItems.Add(activityDetailUpdateOrder.ExistingActivity); + } + catch (Exception ex) + { + Log.Error(ex, ex.Message); + } + } + + return returnItems; + } + + public ActivityProcessingOrder FilterAndOrderUpdates(ActivityProcessingOrder activityProcessingOrder) + { + activityProcessingOrder.ActivityUpdateItems = activityProcessingOrder.ActivityUpdateItems + .OrderBy(x => x.SourceCreated) + .ThenBy(x => x.Detail.AccountStatus.ToString().Equals("Reinstated", StringComparison.InvariantCultureIgnoreCase)) + .ThenBy(x => x.Detail.AccountStatus.ToString().Equals("Cancelled", StringComparison.InvariantCultureIgnoreCase)) + .ThenBy(x => x.Detail.AccountStatus.ToString().Equals("Cancelled/Re-Quoted", StringComparison.InvariantCultureIgnoreCase)) + .ThenBy(x => x.Detail.AccountStatus.ToString().Equals("Sold", StringComparison.InvariantCultureIgnoreCase)) + .ThenBy(x => x.Detail.AccountStatus.ToString().Equals("Flagged", StringComparison.InvariantCultureIgnoreCase)) + .ToList(); + + if (activityProcessingOrder.ExistingActivity == null) + { + return activityProcessingOrder; + } + + var startIndex = activityProcessingOrder.ActivityUpdateItems + .FindIndex(x => + x.Detail.AccountStatus.Equals(activityProcessingOrder.ExistingActivity.Detail.AccountStatus, StringComparison.InvariantCultureIgnoreCase) + ); + + if (startIndex > 0) + { + activityProcessingOrder.ActivityUpdateItems.RemoveRange(0, startIndex); + } + + return activityProcessingOrder; + } + + public async Task HandleActivityProcessingOrder(ActivityProcessingOrder activityProcessingOrder) + { + activityProcessingOrder = FilterAndOrderUpdates(activityProcessingOrder); + + while (!activityProcessingOrder.ProcessingComplete) + { + foreach (var updateActivity in activityProcessingOrder.ActivityUpdateItems) + { + try + { + var updateActivityUpdateCount = 0; + + if (activityProcessingOrder.ExistingActivity == null) + { + try + { + activityProcessingOrder.ExistingActivity = await _activityService.CreateAndRetrieveActivity(updateActivity); + updateActivityUpdateCount++; + } + catch (NotFoundException nex) + { + Log.Error(nex, $"Failed to create new Allied Activity SourceId:{updateActivity.SourceId}"); + await _deadActivityService.SaveDeadActivity( + new DeadActivity(updateActivity, DeadActivityReason.MissingRequiredField, "Failed to create activity due to missing data") + ); + } + catch (Exception ex) + { + Log.Error(ex, $"Failed to create new Allied Activity SourceId:{updateActivity.SourceId}"); + await _deadActivityService.SaveDeadActivity(new DeadActivity(updateActivity, DeadActivityReason.Unknown, "Failed to create activity")); + } + finally + { + if (activityProcessingOrder.ExistingActivity == null) + { + activityProcessingOrder.ProcessingComplete = true; + Log.Debug($"No existing Allied Activity SourceId: {updateActivity.SourceId} "); + } + } + } + else + { + var activityDetailsUpdates = + activityProcessingOrder.ExistingActivity.Detail.GetPropertyValueVariances(updateActivity.Detail); + + foreach (var update in activityDetailsUpdates) + { + await _activityDetailService.SaveActivityDetailUpdate(new ActivityDetailUpdate + { + ActivityDetailId = activityProcessingOrder.ExistingActivity.Detail.Id, + Note = $"{update.ComparedProperty} has changed from {update.PropertyValueOne} to {update.PropertyValueTwo}" + }); + + activityProcessingOrder.ExistingActivity.Detail.GetType() + .GetProperty(update.ComparedProperty) + ? + .SetValue(activityProcessingOrder.ExistingActivity.Detail, + updateActivity.Detail.GetType().GetProperty(update.ComparedProperty)?.GetValue(updateActivity.Detail), + null); + + updateActivityUpdateCount++; + } + } + + + var existingActivityStateUpdate = await _activityDecisionService.TryDecisionActivity(activityProcessingOrder.ExistingActivity); + if (activityProcessingOrder.ExistingActivity.State != existingActivityStateUpdate.State) + { + updateActivityUpdateCount++; + await _activityService.ApplyStateChange(existingActivityStateUpdate); + } + + activityProcessingOrder.UpdateCount += updateActivityUpdateCount; + } + catch (Exception ex) + { + Log.Error(ex, $"Failed to update activity details for activity ID:{activityProcessingOrder.ExistingActivity.Id}"); + } + } + + activityProcessingOrder.ProcessingComplete = true; + } + + return activityProcessingOrder; + } + + private async Task FindEmployee(string query) + { + try + { + var users = await _userRepository.SearchUsersAsync(new UserSearchRequest() + { + Query = query + }); + + if (users?.FirstOrDefault() == null || users.Count() > 1) + { + if (query.IndexOf(",", StringComparison.Ordinal) > 0) + { + users = await _userRepository.SearchUsersAsync(new UserSearchRequest() + { + Query = query.Split(",").First() + }); + + if (users?.FirstOrDefault() == null || users.Count() > 1) + { + return null; + } + } + + return null; + } + + return users?.FirstOrDefault(); + } + catch (Exception ex) + { + Log.Error(ex, "Error when finding user by email in AlliedTransactionImportService"); + return null; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Allied/IAlliedGapMbpTransactionImportService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Allied/IAlliedGapMbpTransactionImportService.cs new file mode 100644 index 0000000..f53d8eb --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Allied/IAlliedGapMbpTransactionImportService.cs @@ -0,0 +1,14 @@ +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.API.Internal.Operations.IncentiveApi.Models.Allied; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Allied; + +public interface IAlliedGapMbpTransactionImportService +{ + Task RunAlliedGapMbpProcess(AlliedGapMbpRequest request); + Task> ImportTransactions(AlliedGapMbpRequest request) where T : AlliedSaleRow; + Task> CreateActivity(IEnumerable transactions); + ActivityProcessingOrder FilterAndOrderUpdates(ActivityProcessingOrder activityProcessingOrder); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AuthorizationService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AuthorizationService.cs new file mode 100644 index 0000000..eea35ec --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AuthorizationService.cs @@ -0,0 +1,153 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public class AuthorizationService : IAuthorizationService +{ + private readonly IDepartmentRepository _departmentRepository; + private readonly IUserAssignmentRepository _userAssignmentRepository; + + public AuthorizationService(IDepartmentRepository departmentRepository, IUserAssignmentRepository userAssignmentRepository) + { + _departmentRepository = departmentRepository; + _userAssignmentRepository = userAssignmentRepository; + } + + public async Task AppUserCanMakeStateChange(AppUser appUser, Activity activity, ActivityState targetState, int targetDepartmentId = 0) + { + if (appUser.HasRoleAnywhere(Role.Admin)) + { + return await Task.FromResult(true); + } + + targetDepartmentId = targetDepartmentId == 0 ? activity.DepartmentId : targetDepartmentId; + var targetDepartmentAppUserPermissions = appUser.ActiveDepartmentsForRange(new MonthRange()) + .FirstOrDefault(x => x.DepartmentId == targetDepartmentId) + ?.Role.Permissions.ToList(); + + if (appUser.AdditionalPermissions != null && targetDepartmentAppUserPermissions == null) + { + targetDepartmentAppUserPermissions = new List(); + targetDepartmentAppUserPermissions.AddRange(appUser.AdditionalPermissions.Select(x => x.Permission)); + } else if (appUser.AdditionalPermissions != null) + { + targetDepartmentAppUserPermissions.AddRange(appUser.AdditionalPermissions.Select(x => x.Permission)); + } + + if (targetDepartmentAppUserPermissions == null || activity.State == targetState) + { + return await Task.FromResult(false); + } + + var canExecuteChange = activity.State switch + { + ActivityState.Accepted => targetState switch + { + ActivityState.ManualReview => targetDepartmentAppUserPermissions.Contains(Permission.SetActivityToManualReviewFromAccepted), + ActivityState.Rejected => targetDepartmentAppUserPermissions.Contains(Permission.SetActivityToRejectedFromAccepted), + _ => false + }, + ActivityState.ManualReview => targetState switch + { + ActivityState.Accepted => targetDepartmentAppUserPermissions.Contains(Permission.SetActivityToAcceptedFromManualReview), + ActivityState.Rejected => targetDepartmentAppUserPermissions.Contains(Permission.SetActivityToRejectedFromManualReview), + _ => false + }, + ActivityState.PendingTransfer => targetState switch + { + ActivityState.Accepted => targetDepartmentAppUserPermissions.Contains(Permission.SetActivityToAcceptedFromPendingTransfer), + ActivityState.Transferred => targetDepartmentAppUserPermissions.Contains(Permission.SetActivityToTransferredFromPendingTransfer), + _ => false + }, + ActivityState.Rejected => targetState switch + { + ActivityState.Accepted => targetDepartmentAppUserPermissions.Contains(Permission.SetActivityToAcceptedFromRejected), + ActivityState.ManualReview => targetDepartmentAppUserPermissions.Contains(Permission.SetActivityToManualReviewFromRejected), + _ => false + }, + _ => false + }; + + return canExecuteChange; + } + + public async Task AppUserCanViewReport(AppUser appUser, string permissionCode) + { + if (appUser.HasRoleAnywhere(Role.Admin)) + { + return await Task.FromResult(true); + } + + var requiredPermission = Permission.LookupPermissionFromCode(permissionCode); + + return appUser.UserHasPermission(requiredPermission); + } + + public async Task ValidateHierarchicalDepartmentPermission( + AppUser appUser, + IReadOnlyCollection allowedDirectRoles, + int departmentId + ) + { + return + appUser.HasRoleAnywhere(Role.Admin) + || appUser.HasAnyRoleForActiveDepartment(departmentId, allowedDirectRoles.ToArray()) + || await ValidateIsBusinessLineManager(appUser, departmentId) + || await ValidateIsRegionalManager(appUser, departmentId); + } + + public async Task ValidateIsBusinessLineManager(AppUser appUser, int departmentId) + { + var userDept = appUser.FindDepartmentForRole(Role.BusinessLineManager); + if (userDept != null && userDept.Department != null) + { + var bl = BusinessLine.FindByCode(userDept.Department.BusinessLineCode); + if (bl == null) + { + return userDept.Department.Id == departmentId; + } + + var departments = await _departmentRepository.FindDepartmentsByBusinessLineAsync(bl); + if (departments.Select(x => x.Id).Contains(departmentId)) + { + return true; + } + } + + return false; + } + + public async Task ValidateIsRegionalManager(AppUser appUser, int departmentId) + { + var userDept = appUser.FindDepartmentForRole(Role.RegionalManager); + if (userDept != null && userDept.Department != null) + { + var userAssign = await _userAssignmentRepository.IsUserAssigned(AssignmentType.RegionalManager, appUser); + Region rc = null; + if (userAssign != null) + { + rc = Region.FindByCode(userAssign.Key); + } + + if (rc == null) + { + rc = Region.FindByCode(userDept.Department.RegionCode); + if (rc == null) + { + return userDept.Department.Id == departmentId; + } + } + + var departments = await _departmentRepository.FindDepartmentsByRegionAsync(rc); + if (departments.Select(x => x.Id).Contains(departmentId)) + { + return true; + } + } + + return false; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/ActivityDecisionService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/ActivityDecisionService.cs new file mode 100644 index 0000000..6066a7d --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/ActivityDecisionService.cs @@ -0,0 +1,413 @@ +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.API.Internal.Operations.IncentiveApi.Extensions; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Results; +using LCU.Library.Internal.ApiResponse.Services; +using LCU.Library.Internal.Exceptions; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision; + +public class ActivityDecisionService : IActivityDecisionService +{ + private const int PageItemLimit = 1000; + private readonly IEnumerable _strategies; + private readonly IActivityService _activityService; + private readonly IActivityDetailService _activityDetailService; + private readonly DebtProtectionAccessoryService _debtProtectionAccessoryService; + private readonly IDnaService _dnaService; + private static readonly List TransactionCodes = new List { "XDEP" }; + private readonly ProcessResult _incentiveProcessResult; + + public ActivityDecisionService( + IEnumerable strategies, + IActivityService activityService, + IActivityDetailService activityDetailService, + DebtProtectionAccessoryService debtProtectionAccessoryService, + IDnaService dnaService + ) + { + _strategies = strategies; + _activityService = activityService; + _activityDetailService = activityDetailService; + _debtProtectionAccessoryService = debtProtectionAccessoryService; + _dnaService = dnaService; + _incentiveProcessResult = new ProcessResult(); + } + + public async Task RunNightlyDecisionProcess(NightlyIncentiveProcessRequest request) + { + try + { + var decisionedCreated = await DecisionActivityByState(ActivityState.Created); + var createdStateResults = $"Auto-decisioned {decisionedCreated.Count} activities in Created state."; + Log.Information(createdStateResults); + _incentiveProcessResult.MessageStack.Add(createdStateResults); + + var decisionedManualReview = await DecisionActivityByState(ActivityState.ManualReview); + var manualStateActivityResults = $"Auto-decisioned {decisionedManualReview.Count} activities in Manual Review state."; + Log.Information(manualStateActivityResults); + _incentiveProcessResult.MessageStack.Add(manualStateActivityResults); + + var decisionManualReviewUpdated = await UpdateAndDecideManualReviewActivities(); + var manualReviewUpdates = $"Updated and Auto-Decisioned {decisionManualReviewUpdated.Count} Activities."; + Log.Information(manualReviewUpdates); + _incentiveProcessResult.MessageStack.Add(manualReviewUpdates); + + var decisionUpdateDpEligible = await UpdateAndDecideDpEligibleLoanActivities(); + var dpEligibleDecisionResults = $"Updated and Auto-Decisioned {decisionUpdateDpEligible.Count} Debt Protection Eligible Loan Activities."; + Log.Information(dpEligibleDecisionResults); + _incentiveProcessResult.MessageStack.Add(dpEligibleDecisionResults); + + await QueueReturnResponse.QueueResponse(request, _incentiveProcessResult.Success, _incentiveProcessResult.MessageStack); + } + catch (Exception exception) + { + var message = $"There was an issue with handling the Daily Decision Process Exception Message: {exception.Message}."; + _incentiveProcessResult.MessageStack.Add(message); + _incentiveProcessResult.Success = false; + await QueueReturnResponse.QueueResponse(request, _incentiveProcessResult.Success, _incentiveProcessResult.MessageStack); + throw new ServiceException(message); + } + } + + public async Task TryDecisionActivity(Activity activity) + { + var strategy = _strategies.FirstOrDefault(x => x.CanDecideActivity(activity)); + + if (strategy != null) + { + return await strategy.DecideActivity(activity); + } + + throw new NoAvailableDecisionStrategy( + $"No decision strategy available, {activity.Id} - {activity.Product}:{activity.Type}"); + } + + public async Task> DecisionActivityByState(ActivityState state) + { + var changes = new List(); + var allItemsProcessed = false; + var page = 0; + var processedItemCount = 0; + + while (!allItemsProcessed) + { + try + { + var activityPage = await _activityService.ListByFilterAsync(new ActivityFilter + { + Limit = PageItemLimit, + Page = page += 1, + State = new List { state } + }); + + if (activityPage?.Items == null) + { + return new List(); + } + + var activities = activityPage.Items.ToList(); + changes.AddRange(await AutoDecideActivities(activities)); + processedItemCount += activities.Count; + allItemsProcessed = processedItemCount == activityPage.Total; + } + catch (Exception ex) + { + Log.Error(ex, $"Failed to process all updates for activities in State:{state}"); + allItemsProcessed = true; + } + } + + return changes; + } + + public async Task> UpdateAndDecideManualReviewActivities() + { + var changes = new List(); + var allItemsProcessed = false; + var page = 0; + var processedItemCount = 0; + + while (!allItemsProcessed) + { + try + { + var activityPage = await _activityService.ListByFilterAsync(new ActivityFilter + { + Limit = PageItemLimit, + Page = page += 1, + State = new List { ActivityState.ManualReview } + }); + + if (activityPage?.Items == null) + { + return new List(); + } + + var activities = activityPage.Items.ToList(); + var updatedActivities = await _activityDetailService.TryUpdateActivityAccountDetails(activities); + + changes.AddRange(await AutoDecideActivities(updatedActivities)); + processedItemCount += activities.Count; + allItemsProcessed = processedItemCount == activityPage.Total; + } + catch (Exception ex) + { + Log.Error(ex, $"Failed to process all Manual Review updates"); + allItemsProcessed = true; + } + } + + return changes; + } + + public async Task> UpdateAndDecideDpEligibleLoanActivities() + { + var dateLow = DateTime.Now.StartOfMonth().AddMonths(-2).AddDays(-1); + var dpEligibleProducts = new List { Product.DebtProtectionEligibleLoan, Product.Mortgage, Product.Vehicle }; + var changes = new List(); + var allItemsProcessed = false; + var page = 0; + var processedItemCount = 0; + + while (!allItemsProcessed) + { + try + { + var activityResults = new PagedResult(); + activityResults = await _activityService.ListByFilterAsync(new ActivityFilter + { + Limit = PageItemLimit, + Page = page += 1, + DateLow = dateLow, + State = new List { ActivityState.Accepted }, + Product = dpEligibleProducts + }); + + if (activityResults.Items != null) + { + var activities = activityResults.Items.ToList(); + var updatedDpActivities = await _debtProtectionAccessoryService.ProcessDpAccessoryUpdates(activities); + var updatedActivities = await _activityDetailService.TryUpdateDpEligibleLoanDetail(activities); + updatedActivities.AddRange(await _debtProtectionAccessoryService.ProcessDpAccessoryUpdates(updatedActivities)); + updatedActivities.AddRange(updatedDpActivities); + changes.AddRange(await AutoDecideActivities(updatedActivities)); + processedItemCount += activities.Count; + } + + allItemsProcessed = processedItemCount == activityResults.Total; + } + catch (Exception ex) + { + Log.Error(ex, $"Failed to process all Debt Protection updates UpdateAndDecideDpEligibleLoanActivities"); + allItemsProcessed = true; + } + } + + changes.AddRange(await RejectDpOnRejectedDpEligibleLoanActivities(dateLow, dpEligibleProducts)); + + return changes; + } + + private async Task> RejectDpOnRejectedDpEligibleLoanActivities(DateTime dateLow, List dpEligibleProducts) + { + var changes = new List(); + var allItemsProcessed = false; + var page = 0; + var processedItemCount = 0; + + while (!allItemsProcessed) + { + try + { + var activityPage = await _activityService.ListByFilterAsync(new ActivityFilter + { + Limit = PageItemLimit, + Page = page += 1, + DateLow = dateLow, + State = new List { ActivityState.Rejected }, + Product = dpEligibleProducts + }); + + if (activityPage?.Items == null) + { + return new List(); + } + var activities = activityPage.Items.ToList(); + var dpActivitiesForRejection = await _debtProtectionAccessoryService.GetNonRejectedDpAccessoriesWithRejectedMatchedSale(activities); + + changes.AddRange(await RejectDpActivities(dpActivitiesForRejection)); + + processedItemCount += activities.Count; + allItemsProcessed = processedItemCount == activityPage.Total; + } + catch (Exception ex) + { + Log.Error(ex, $"Failed to process all Debt Protection updates on RejectDpOnRejectedDpEligibleLoanActivities"); + allItemsProcessed = true; + } + } + + return changes; + } + + public async Task> UpdateAndDecideLicReferralActivities() + { + var changes = new List(); + var allItemsProcessed = false; + var page = 0; + var processedItemCount = 0; + + while (!allItemsProcessed) + { + try + { + var activityPage = await _activityService.ListByFilterAsync(new ActivityFilter + { + Limit = PageItemLimit, + Page = page += 1, + State = new List { ActivityState.ManualReview }, + Product = new List { Product.LIC } + }); + + if (activityPage?.Items == null) + { + return new List(); + } + + var activities = activityPage.Items.ToList(); + var updatedActivities = await _activityDetailService.TryUpdateLicReferralStationDetail(activities); + + changes.AddRange(await AutoDecideActivities(updatedActivities)); + processedItemCount += activities.Count; + allItemsProcessed = processedItemCount == activityPage.Total; + } + catch (Exception ex) + { + Log.Error(ex, $"Failed to process all LIC Referral updates"); + allItemsProcessed = true; + } + } + + return changes; + } + + public async Task> AutoDecideActivities(IEnumerable activities) + { + var changes = new List(); + foreach (var activity in activities.Where(x => x.CanAutoDecision())) + { + try + { + var decision = await TryDecisionActivity(activity); + if (decision.State == activity.State) continue; + await _activityService.ApplyStateChange(decision); + changes.Add(decision); + } + catch (Exception ex) + { + Log.Error(ex, $"Failed to decision activity {activity.Id}"); + } + } + + return changes; + } + + private async Task> RejectDpActivities(IEnumerable activities) + { + var changes = new List(); + foreach (var activity in activities) + { + try + { + var change = new ActivityStateChange + { + ActivityId = activity.Id, + ActivityMatchId = activity.MatchingDpAccessoryActivityId, + Note = $"Debt Protection Matched Activity ID: {activity.MatchingDpAccessoryActivityId} Rejected", + State = ActivityState.Rejected + }; + await _activityService.ApplyStateChange(change); + changes.Add(change); + } + catch (Exception ex) + { + Log.Error(ex, $"Failed to decision activity {activity.Id}"); + } + } + + return changes; + } + + public async Task> UpdateAndDecideDirectDepositPendingReferralActivities() + { + var activityPage = await _activityService.ListByFilterAsync(new ActivityFilter + { + Limit = PageItemLimit, + Page = 1, + State = new List { ActivityState.PendingReferral }, + Product = new List { Product.DirectDeposit }, + Type = new List { ActivityType.Referral } + }); + + if (activityPage?.Items == null) + { + return new List(); + } + + if (activityPage.Total > PageItemLimit) + { + Log.Warning($"More than {PageItemLimit} items to process in the Update And Decide Direct Deposit Pending Referrals!"); + } + + var activities = activityPage.Items.ToList(); + var currentDateTime = DateTime.Now; + var changes = new List(); + + foreach (var activity in activities) + { + try + { + var activityHistory = + await _dnaService.GetTransactionHistory(activity.Detail.AccountNumber, activity.Created, currentDateTime, TransactionCodes); + + if (activityHistory == null) + { + var decision = new ActivityStateChange + { + ActivityId = activity.Id, + State = ActivityState.Rejected, + Note = "No Account Was Found With The Given Account Number", + AcceptedDate = activity.Accepted.Date.AddHours(12) + }; + + await _activityService.ApplyStateChange(decision); + changes.Add(decision); + } + + if (activityHistory != null && activityHistory.Any()) + { + var decision = new ActivityStateChange + { + ActivityId = activity.Id, + State = ActivityState.Accepted, + Note = "Account Had At Least One External Deposit Transaction", + AcceptedDate = activity.Accepted.Date.AddHours(12) + }; + + await _activityService.ApplyStateChange(decision); + changes.Add(decision); + } + } + catch (Exception ex) + { + Log.Error(ex, $"Failed to save direct deposit referral state as accepted for activity:{activity.Id}"); + } + } + + return changes; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/CardSaleDecisionStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/CardSaleDecisionStrategy.cs new file mode 100644 index 0000000..bcf4dc4 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/CardSaleDecisionStrategy.cs @@ -0,0 +1,24 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision; + +public class CardSaleDecisionStrategy : IDecisionStrategy +{ + public bool CanDecideActivity(Activity activity) + { + return (activity.Product == Product.ATMCard || activity.Product == Product.CheckCard) + && activity.Type == ActivityType.Other; + } + + public async Task DecideActivity(Activity activity) + { + return await Task.FromResult(new ActivityStateChange + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = "Auto-Accept. Will Match with Referral.", + State = ActivityState.Accepted + }); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/DebtProtectionAccessoryService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/DebtProtectionAccessoryService.cs new file mode 100644 index 0000000..8d7acfa --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/DebtProtectionAccessoryService.cs @@ -0,0 +1,78 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision; + +public class DebtProtectionAccessoryService +{ + private readonly IActivityService _activityService; + private readonly IActivityDetailService _activityDetailService; + + public DebtProtectionAccessoryService(IActivityService activityService, IActivityDetailService activityDetailService) + { + _activityService = activityService; + _activityDetailService = activityDetailService; + } + + public async Task> GetNonRejectedDpAccessoriesWithRejectedMatchedSale(List activities) + { + var returnActivities = new List(); + + foreach (var activity in activities) + { + if (activity.MatchingDpAccessoryActivityId != 0) + { + var dpActivity = await _activityService.GetActivity(activity.MatchingDpAccessoryActivityId); + if (dpActivity.State != ActivityState.Rejected) + { + returnActivities.Add(dpActivity); + } + } + } + + return returnActivities; + } + + public async Task> ProcessDpAccessoryUpdates(IEnumerable saleActivities) + { + var updatedActivities = new List(); + var filteredSaleActivities = saleActivities.Where(x => x.HasDpAccessory()); + + foreach (var saleActivity in filteredSaleActivities) + { + if (saleActivity.MatchingDpAccessoryActivityId == 0 && saleActivity.Detail.DebtProtectionAddDate != null) + { + var newDpAccessoryActivity = Activity.CopyActivityForDpAccessory(saleActivity); + saleActivity.MatchingDpAccessoryActivityId = await _activityService.SaveActivityAsync(newDpAccessoryActivity); + var change = new ActivityStateChange + { + ActivityId = saleActivity.Id, + ActivityMatchId = newDpAccessoryActivity.Id, + Note = $"{newDpAccessoryActivity.Product} Added On Activity Id: {newDpAccessoryActivity.Id}", + }; + await _activityService.ApplyStateChange(change); + updatedActivities.Add(newDpAccessoryActivity); + } + else + { + if (saleActivity.MatchingDpAccessoryActivityId != 0) + { + var existingDpAccessoryActivity = await _activityService.GetActivity(saleActivity.MatchingDpAccessoryActivityId); + if (existingDpAccessoryActivity.Id != 0 && DpAccessoryRequiresUpdate(saleActivity, existingDpAccessoryActivity)) + { + updatedActivities.Add(await _activityDetailService.TryUpdateDpAccessory(saleActivity, existingDpAccessoryActivity)); + Log.Information($"After TryUpdateDpAccessory {saleActivity.Id} existingDpAccessoryActivity {existingDpAccessoryActivity.Id})"); + } + } + } + } + + return updatedActivities; + } + + private static bool DpAccessoryRequiresUpdate(Activity saleActivity, Activity dpAccessoryActivity) + { + return saleActivity.Detail.DebtProtectionAddDate?.Date != dpAccessoryActivity.Detail.DebtProtectionAddDate?.Date + || saleActivity.Detail.DebtProtectionRemoveDate?.Date != dpAccessoryActivity.Detail.DebtProtectionRemoveDate?.Date; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/DebtProtectionDecisionStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/DebtProtectionDecisionStrategy.cs new file mode 100644 index 0000000..04f5e90 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/DebtProtectionDecisionStrategy.cs @@ -0,0 +1,57 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision; + +public class DebtProtectionDecisionStrategy : IDecisionStrategy +{ + private const Product DecidableProduct = Product.DebtProtection; + + public bool CanDecideActivity(Activity activity) + { + return activity.Product == DecidableProduct; + } + + public async Task DecideActivity(Activity activity) + { + if ( + activity.MatchingDpAccessoryActivityId != 0 + && activity.Detail.DebtProtectionAddDate != null + && activity.Detail.DebtProtectionRemoveDate == null + ) + { + return await Task.FromResult(new ActivityStateChange + { + AcceptedDate = activity.Detail.DebtProtectionAddDate?.AddHours(12), + ActivityId = activity.Id, + ActivityMatchId = activity.MatchingDpAccessoryActivityId, + Note = $"{DecidableProduct} Added From Activity Id: {activity.MatchingDpAccessoryActivityId}, Auto-Decision: {ActivityState.Accepted}", + State = ActivityState.Accepted + }); + } + + if ( + activity.MatchingDpAccessoryActivityId != 0 + && activity.Detail.DebtProtectionAddDate != null + && activity.Detail.DebtProtectionRemoveDate != null + ) + { + return await Task.FromResult(new ActivityStateChange + { + AcceptedDate = activity.Detail.DebtProtectionRemoveDate?.AddHours(12), + ActivityId = activity.Id, + ActivityMatchId = activity.MatchingDpAccessoryActivityId, + Note = $"Debt Protection Removed From Activity Id: {activity.MatchingDpAccessoryActivityId}, Auto-Decision: {ActivityState.Rejected}", + State = ActivityState.Rejected + }); + } + + return await Task.FromResult(new ActivityStateChange + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = $"Unable To Auto-Decision: {ActivityState.ManualReview}", + State = ActivityState.ManualReview + }); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/DebtProtectionEligibleLoanDecisionStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/DebtProtectionEligibleLoanDecisionStrategy.cs new file mode 100644 index 0000000..4b1a2ac --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/DebtProtectionEligibleLoanDecisionStrategy.cs @@ -0,0 +1,56 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision; + +public class DebtProtectionEligibleLoanDecisionStrategy : IDecisionStrategy +{ + public bool CanDecideActivity(Activity activity) + { + return activity.Product == Product.DebtProtectionEligibleLoan && activity.Type == ActivityType.Sale; + } + + public async Task DecideActivity(Activity activity) + { + if (activity.Detail.AccountStatus == null) + { + return await Task.FromResult(new ActivityStateChange() + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = "Cannot Determine Account Status.", + State = ActivityState.Rejected + }); + } + + if (activity.Detail.AccountStatus.Equals("ACT", StringComparison.InvariantCultureIgnoreCase)) + { + return await Task.FromResult(new ActivityStateChange + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = "Account is Active", + State = ActivityState.Accepted + }); + } + + if (activity.Detail.AccountStatus.Equals("CLS", StringComparison.CurrentCultureIgnoreCase)) + { + return await Task.FromResult(new ActivityStateChange + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = "Account is closed.", + State = ActivityState.Rejected + }); + } + + return await Task.FromResult(new ActivityStateChange + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = "Account is not Closed or Active. Awaiting Manual Review or Status Update.", + State = ActivityState.ManualReview + }); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/GapDecisionStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/GapDecisionStrategy.cs new file mode 100644 index 0000000..ff76c5b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/GapDecisionStrategy.cs @@ -0,0 +1,52 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision; + +public class GapDecisionStrategy : IDecisionStrategy +{ + public bool CanDecideActivity(Activity activity) + { + return activity.Product == Product.GAPInsurance; + } + + public async Task DecideActivity(Activity activity) + { + if ( + string.Equals(activity.Detail.AccountStatus, "Flagged", StringComparison.CurrentCultureIgnoreCase) + || string.Equals(activity.Detail.AccountStatus, "Reinstated", StringComparison.CurrentCultureIgnoreCase) + || string.Equals(activity.Detail.AccountStatus, "Sold", StringComparison.CurrentCultureIgnoreCase) + ) + { + return await Task.FromResult(new ActivityStateChange + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = "Auto-Decision: Gap Accepted", + State = ActivityState.Accepted + }); + } + + if ( + string.Equals(activity.Detail.AccountStatus, "Cancelled", StringComparison.CurrentCultureIgnoreCase) + || string.Equals(activity.Detail.AccountStatus, "Cancelled/Re-Quoted", StringComparison.CurrentCultureIgnoreCase) + ) + { + return await Task.FromResult(new ActivityStateChange + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = $"Auto-Decision: Gap {activity.Detail.AccountStatus}", + State = ActivityState.Rejected + }); + } + + return await Task.FromResult(new ActivityStateChange + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = "Auto-Decision: Gap Accepted", + State = ActivityState.Accepted + }); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/IActivityDecisionService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/IActivityDecisionService.cs new file mode 100644 index 0000000..0cbd8d7 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/IActivityDecisionService.cs @@ -0,0 +1,18 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision +{ + public interface IActivityDecisionService + { + Task RunNightlyDecisionProcess(NightlyIncentiveProcessRequest request); + Task TryDecisionActivity(Activity activity); + Task> DecisionActivityByState(ActivityState state); + Task> UpdateAndDecideManualReviewActivities(); + Task> UpdateAndDecideLicReferralActivities(); + Task> UpdateAndDecideDpEligibleLoanActivities(); + Task> UpdateAndDecideDirectDepositPendingReferralActivities(); + Task> AutoDecideActivities(IEnumerable activities); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/IDecisionStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/IDecisionStrategy.cs new file mode 100644 index 0000000..2842ba8 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/IDecisionStrategy.cs @@ -0,0 +1,10 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision +{ + public interface IDecisionStrategy + { + public bool CanDecideActivity(Activity activity); + public Task DecideActivity(Activity activity); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/ManualDecisionStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/ManualDecisionStrategy.cs new file mode 100644 index 0000000..dffd11c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/ManualDecisionStrategy.cs @@ -0,0 +1,31 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision; + +public class ManualDecisionStrategy : IDecisionStrategy +{ + public bool CanDecideActivity(Activity activity) + { + return activity.Product != Product.ATMCard && + activity.Product != Product.CheckCard && + activity.Product != Product.Mortgage && + activity.Product != Product.NewChecking && + activity.Product != Product.GAPInsurance && + activity.Product != Product.MechanicalBreakdownProtection && + activity.Product != Product.Vehicle && + activity.Product != Product.DebtProtectionEligibleLoan && (activity.Product == Product.DirectDeposit || + activity.Product == Product.CreditCardBalanceTransfer || + activity.Product == Product.LIC) && activity.Type != ActivityType.Referral; + } + + public async Task DecideActivity(Activity activity) + { + return await Task.FromResult(new ActivityStateChange + { + ActivityId = activity.Id, + Note = "Auto-Decision Unavailable: Please Manually Review", + State = ActivityState.ManualReview + }); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/MbpDecisionStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/MbpDecisionStrategy.cs new file mode 100644 index 0000000..7400251 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/MbpDecisionStrategy.cs @@ -0,0 +1,50 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision; + +public class MbpDecisionStrategy : IDecisionStrategy +{ + public bool CanDecideActivity(Activity activity) + { + return activity.Product == Product.MechanicalBreakdownProtection; + } + + public async Task DecideActivity(Activity activity) + { + if ( + string.Equals(activity.Detail.AccountStatus, "Sold", StringComparison.CurrentCultureIgnoreCase) + || string.Equals(activity.Detail.AccountStatus, "Flagged", StringComparison.CurrentCultureIgnoreCase) + ) + { + return await Task.FromResult(new ActivityStateChange + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = "Auto-Decision: MBP Accepted", + State = ActivityState.Accepted + }); + } + + if ( + string.Equals(activity.Detail.AccountStatus, "Cancelled", StringComparison.CurrentCultureIgnoreCase) + || string.Equals(activity.Detail.AccountStatus, "Cancelled/Re-Quoted", StringComparison.CurrentCultureIgnoreCase) + ) + { + return await Task.FromResult(new ActivityStateChange + { + ActivityId = activity.Id, + Note = $"Auto-Decision: MBP {activity.Detail.AccountStatus}", + State = ActivityState.Rejected + }); + } + + return await Task.FromResult(new ActivityStateChange + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = "Auto-Decision: MBP Accepted", + State = ActivityState.Accepted + }); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/MortgageSaleDecisionStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/MortgageSaleDecisionStrategy.cs new file mode 100644 index 0000000..7ddc275 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/MortgageSaleDecisionStrategy.cs @@ -0,0 +1,58 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision; + +public class MortgageSaleDecisionStrategy : IDecisionStrategy +{ + public bool CanDecideActivity(Activity activity) + { + return + (activity.Product.Equals(Product.Mortgage) || activity.Product.Equals(Product.FirstMortgageReferral)) + && activity.Type == ActivityType.Sale; + } + + public async Task DecideActivity(Activity activity) + { + if (activity.Detail.AccountStatus == null) + { + return await Task.FromResult(new ActivityStateChange() + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = "Could Not Determine Account Status.", + State = ActivityState.Rejected + }); + } + + if (activity.Detail.AccountStatus.Equals("ACT", StringComparison.InvariantCultureIgnoreCase)) + { + return await Task.FromResult(new ActivityStateChange + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = "Mortgage Loan is Active.", + State = ActivityState.Accepted + }); + } + + if (activity.Detail.AccountStatus.Equals("CLS", StringComparison.CurrentCultureIgnoreCase)) + { + return await Task.FromResult(new ActivityStateChange + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = "Mortgage Loan is closed.", + State = ActivityState.Rejected + }); + } + + return await Task.FromResult(new ActivityStateChange + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = "Mortgage is not Active or Closed. Waiting for Manual Review or Status Update.", + State = ActivityState.ManualReview + }); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/NewCheckingDecisionStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/NewCheckingDecisionStrategy.cs new file mode 100644 index 0000000..e536a7c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/NewCheckingDecisionStrategy.cs @@ -0,0 +1,67 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision; + +public class NewCheckingDecisionStrategy : IDecisionStrategy +{ + public bool CanDecideActivity(Activity activity) + { + return activity.Product == Product.NewChecking && activity.Type == ActivityType.Sale; + } + + public async Task DecideActivity(Activity activity) + { + if (activity.Detail.AccountStatus == null) + { + return await Task.FromResult(new ActivityStateChange() + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = "Could Not Determine Account Status", + State = ActivityState.Rejected + }); + } + + if (activity.Detail.AccountStatus != null && activity.SourceSystem == SystemType.DigitalBanking) + { + return await Task.FromResult(new ActivityStateChange() + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = "Digital Banking Sourced Sale", + State = ActivityState.Accepted + }); + } + + if (activity.Detail.AccountStatus.Equals("ACT", StringComparison.InvariantCultureIgnoreCase)) + { + return await Task.FromResult(new ActivityStateChange + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = "Checking account is Active", + State = ActivityState.Accepted + }); + } + + if (activity.Detail.AccountStatus.Equals("CLS", StringComparison.InvariantCultureIgnoreCase)) + { + return await Task.FromResult(new ActivityStateChange + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = "Checking account is Closed", + State = ActivityState.Rejected + }); + } + + return await Task.FromResult(new ActivityStateChange + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = "Checking Account is not Active or Closed. Waiting for Manual Review or Status Update", + State = ActivityState.ManualReview + }); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/ReferralDecisionStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/ReferralDecisionStrategy.cs new file mode 100644 index 0000000..c6fb8a2 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/ReferralDecisionStrategy.cs @@ -0,0 +1,70 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision; + +public class ReferralDecisionStrategy : IDecisionStrategy +{ + public bool CanDecideActivity(Activity activity) + { + return activity.Type == ActivityType.Referral && + (activity.Product == Product.ATMCard || + activity.Product == Product.CheckCard || + activity.Product == Product.NewChecking || + activity.Product == Product.Mortgage || + activity.Product == Product.DirectDeposit || + activity.Product == Product.CreditCardBalanceTransfer || + activity.Product == Product.LIC || + activity.Product == Product.FirstMortgageReferral || + activity.Product == Product.eStatement); + } + + public async Task DecideActivity(Activity activity) + { + if (activity.Product == Product.LIC) + { + if (!string.IsNullOrWhiteSpace(activity.Detail.StationName)) + { + if (activity.Detail.StationName.Equals("LIC Referral-Qualified", StringComparison.InvariantCultureIgnoreCase)) + { + return await Task.FromResult(new ActivityStateChange() + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = "LIC Referral In Station 'LIC Referral-Qualified'.", + State = ActivityState.Accepted + }); + } + + if (activity.Detail.StationName.Equals("LIC Referral- Not Qualified", StringComparison.InvariantCultureIgnoreCase)) + { + return await Task.FromResult(new ActivityStateChange() + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = "LIC Referral In Station 'LIC Referral- Not Qualified'.", + State = ActivityState.Rejected + }); + } + } + + return await Task.FromResult(new ActivityStateChange + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = "LIC Referral Not In Final Station. Waiting for Manual Review or Station Update.", + State = ActivityState.ManualReview + }); + } + + var daysBeforeExpiration = activity.Product != Product.FirstMortgageReferral ? "60" : "120"; + + return await Task.FromResult(new ActivityStateChange + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = $"Waiting for Matching Sale. Expires in {daysBeforeExpiration} days from creation.", + State = ActivityState.PendingReferral + }); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/VehicleDecisionStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/VehicleDecisionStrategy.cs new file mode 100644 index 0000000..7d6fb41 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/VehicleDecisionStrategy.cs @@ -0,0 +1,56 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision; + +public class VehicleDecisionStrategy : IDecisionStrategy +{ + public bool CanDecideActivity(Activity activity) + { + return activity.Product.Equals(Product.Vehicle) && activity.Type == ActivityType.Sale; + } + + public async Task DecideActivity(Activity activity) + { + if (activity.Detail.AccountStatus == null) + { + return await Task.FromResult(new ActivityStateChange() + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = "Could Not Determine Account Status.", + State = ActivityState.Rejected + }); + } + + if (activity.Detail.AccountStatus.Equals("ACT", StringComparison.InvariantCultureIgnoreCase)) + { + return await Task.FromResult(new ActivityStateChange + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = "Vehicle Loan is Active.", + State = ActivityState.Accepted + }); + } + + if (activity.Detail.AccountStatus.Equals("CLS", StringComparison.CurrentCultureIgnoreCase)) + { + return await Task.FromResult(new ActivityStateChange + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = "Vehicle Loan is closed.", + State = ActivityState.Rejected + }); + } + + return await Task.FromResult(new ActivityStateChange + { + AcceptedDate = activity.Accepted.Date.AddHours(12), + ActivityId = activity.Id, + Note = "Vehicle Loan is not Active or Closed. Waiting for Manual Review or Status Update.", + State = ActivityState.ManualReview + }); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/CommissionService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/CommissionService.cs new file mode 100644 index 0000000..81b0c44 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/CommissionService.cs @@ -0,0 +1,49 @@ +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public class CommissionService : ICommissionService +{ + private readonly ICommissionRepository _commissionRepository; + private readonly IUnitOfWork _unitOfWork; + + public CommissionService(ICommissionRepository commissionRepository, IUnitOfWork unitOfWork) + { + _commissionRepository = commissionRepository; + _unitOfWork = unitOfWork; + } + + public async Task> GetCommissionsAsync() + { + return await _commissionRepository.GetCommissionsAsync(); + } + + public async Task SaveCommissionsAsync(List commissionsToSave) + { + foreach (var commission in commissionsToSave) + { + if (commission.Id == 0) + { + var existing = await _commissionRepository.FindExistingCommission(commission.Role, commission.Product); + if (existing != null) + { + throw new CommissionExistsException($"Commission already exists for Role={existing.Role.Name}, Product={existing.Product}"); + } + + await _commissionRepository.SaveCommission(commission); + } + else + { + var existing = await _commissionRepository.FindExistingCommission(commission.Id); + if (existing != null) + { + existing.RatePerProduct = commission.RatePerProduct; + } + } + } + + await _unitOfWork.CompleteAsync(); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DashboardService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DashboardService.cs new file mode 100644 index 0000000..4f5d4cd --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DashboardService.cs @@ -0,0 +1,110 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Services.Rules; +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public class DashboardService : IDashboardService +{ + private readonly IDepartmentService _departmentService; + private readonly IActivityRepository _activityRepository; + private readonly IGoalRepository _goalRepository; + private readonly IBusinessRuleService _businessRuleService; + + public DashboardService(IDepartmentService departmentService, IActivityRepository activityRepository, + IGoalRepository goalRepository, IBusinessRuleService businessRuleService) + { + _departmentService = departmentService; + _activityRepository = activityRepository; + _goalRepository = goalRepository; + _businessRuleService = businessRuleService; + } + + public async Task GetRegionProgressForMonthRangeAsync( + Region region, MonthRange monthRange) + { + var departments = await _departmentService.GetDepartmentsByRegionAsync(region); + var regionProgress = new RegionProgress(region, monthRange); + foreach (var dept in departments.Where(x => x.State == DepartmentState.Active)) + { + var progress = await GetDepartmentProgressForMonthRangeAsync(dept.Id.Value, monthRange); + regionProgress.Departments.Add(progress); + } + + return regionProgress; + } + + public async Task GetBusinessLineProgressForMonthRangeAsync( + BusinessLine businessLine, MonthRange monthRange) + { + var departments = await _departmentService.GetDepartmentsByBusinessLineAsync(businessLine); + var businessLineProgress = new BusinessLineProgress(businessLine, monthRange); + foreach (var dept in departments.Where(x => x.State == DepartmentState.Active)) + { + var progress = await GetDepartmentProgressForMonthRangeAsync(dept.Id.Value, monthRange); + businessLineProgress.Departments.Add(progress); + } + + return businessLineProgress; + } + + public async Task GetEmployeeProgressForMonthRangeAsync(AppUser employee, MonthRange monthRange) + { + var users = new List() + { + employee + }; + var progress = new EmployeeProgress(employee, monthRange, new List()); + foreach (var dept in employee.Departments) + { + var employeeGoals = + await _goalRepository.FindPersonnelGoalsByMonthRange(dept.DepartmentId, users, monthRange); + progress.ApplyGoals(employeeGoals); + } + + progress.ApplyAll(await _activityRepository.FindEmployeeActivityByMonthRange(employee, monthRange)); + return progress; + } + + public async Task> GetDepartmentProgressForMonthRangeAsync( + List userDepartments, MonthRange monthRange) + { + var deptProgress = new List(); + foreach (var dept in userDepartments) + { + var perDeptProgress = await GetDepartmentProgressForMonthRangeAsync(dept.DepartmentId, monthRange); + if (perDeptProgress == null) continue; + deptProgress.Add(perDeptProgress); + } + + return deptProgress; + } + + public async Task GetDepartmentProgressForMonthRangeAsync(int departmentId, + MonthRange monthRange) + { + var employees = + await _departmentService.FindDepartmentAssignmentsForRange(departmentId, monthRange); + + var departmentActivity = await _activityRepository.FindDepartmentActivityByMonthRange( + departmentId, monthRange); + var departmentGoals = + await _goalRepository.FindDepartmentGoalsByMonthRange(departmentId, monthRange); + + var progress = new DepartmentProgress(departmentId, monthRange, + employees.Select(x => x.Employee.EmployeeId).Distinct().ToList(), + _businessRuleService.GetRules(PayoutType.Department), + _businessRuleService.GetRules(PayoutType.Personal)); + + progress.ApplyActivity(departmentActivity); + progress.ApplyGoals(departmentGoals); + var employeeGoals = + await _goalRepository.FindPersonnelGoalsByMonthRange(departmentId, progress.EmployeeList.ToList(), + monthRange); + progress.ApplyGoals(employeeGoals); + return progress; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DataCleaningService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DataCleaningService.cs new file mode 100644 index 0000000..700d116 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DataCleaningService.cs @@ -0,0 +1,13 @@ +using System.Text.RegularExpressions; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Services; + +public static class DataCleaningService +{ + public static string CleanedPhoneNumber(string input) + { + var cleanPhoneNumber = input == null ? string.Empty : Regex.Replace(input, @"\D", string.Empty); + cleanPhoneNumber = cleanPhoneNumber.Length == 11 ? cleanPhoneNumber[1..] : cleanPhoneNumber; + return cleanPhoneNumber; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DataParsingService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DataParsingService.cs new file mode 100644 index 0000000..df0f606 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DataParsingService.cs @@ -0,0 +1,58 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Services; + +public class DataParsingService +{ + public static List MapActivityMetaData(List dataStrings) + { + var parsedMetaData = new List(); + + foreach (var dataString in dataStrings) + { + if (!string.IsNullOrWhiteSpace(dataString)) + { + foreach (var item in dataString.Split(",", StringSplitOptions.RemoveEmptyEntries)) + { + parsedMetaData.Add(DetermineActivityMetaData(item)); + } + } + } + + return parsedMetaData; + } + + public static ActivityMetaData DetermineActivityMetaData(string metaDataString) + { + if (string.IsNullOrWhiteSpace(metaDataString)) + { + return null; + } + + if (metaDataString.Contains("@")) + { + return new ActivityMetaData + { + MetaData = metaDataString.Trim(), + MetaDataType = MetaDataType.Email + }; + } + + var phoneString = DataCleaningService.CleanedPhoneNumber(metaDataString); + if (phoneString.Length == 10) + { + return new ActivityMetaData + { + MetaData = phoneString.Trim(), + MetaDataType = MetaDataType.Phone + }; + } + + return new ActivityMetaData + { + MetaData = metaDataString.Trim(), + MetaDataType = MetaDataType.Unknown + }; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Dayforce/DayForceClientService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Dayforce/DayForceClientService.cs new file mode 100644 index 0000000..e8be084 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Dayforce/DayForceClientService.cs @@ -0,0 +1,78 @@ +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; +using System.Text; +using System.Net.Http; +using System.Net.Http.Headers; +using Newtonsoft.Json; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Dayforce; + +public class DayForceClientService : IDayForceClientService +{ + private readonly DayForceConfiguration _dayForceConfig; + private readonly Uri _baseUri; + + public DayForceClientService(DayForceConfiguration dayForceConfig) + { + _dayForceConfig = dayForceConfig; + _baseUri = new Uri(_dayForceConfig.Url + "/" + + _dayForceConfig.DayForceClientName + "/" + + _dayForceConfig.DayForceApiVersion); + } + + public async Task GetReportData(string reportXRefCode) + { + var url = _baseUri.ToString() + "/Reports/" + reportXRefCode; + var response = await GetHttpResponseAsync(url, true); + if (response.IsSuccessStatusCode) + { + try + { + var jsonString = await response.Content.ReadAsStringAsync(); + var payload = JsonConvert.DeserializeObject>(jsonString); + return payload.Data; + } + catch (Exception ex) + { + Log.Error(ex, "Error Deserializing Dayforce Report"); + } + } + else + { + Log.Error("Bad Status Code retrieving report data from Dayforce {0}", response.ToString()); + } + + return null; + } + + public async Task GetHttpResponseAsync(string apiUrl, bool attemptRedirect = false) + { + var client = TrustingHttpClient.GetClient(apiUrl); + var request = new HttpRequestMessage(HttpMethod.Get, apiUrl); + request.Headers.Authorization = CreateBasicAuthorizationHeader(request, + _dayForceConfig.DayForceUsername, + _dayForceConfig.DayForcePassword); + + var response = await client.SendAsync(request); + + if (attemptRedirect) + { + response = await GetHttpResponseAsync(response.RequestMessage.RequestUri.ToString(), false); + } + + return response; + } + + public AuthenticationHeaderValue CreateBasicAuthorizationHeader(HttpRequestMessage request, string userName, + string password) + { + if (!(string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password))) + { + return new AuthenticationHeaderValue( + "Basic", Convert.ToBase64String( + Encoding.UTF8.GetBytes(userName + ":" + password))); + } + + return null; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Dayforce/DayForceSyncService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Dayforce/DayForceSyncService.cs new file mode 100644 index 0000000..2ff0480 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Dayforce/DayForceSyncService.cs @@ -0,0 +1,293 @@ +using LCU.API.Internal.Operations.IncentiveApi.Services.Payout; +using LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Results; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.ApiResponse.Services; +using LCU.Library.Internal.Exceptions; +using ProcessResult = LCU.Library.Internal.IncentiveApiModels.Results.ProcessResult; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Dayforce; + +public class DayForceSyncService : IDayForceSyncService +{ + private readonly IUnitOfWork _unitOfWork; + private readonly IUserService _userService; + private readonly INotificationApiService _notificationService; + private readonly IPayoutService _payoutService; + private readonly IDayForceClientService _dayForceClientService; + private readonly ISettingsService _settingsService; + private readonly DayForceConfiguration _dayForceConfig; + private readonly ProcessResult _dayforceSyncProcessResult; + + public DayForceSyncService( + IUnitOfWork unitOfWork, + IUserService userService, + INotificationApiService notificationService, + IPayoutService payoutService, + IDayForceClientService dayForceClientService, + ISettingsService settingsService, + DayForceConfiguration dayForceConfig + ) + { + _unitOfWork = unitOfWork; + _userService = userService; + _notificationService = notificationService; + _payoutService = payoutService; + _dayForceClientService = dayForceClientService; + _settingsService = settingsService; + _dayForceConfig = dayForceConfig; + _dayforceSyncProcessResult = new ProcessResult(); + } + + public async Task SyncDayForceUsers(DayforceProcessRequest request) + { + try + { + Log.Information("Starting Dayforce Sync Process for Active Users."); + + if (request.MissingRoleNotification == null) + { + request.MissingRoleNotification = _dayForceConfig.DayForceManualReportRecipients; + } + + if (request.TerminatedEmployeeNotification == null) + { + request.TerminatedEmployeeNotification = _dayForceConfig.DayForceManualReportRecipients; + } + + var activeUsersResult = await SyncActiveDayForceUsers(request.MissingRoleNotification); + Log.Information("Completed Dayforce Sync Process for Active Users."); + _dayforceSyncProcessResult.MessageStack.Add($"Dayforce Sync Process for Active Users complete with {activeUsersResult.Count()}"); + + Log.Information("Starting Dayforce Sync Process for Terminated Users."); + var terminatedUsersResult = await SyncTerminatedDayForceUsers(request.TerminatedEmployeeNotification); + Log.Information("Completed Dayforce Sync Process for Terminated Users."); + + _dayforceSyncProcessResult.MessageStack.Add($"Dayforce Sync Process for Terminated Users complete with {terminatedUsersResult.Count()}"); + await QueueReturnResponse.QueueResponse(request, _dayforceSyncProcessResult.Success, _dayforceSyncProcessResult.MessageStack); + } + catch ( Exception exception ) + { + Log.Fatal(exception, "There was an Unhandled error Executing Dayforce Sync Process"); + var message = $"There was an issue with handling the Dayforce Sync Service Process Exception Message: {exception.Message}."; + _dayforceSyncProcessResult.MessageStack.Add(message); + _dayforceSyncProcessResult.Success = false; + await QueueReturnResponse.QueueResponse(request, _dayforceSyncProcessResult.Success, _dayforceSyncProcessResult.MessageStack); + throw new ServiceException(message); + } + } + + public async Task> SyncActiveDayForceUsers(List recipients) + { + var report = await _dayForceClientService.GetReportData(_dayForceConfig.DayForceActiveUserReport); + var results = new List(); + + if (report.Rows == null) + { + throw new Exception("Dayforce returned invalid active report"); + } + + foreach (var reportRow in report.Rows) + { + var receipt = new ActiveEmployeeReceipt(reportRow); + results.Add(receipt); + try + { + var employee = new ActiveEmployeeResult(reportRow); + receipt.EmployeeRow = employee; + + if (!employee.CanProcess()) + { + Log.Error("Cannot process dayforce employee. {reportRow}", reportRow.Values); + receipt.Status = ActiveEmployeeReceiptStatus.MISSING_DATA; + continue; + } + + var appUser = await _userService.SyncDayForceEmployee(employee); + receipt.AppUser = appUser; + receipt.Status = ActiveEmployeeReceiptStatus.SUCCESS; + + if (appUser.Departments.Any(x => x.Role == Role.MISSING_ROLE)) + { + receipt.Status = ActiveEmployeeReceiptStatus.MISSING_ROLE; + } + } + catch (Exception ex) + { + Log.Error(ex, "Failed to process dayforce employee. {reportRow}", reportRow.Values); + receipt.Status = ActiveEmployeeReceiptStatus.EXCEPTION; + } + } + + var nonExistentRoleAssignments = results + .Where(x => x.Status == ActiveEmployeeReceiptStatus.MISSING_ROLE) + .Select(x => x.AppUser) + .ToList(); + + if (nonExistentRoleAssignments.Any()) + { + var newRoleMappings = nonExistentRoleAssignments.Select(x => x.BaseRole).ToList().Distinct(); + await _notificationService.SendMissingRoleNotification(nonExistentRoleAssignments, recipients); + await _settingsService.SaveNewUnknownRoleMappingRangeAsync(newRoleMappings); + } + + return results; + } + + public async Task> SyncTerminatedDayForceUsers(List recipients) + { + var report = await _dayForceClientService.GetReportData(_dayForceConfig.DayForceTerminatedUserReport); + var terminated = new List(); + + if (report.Rows == null) + { + throw new Exception("Dayforce returned invalid termination report"); + } + + foreach (var reportRow in report.Rows) + { + try + { + var employee = new TerminatedEmployeeResult(reportRow); + var user = await _userService.TerminateDayForceEmployee(employee); + + if (user != null) + { + var manager = await _userService.FindManager(user); + var receipt = await _payoutService.CreateTerminatedEmployeePayouts(user, manager); + terminated.Add(receipt); + } + } + catch (Exception ex) + { + Log.Error(ex, "Failed to term dayforce employee. {reportRow}", reportRow.Values); + } + } + + if (terminated.Any()) + { + await _notificationService.SendTerminatedEmployeeNotification(terminated, recipients); + } + + return terminated; + } + + public async Task UpdateEmployeeDetails(EmployeeDetailsUpdate employeeDetailsUpdate) + { + var currentAppUser = await _userService.GetUserByAdEmailAsync(employeeDetailsUpdate.OldEmail); + if (currentAppUser == null) + { + return new EmployeeDetailsUpdateResult + { + UpdateMessage = $"Employee Details Update Failed, No Current Users Returned From Database" + }; + } + + if (!string.IsNullOrWhiteSpace(employeeDetailsUpdate.InvalidUpdateData())) + { + return new EmployeeDetailsUpdateResult + { + AppUser = currentAppUser, + UpdateMessage = $"Employee Details Update Failed, {employeeDetailsUpdate.InvalidUpdateData()}" + }; + } + + var existingUserForNewEmail = await _userService.GetActiveUserByAdEmailAsync(employeeDetailsUpdate.NewEmail); + if (existingUserForNewEmail != null + && !existingUserForNewEmail.AdEmail.Equals(currentAppUser.AdEmail, StringComparison.InvariantCultureIgnoreCase)) + { + return new EmployeeDetailsUpdateResult + { + AppUser = currentAppUser, + UpdateMessage = $"Employee Details Update Failed, Another Active User Already Exists With Email: {employeeDetailsUpdate.NewEmail}" + }; + } + + var existingUserForNewEmployeeId = await _userService.GetActiveUserByEmployeeIdAsync(employeeDetailsUpdate.NewEmployeeId); + if (existingUserForNewEmployeeId != null + && !existingUserForNewEmployeeId.EmployeeId.Equals(currentAppUser.EmployeeId, StringComparison.InvariantCultureIgnoreCase)) + { + return new EmployeeDetailsUpdateResult + { + AppUser = currentAppUser, + UpdateMessage = $"Employee Details Update Failed, Another Active User Already Exists With EmployeeId: {employeeDetailsUpdate.NewEmployeeId}" + }; + } + + var hcmUsersMatched = await MatchingHcmUserCount(employeeDetailsUpdate); + if (hcmUsersMatched != 1) + { + return new EmployeeDetailsUpdateResult + { + AppUser = currentAppUser, + UpdateMessage = $"Employee Details Update Failed, {hcmUsersMatched} Users Returned From HCM System Matching New Details" + }; + } + + return await _userService.UpdateEmployeeDetails(employeeDetailsUpdate); + } + + public async Task> GetMatchingHcmUsers(string email = "", string employeeId = "", string fullName = "") + { + if ( + string.IsNullOrWhiteSpace(email) + && string.IsNullOrWhiteSpace(employeeId) + && string.IsNullOrWhiteSpace(fullName) + ) + { + return new List(); + } + + var dayForceUsers = await GetAllHcmUsers(); + return dayForceUsers.Where(x => + x.EmployeeNumber.ToString().Equals(employeeId, StringComparison.InvariantCultureIgnoreCase) + || x.DisplayName.ToString().Equals(fullName, StringComparison.InvariantCultureIgnoreCase) + || x.LoginName.ToString().Equals(email, StringComparison.InvariantCultureIgnoreCase) + ) + .ToList(); + } + + private async Task> GetAllHcmUsers() + { + var report = await _dayForceClientService.GetReportData(_dayForceConfig.DayForceActiveUserReport); + var results = new List(); + + if (report.Rows == null) + { + throw new Exception("Dayforce returned invalid active report"); + } + + foreach (var reportRow in report.Rows) + { + try + { + var employee = new ActiveEmployeeResult(reportRow); + results.Add(employee); + } + catch (Exception ex) + { + Log.Error(ex, "Failed to parse dayforce employee. {reportRow}", reportRow.Values); + } + } + + return results.Where(x => x.CanProcess()).OrderBy(x => x.DisplayName).ToList(); + } + + private async Task MatchingHcmUserCount(EmployeeDetailsUpdate employeeDetailsUpdate) + { + var dayForceUsers = await GetAllHcmUsers(); + var matchingDayforceUsers = dayForceUsers.Where(x => + x.EmployeeNumber.ToString().Equals(employeeDetailsUpdate.NewEmployeeId, StringComparison.InvariantCultureIgnoreCase) + && x.DisplayName.ToString().Equals(employeeDetailsUpdate.NewFullName, StringComparison.InvariantCultureIgnoreCase) + && x.LoginName.ToString().Equals(employeeDetailsUpdate.NewEmail, StringComparison.InvariantCultureIgnoreCase) + ) + .ToList(); + + return matchingDayforceUsers.Count; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Dayforce/IDayForceClientService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Dayforce/IDayForceClientService.cs new file mode 100644 index 0000000..2a66b67 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Dayforce/IDayForceClientService.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Dayforce; + +public interface IDayForceClientService +{ + Task GetReportData(string reportXRefCode); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Dayforce/IDayForceSyncService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Dayforce/IDayForceSyncService.cs new file mode 100644 index 0000000..2801885 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Dayforce/IDayForceSyncService.cs @@ -0,0 +1,14 @@ +using LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Results; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Dayforce; + +public interface IDayForceSyncService +{ + Task SyncDayForceUsers(DayforceProcessRequest request); + Task> SyncActiveDayForceUsers(List recipients); + Task> SyncTerminatedDayForceUsers(List recipients); + Task> GetMatchingHcmUsers(string email = "", string employeeId = "", string fullName = ""); + Task UpdateEmployeeDetails(EmployeeDetailsUpdate employeeDetailsUpdate); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Dayforce/TrustingHttpClient.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Dayforce/TrustingHttpClient.cs new file mode 100644 index 0000000..8bc2384 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Dayforce/TrustingHttpClient.cs @@ -0,0 +1,24 @@ +using System.Net.Http; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Dayforce; + +public static class TrustingHttpClient +{ + public static HttpClient GetClient(string endPoint) + { + var handler = new HttpClientHandler + { + SslProtocols = System.Security.Authentication.SslProtocols.Tls12 | + System.Security.Authentication.SslProtocols.Tls11 | + System.Security.Authentication.SslProtocols.Tls, + ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true + }; + var httpClient = new HttpClient(handler) + { + Timeout = TimeSpan.FromSeconds(300), + BaseAddress = new Uri(endPoint) + }; + return httpClient; + } +} + diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DeadActivityService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DeadActivityService.cs new file mode 100644 index 0000000..8d61592 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DeadActivityService.cs @@ -0,0 +1,98 @@ +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public class DeadActivityService : IDeadActivityService +{ + private readonly IDeadActivityRepository _deadActivityRepository; + private readonly IUnitOfWork _unitOfWork; + + public DeadActivityService(IDeadActivityRepository deadActivityRepository, IUnitOfWork unitOfWork) + { + _deadActivityRepository = deadActivityRepository; + _unitOfWork = unitOfWork; + } + + public async Task SaveDeadActivity(DeadActivity newDeadActivity) + { + var deadActivity = await SaveDeadActivityRange(new List() { newDeadActivity }); + await _unitOfWork.CompleteAsync(); + return deadActivity.FirstOrDefault(); + } + + public async Task> SaveDeadActivityRange(List newDeadActivities) + { + var saved = new List() { }; + if (!newDeadActivities.Any()) + { + return saved; + } + + var uniqueDeadActivities = await FilterActivitiesByIdentifyingFields(newDeadActivities); + if (uniqueDeadActivities.Any()) + { + saved = await _deadActivityRepository.SaveDeadActivityRange(uniqueDeadActivities); + } + await _unitOfWork.CompleteAsync(); + return saved; + } + + public async Task GetDeadActivity(int activityId) + { + + var result = await _deadActivityRepository.GetDeadActivityById(activityId); + if (result == null) + { + throw new NotFoundException("Could not find dead activity with that id"); + } + + return result; + } + + public async Task ProcessDeadActivity(int deadActivityId, int activityRefId) + { + var result = await GetDeadActivity(deadActivityId); + result.LivingActivityId = activityRefId; + await _unitOfWork.CompleteAsync(); + return result; + } + + public async Task> FilterActivitiesByIdentifyingFields(List incomingDeadActivities) + { + var existingDeadActivity = + await _deadActivityRepository.GetDeadActivitiesBySourceIds(incomingDeadActivities.Select(x => x.SourceId) + .Where(x => !string.IsNullOrWhiteSpace(x)).ToList()); + + if (existingDeadActivity!= null && existingDeadActivity.Any()) + { + return incomingDeadActivities + .Where(newDeadActivity => !existingDeadActivity.Any(existing => + newDeadActivity.Product == existing.Product && + newDeadActivity.Type == existing.Type && + newDeadActivity.SourceId == existing.SourceId && + newDeadActivity.SourceSystem == existing.SourceSystem && + newDeadActivity.EmployeeId == existing.EmployeeId && + newDeadActivity.EmployeeEmail?.ToLowerInvariant() == existing.EmployeeEmail?.ToLowerInvariant() && + newDeadActivity.DepartmentId == existing.DepartmentId && + newDeadActivity.DeadDetail?.AccountNumber == existing.DeadDetail?.AccountNumber)).ToList(); + } + + return incomingDeadActivities; + } + + public async Task> ListByFilterAsync(DeadActivityFilter filter) + { + try + { + return await _deadActivityRepository.GetFilteredDeadActivities(filter); + } + catch (Exception ex) + { + Log.Error(ex, $"Failed to retrieve filtered Actions limit: {filter.Limit}, page: {filter.Page}"); + throw new ServerErrorException(); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DepartmentService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DepartmentService.cs new file mode 100644 index 0000000..91fe3a9 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DepartmentService.cs @@ -0,0 +1,244 @@ +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public class DepartmentService : IDepartmentService +{ + private readonly IDepartmentRepository _departmentRepository; + private readonly IUnitOfWork _unitOfWork; + private readonly IUserAssignmentService _userAssignmentService; + + public DepartmentService(IDepartmentRepository departmentRepository, + IUserAssignmentService userAssignmentService, IUnitOfWork unitOfWork) + { + _departmentRepository = departmentRepository; + _userAssignmentService = userAssignmentService; + _unitOfWork = unitOfWork; + } + + public async Task> GetAllDepartmentsAsync() + { + try + { + return await _departmentRepository.GetAllDepartmentsAsync(); + } + catch (Exception ex) + { + Log.Error(ex, "Failed to Retrieve All Departments"); + throw new ServerErrorException(); + } + } + + public async Task GetDepartmentByIdAsync(int id) + { + try + { + var department = await _departmentRepository.FindDepartmentByIdAsync(id); + if (department == null) + { + throw new NotFoundException($"Could Not Find Department With Id {id}"); + } + + return department; + } + catch (NotFoundException) + { + throw; + } + catch (Exception ex) + { + Log.Error(ex, $"Failed to Retrieve Department with Id {id}"); + throw new ServerErrorException(); + } + } + + public async Task> GetDepartmentsByRegionAsync(Region region) + { + try + { + var departments = await _departmentRepository.FindDepartmentsByRegionAsync(region); + if (departments == null) + { + throw new NotFoundException($"Could Not Find Departments for {region.Name}"); + } + + return departments; + } + catch (NotFoundException) + { + throw; + } + catch (Exception ex) + { + Log.Error(ex, $"Could Not Find Departments for {region.Name}"); + throw new ServerErrorException(); + } + } + + public async Task> GetDepartmentsByBusinessLineAsync(BusinessLine businessLine) + { + var departments = await _departmentRepository.FindDepartmentsByBusinessLineAsync(businessLine); + if (departments == null) + { + throw new NotFoundException($"Could Not Find Departments for {businessLine.Name}"); + } + return departments; + } + + public async Task> FindDepartmentAssignmentsForRange(int departmentId, + MonthRange monthRange) + { + return await _departmentRepository.FindDepartmentAssignmentsForRange(departmentId, monthRange); + } + + public async Task GetDepartmentsByDayforceGlCodeAsync(int glCode) + { + var department = await _departmentRepository.GetDepartmentByDayforceGlCode(glCode); + if (department == null) + { + throw new NotFoundException($"Could Not Find Department for {glCode}"); + } + + return department; + } + + public async Task> GetHierarchicalDepartmentsForEmployeeAsync(AppUser employee, bool includeManagers) + { + try + { + if (employee.Departments.Any(x => x.Role == Role.Admin)) + { + return await GetAllDepartmentsAsync(); + } + + var userDept = employee.Departments.FirstOrDefault(x => x.Role == Role.RegionalManager); + if (userDept != null && userDept.Department != null && !string.IsNullOrWhiteSpace(userDept.Department.RegionCode)) + { + var region = Region.FindByCode(userDept.Department.RegionCode); + if (region != null) + { + return await _departmentRepository.FindDepartmentsByRegionAsync(region); + } + } + + var includedRoles = Role.LeadershipRoles; + if (includeManagers) + { + includedRoles = Role.DepartmentManagerRoles; + } + + return employee.Departments + .Where(x => includedRoles.Contains(x.Role)) + .Select(x => x.Department).ToList(); + } + catch (Exception ex) + { + Log.Error(ex, "Failed to Retrieve departments user can manage"); + throw new ServerErrorException(); + } + } + + public async Task UpdateDepartmentAsync(Department department, int id) + { + try + { + if (!await IsDepartmentNameUnique(department.DepartmentName, id)) + { + Log.Debug("Attempted to add department with name that already exists."); + throw new DuplicateDataException("DepartmentName"); + } + + var existingDepartment = await _departmentRepository.FindDepartmentByIdAsync(id); + if (existingDepartment == null) + { + throw new NotFoundException($"Could Not Find Department With Id {id}"); + } + + existingDepartment.DepartmentName = department.DepartmentName; + existingDepartment.State = department.State; + existingDepartment.BusinessLineCode = department.BusinessLineCode; + existingDepartment.RegionCode = department.RegionCode; + + await _unitOfWork.CompleteAsync(); + return existingDepartment; + } + catch (DuplicateDataException) + { + throw; + } + catch (Exception ex) + { + Log.Error(ex, $"Failed to Update Department with Id {id}"); + throw new ServerErrorException(); + } + } + + public async Task SaveDepartmentAsync(Department newDepartment) + { + try + { + if (!await IsDepartmentNameUnique(newDepartment.DepartmentName)) + { + Log.Debug("Attempted to add department with name that already exists."); + throw new DuplicateDataException("DepartmentName"); + } + + var department = await _departmentRepository.SaveDepartmentAsync(newDepartment); + await _unitOfWork.CompleteAsync(); + return department; + } + catch (DuplicateDataException) + { + throw; + } + catch (Exception ex) + { + Log.Error(ex, $"Failed to save new Department with Name {newDepartment.DepartmentName}."); + throw new ServerErrorException(); + } + } + + public async Task IsDepartmentNameUnique(string departmentName, int? id = null) + { + var existingDepartments = await _departmentRepository.GetAllDepartmentsAsync(); + if (!existingDepartments.Any()) + { + return true; + } + + return !existingDepartments.Where(x => x.Id != id).Any(dept => + string.Equals(dept.DepartmentName, departmentName, StringComparison.InvariantCultureIgnoreCase)); + } + + public async Task GetDepartmentDetailsAsync(int id) + { + var department = await _departmentRepository.FindDepartmentByIdAsync(id); + if (department == null) + { + throw new NotFoundException($"Could Not Find Department With ID {id}"); + } + + var assignments = await _departmentRepository.FindCurrentDepartmentAssignments(id); + + var departmentDetails = new DepartmentDetails + { + Department = department, + Assignments = assignments, + }; + + if (department.RegionCode != null) + { + var regionalManagerAssignment = + await _userAssignmentService.GetUserAssignment(AssignmentType.RegionalManager, + department.RegionCode); + departmentDetails.RegionalManager = regionalManagerAssignment?.AppUser; + } + + return departmentDetails; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DispersalStrategy/DispersalStrategyService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DispersalStrategy/DispersalStrategyService.cs new file mode 100644 index 0000000..d63bd1c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DispersalStrategy/DispersalStrategyService.cs @@ -0,0 +1,286 @@ +using LCU.API.Internal.Operations.IncentiveApi.Extensions; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.API.Internal.Operations.IncentiveApi.Models.GoalDispersal; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.DispersalStrategy; + +public class DispersalStrategyService : IDispersalStrategyService +{ + private readonly IUserService _userService; + public DispersalStrategyService(IUserService userService) + { + _userService = userService; + } + + public GoalDispersalReceipt ExtrapolateFromExistingGoals(ExtrapolationGoalDispersalData data) + { + var receipt = new GoalDispersalReceipt(data.DepartmentId, GoalDispersalStrategy.ExtrapolateFromExistingGoals); + foreach (var (employeeId, modelEmployeeProductGoals) in data.ModelPersonnelGoals.Goals) + { + var employee = data.DispersalMonthPersonnel.FirstOrDefault(x => x.EmployeeId.Equals(employeeId)); + if (employee == null) + { + var missingEmployee = + data.ModelPersonnelGoals.Personnel.FirstOrDefault(x => x.EmployeeId == employeeId); + receipt.AddNewDispersalError(GoalDispersalError.UserNoLongerInDepartment, employeeId, + $"This employee is not assigned to dept: {data.DepartmentId} for the selected dispersal month", missingEmployee?.FullName); + continue; + } + foreach (var (goalProduct, dispersalMonthGoalValue) in data.DispersalMonthDepartmentGoals.ProductGoals) + { + ExtrapolateAndCreateEmployeeGoalChangesForProduct(employee,goalProduct, data, receipt); + } + } + + BuildDispersalReceipt(data.DispersalMonthPersonnel, receipt); + CheckForAndLogDuplicates(receipt.GoalChanges, "Extrapolate"); + + return receipt; + } + + private void ExtrapolateAndCreateEmployeeGoalChangesForProduct(AppUser employee, Product product, + ExtrapolationGoalDispersalData data, + GoalDispersalReceipt receipt) + { + var dispersalMonthGoalValue = data.DispersalMonthDepartmentGoals.ProductGoals[product]; + var dispersalEmployeePerProductGoal = data.DispersalMonthPersonnelGoals.Goals[employee.EmployeeId]; + var modelEmployeePerProductGoal = data.ModelPersonnelGoals.Goals[employee.EmployeeId]; + + try + { + double modelPersonnelValue = modelEmployeePerProductGoal.ProductGoals[product]; + double dispersalPersonnelValue = dispersalEmployeePerProductGoal.ProductGoals[product]; + double modelDeptGoalValue = data.ModelDepartmentGoals.ProductGoals[product]; + int newValue; + + if (modelDeptGoalValue == 0) + { + newValue = 0; + } + else + { + newValue = (int) Math.Ceiling(modelPersonnelValue / modelDeptGoalValue * dispersalMonthGoalValue); + } + + if (newValue == (int) dispersalPersonnelValue) + { + return; + } + + receipt.GoalChanges.Add(new GoalChange() + { + AppUserId = employee.Id, + DepartmentId = data.DepartmentId, + EndDate = data.DispersalMonthStartDate.EndOfMonth(), + PersonId = employee.EmployeeId, + Product = product, + StartDate = data.DispersalMonthStartDate, + Value = newValue + }); + } + catch (Exception ex) + { + Log.Error(ex, + $"Failed to create goal change for employee {employee.EmployeeId} on product: {product} in department {data.DepartmentId}"); + receipt.AddNewDispersalError(GoalDispersalError.ErrorsDuringDisperse, employee.EmployeeId, + $"Failed to create goal change for product: {product}. See Logs."); + } + } + + private List BuildDispersalReceipt(List dispersalMonthPersonnel, GoalDispersalReceipt receipt) + { + dispersalMonthPersonnel.ForEach(user => + { + if (receipt.GoalChanges.Any(x => x.PersonId == user.EmployeeId && x.Value > 0)) + { + if (!user.UserHasPermission(Permission.HaveSalesGoals)) + { + receipt.AddNewDispersalError(GoalDispersalError.DispersedToUserWithoutPermission, + user.EmployeeId, "User received goals, but does not have HasSalesGoal permission. May require manual update.", user.FullName); + } + } + + if(!receipt.GoalChanges.Any(x => x.PersonId == user.EmployeeId && x.Value > 0)) + { + if (user.UserHasPermission(Permission.HaveSalesGoals)) + { + receipt.AddNewDispersalError(GoalDispersalError.NewUserRequiresManualEntry, user.EmployeeId, + "User did not receive goals, but has HasSalesGoal permission. May require manual update.", user.FullName); + } + } + }); + + return receipt.GoalDispersalErrors.ToList(); + } + + public GoalDispersalReceipt DefaultWeightedDispersal(DefaultWeightedGoalDispersalData data) + { + var receipt = new GoalDispersalReceipt(data.DepartmentId, GoalDispersalStrategy.DefaultDisperseWithWeights); + var allRolesInDept = data.DispersalMonthPersonnel.Select(x => x.Departments.First(d => d.DepartmentId == data.DepartmentId).Role).ToList(); + + foreach (var employee in data.DispersalMonthPersonnel) + { + foreach (var (goalProduct, goalValue) in data.DispersalMonthDepartmentGoals.ProductGoals) + { + try + { + var userDept = employee.Departments.First(x => x.DepartmentId == data.DepartmentId); + receipt.GoalChanges.Add(new GoalChange() + { + AppUserId = employee.Id, + DepartmentId = data.DepartmentId, + EndDate = data.DispersalMonthStartDate.EndOfMonth(), + PersonId = employee.EmployeeId, + Product = goalProduct, + StartDate = data.DispersalMonthStartDate, + Value = CalculateDefaultWeightedValue(userDept.Role, goalValue, allRolesInDept) + }); + } + catch (Exception ex) + { + Log.Error(ex, + $"Failed to create goal change for employee {employee.EmployeeId} on product: {goalProduct} in department {data.DepartmentId}"); + receipt.AddNewDispersalError(GoalDispersalError.ErrorsDuringDisperse, employee.EmployeeId, + $"Failed to create goal change for product: {goalProduct}. See logs."); + } + } + } + + CheckForAndLogDuplicates(receipt.GoalChanges, "DefaultWeight"); + + return receipt; + } + + public int CalculateDefaultWeightedValue(Role role, int goalValue, List allRolesInDept) + { + if (role == Role.PFO) + { + return (int) Math.Ceiling(goalValue * 0.6 / allRolesInDept.Count(x => x.Equals(Role.PFO))); + } + + if (role == Role.BM) + { + return (int) Math.Ceiling(goalValue * 0.25 / allRolesInDept.Count(x => x.Equals(Role.BM))); + } + + if (role == Role.ABM) + { + return (int) Math.Ceiling(goalValue * 0.15 / allRolesInDept.Count(x => x.Equals(Role.ABM))); + } + + return 0; + } + + public GoalDispersalReceipt ExtrapolateSingleDepartmentProduct(DepartmentGoalExtrapolationRequest request, PersonnelGoals departmentPersonnelGoals) + { + var receipt = new GoalDispersalReceipt(request.DepartmentId, GoalDispersalStrategy.ExtrapolateSingleDepartmentProduct); + + foreach (var (employeeId, personnelPerProductGoals) in departmentPersonnelGoals.Goals) + { + try + { + var existingEmployeeGoal = personnelPerProductGoals.ProductGoals[request.Product]; + int newValue; + if (request.OriginalDepartmentGoal == 0) + { + newValue = existingEmployeeGoal; + } + else + { + newValue = (int)Math.Ceiling((decimal)request.NewDepartmentGoal / request.OriginalDepartmentGoal * + existingEmployeeGoal); + } + + if (newValue == existingEmployeeGoal) + { + continue; + } + + var appUser = _userService.GetActiveUserByEmployeeIdAsync(employeeId).GetAwaiter().GetResult(); + receipt.GoalChanges.Add(new GoalChange() + { + AppUserId = appUser.Id, + PersonId = employeeId, + DepartmentId = request.DepartmentId, + Value = newValue, + Product = request.Product, + StartDate = request.DepartmentGoalStartDate.StartOfMonth(), + EndDate = request.DepartmentGoalEndDate.EndOfMonth() + }); + } + catch (Exception ex) + { + Log.Error(ex, + $"Failed to create goal change for employee {employeeId} on product: {request.Product} in department {request.DepartmentId}"); + receipt.AddNewDispersalError(GoalDispersalError.ErrorsDuringDisperse, employeeId, + $"Failed to create goal change for product: {request.Product}. See logs."); + } + } + + return receipt; + + } + + public GoalDispersalReceipt + CustomWeightedDispersal(CustomWeightedDispersalRequest request, List personnel, int departmentGoal) + { + var receipt = new GoalDispersalReceipt(request.DepartmentId, GoalDispersalStrategy.CustomWeightedDispersal); + var dispersalDate = new DateTime(request.year, request.month, 01); + var allRolesInDept = personnel + .Select(x => x.Departments.FirstOrDefault(d => d.DepartmentId == request.DepartmentId)) + .Where(x => x != null) + .Select(x => x.Role) + .ToList(); + + foreach (var user in personnel) + { + var userDept = user.Departments.FirstOrDefault(x => x.DepartmentId == request.DepartmentId); + if(userDept == null){continue;} + if (request.DispersalWeightByRole.TryGetValue(userDept.Role.Name, out var percentage)) + { + var newValue = (int)Math.Ceiling(departmentGoal * percentage / + allRolesInDept.Count(x => + x.Name.ToLowerInvariant() == userDept.Role.Name.ToLowerInvariant())); + receipt.GoalChanges.Add(new GoalChange() + { + AppUserId = user.Id, + PersonId = user.EmployeeId, + DepartmentId = request.DepartmentId, + Value = newValue, + Product = request.DispersalProduct, + StartDate = dispersalDate.StartOfMonth(), + EndDate = dispersalDate.EndOfMonth() + }); + } + } + + CheckForAndLogDuplicates(receipt.GoalChanges, "CustomWeight"); + + return receipt; + } + + + private void CheckForAndLogDuplicates(List goalChanges, string strategy) + { + if (goalChanges == null) + { + return; + } + + var duplicateGoals = goalChanges.GroupBy(g => new { + g.StartDate, + g.EndDate, + g.Product, + g.Value, + g.PersonId }) + .Where(g => g.Skip(1).Any()); + + if (duplicateGoals.Any()) + { + Log.Information($"Duplicate goals in department {goalChanges.Select(g => g.DepartmentId).FirstOrDefault()} during {strategy} strategy."); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DispersalStrategy/IDispersalStrategyService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DispersalStrategy/IDispersalStrategyService.cs new file mode 100644 index 0000000..74da75a --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DispersalStrategy/IDispersalStrategyService.cs @@ -0,0 +1,17 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.API.Internal.Operations.IncentiveApi.Models.GoalDispersal; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.DispersalStrategy; + +public interface IDispersalStrategyService +{ + public GoalDispersalReceipt ExtrapolateFromExistingGoals(ExtrapolationGoalDispersalData data); + public GoalDispersalReceipt DefaultWeightedDispersal(DefaultWeightedGoalDispersalData data); + public int CalculateDefaultWeightedValue(Role userRole, int goalValue, List allRolesInDept); + public GoalDispersalReceipt ExtrapolateSingleDepartmentProduct(DepartmentGoalExtrapolationRequest request, + PersonnelGoals departmentPersonnelGoals); + + public GoalDispersalReceipt CustomWeightedDispersal(CustomWeightedDispersalRequest request, + List personnel, int departmentGoal); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/AtmCheckCardActivityService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/AtmCheckCardActivityService.cs new file mode 100644 index 0000000..cc280cb --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/AtmCheckCardActivityService.cs @@ -0,0 +1,97 @@ +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Results; +using LCU.Library.Internal.LCUServiceFactory.Configurations; +using LCU.API.Internal.Operations.IncentiveApi.Services.IncentiveProcesses; +using Microsoft.Extensions.DependencyInjection; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.DnaActivityServices; + +public class AtmCheckCardActivityService : IncentiveProcessesService, IIncentiveProcessesService +{ + private readonly ICoreApiWrapperService _coreApiWrapperService; + private readonly IServiceScopeFactory _serviceScopeFactory; + private readonly ProcessResult _incentiveProcessResult; + + public AtmCheckCardActivityService(ServicesFactoryConfiguration servicesFactoryConfiguration, + ICoreApiWrapperService coreApiWrapperService, + IServiceScopeFactory serviceScopeFactory) + : base(servicesFactoryConfiguration) + { + _coreApiWrapperService = coreApiWrapperService; + _serviceScopeFactory = serviceScopeFactory; + _incentiveProcessResult = new ProcessResult(); + } + + public async Task SyncLatestActivitiesFromDna(IncentiveProcessRequest request) + { + try + { + var externalActivities = await ProcessLatestActivity(); + var message = $"Added {externalActivities.Count()} Atm Check Card Sales from DNA"; + Log.Information(message); + _incentiveProcessResult.MessageStack.Add(message); + } + catch (Exception exception) + { + var message = $"There was an issue with Atm Check Card Sales from DNA process. Exception Message: {exception.Message}."; + _incentiveProcessResult.MessageStack.Add(message); + _incentiveProcessResult.Success = false; + } + + return _incentiveProcessResult; + } + + public async Task> ProcessLatestActivity() + { + try + { + using var scope = _serviceScopeFactory.CreateScope(); + var lastUpdate = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().GetLastActivityProcessedData(Process.ImportAtmCheckCardSalesFromDna) ?? throw new MissingProcessorRunHistoryException(Process.ImportAtmCheckCardSalesFromDna)); + var allActivities = (await _coreApiWrapperService.GetNewAtmCheckCardSalesActivity(lastUpdate.LastProcessedRecordTimestamp)).ToList(); + var filteredActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().GetFilteredExternalActivities(allActivities.Cast().ToList())); + var mappedActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().HandleMapExternalActivities(filteredActivities.ToList(), Product.CheckCard, ActivityType.Other)); + + if (mappedActivities.Count > 0) + { + foreach (var activity in mappedActivities) + { + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().HandleIncomingActivityDecision(activity, Product.CheckCard, ActivityType.Other)); + } + } + + if (allActivities.Count < 1) + { + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().UpdateProcessorHistory(Process.ImportAtmCheckCardSalesFromDna, lastUpdate.LastProcessedRecordId, lastUpdate.LastProcessedRecordTimestamp)); + return filteredActivities; + } + + var latestActivity = allActivities.Where(activity => activity.ActivityDateTime == allActivities.Select(ca => ca.ActivityDateTime).Max()).OrderByDescending(x => x.ActivityNbr).First(); + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().UpdateProcessorHistory(Process.ImportAtmCheckCardSalesFromDna, latestActivity.ActivityNbr, latestActivity.ActivityDateTime)); + + return filteredActivities; + } + + catch (ServiceException serviceException) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Error(serviceException, $"Import Check and ATM Cards Failure. " + + $"Failed to Process Latest Check and ATM Cards. Will try again from same starting position on next run."); + throw; + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Import Check and ATM Cards Failure. " + + $"Failed to Process Latest Check and ATM Cards. Will try again from same starting position on next run."); + throw; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/DnaActivityProcessingService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/DnaActivityProcessingService.cs new file mode 100644 index 0000000..9f75ca7 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/DnaActivityProcessingService.cs @@ -0,0 +1,100 @@ +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision; +using LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.API.Internal.Operations.IncentiveApi.Services; + +namespace LCU.API.Internal.Operations.IncentiveApi.DnaActivityServices; + +public class DnaActivityProcessingService : IDnaActivityProcessingService +{ + private readonly IActivityService _activityService; + private readonly IActivityDecisionService _activityDecisionService; + private readonly IExternalActivityService _externalActivityService; + private readonly IProcessorHistoryService _processorHistoryService; + private readonly IDeadActivityService _deadActivityService; + + public DnaActivityProcessingService(IActivityService activityService, IProcessorHistoryService processorHistoryService, + IActivityDecisionService activityDecisionService, IDeadActivityService deadActivityService, IExternalActivityService externalActivityService) + { + _activityDecisionService = activityDecisionService; + _activityService = activityService; + _externalActivityService = externalActivityService; + _deadActivityService = deadActivityService; + _processorHistoryService = processorHistoryService; + } + + public async Task GetLastActivityProcessedData(Process processName) + { + var lastActivityProcess = await _processorHistoryService.GetProcessorHistory(processName); + + if (lastActivityProcess == null) + { + throw new MissingProcessorRunHistoryException(processName); + } + return lastActivityProcess; + } + + public async Task> GetFilteredExternalActivities(List activityList) + { + return await _activityService.FilterExternalActivitiesByExistingSourceId(activityList); + } + + public async Task> HandleMapExternalActivities(List externalActivityList, Product productName, ActivityType activityType) + { + var mappedActivities = new List(); + foreach (var item in externalActivityList) + { + try + { + mappedActivities.Add(await _externalActivityService.TryMapExternalActivity(item)); + } + catch (Exception ex) + { + Log.Error(ex, $"Could not map activity for item with product {item.GetProductType()} of type {item.GetActivityType()}. Will Try to save as Dead Activity."); + await _deadActivityService.SaveDeadActivity(new DeadActivity(item.GetSourceId(), SystemType.Dna, DeadActivityReason.FailureToMap, "Total Mapping Failure. Only SourceID will be saved.")); + } + } + return mappedActivities; + } + + public async Task HandleIncomingActivityDecision(Activity activity, Product productName, ActivityType activityType) + { + try + { + var activityId = await _activityService.SaveActivityAsync(activity); + var savedActivity = await _activityService.GetActivity(activityId); + if (savedActivity.CanAutoDecision()) + { + var decision = await _activityDecisionService.TryDecisionActivity(activity); + await _activityService.ApplyStateChange(decision); + } + } + catch (DeadActivityException ex) + { + var deadActivity = new DeadActivity(activity, DeadActivityReason.MissingRequiredField, $"{ex.Message}"); + deadActivity = await _deadActivityService.SaveDeadActivity(deadActivity); + if (deadActivity != null) + { + Log.Warning($"Activity from Queue was Incomplete product {productName}-{activityType} and sourceId: {activity.SourceId}. Saving as Dead Activity with ID: {deadActivity.Id}"); + } + } + catch (Exception ex) + { + Log.Fatal(ex, $"Failed to save or update an incoming activity: {productName}-{activityType} and sourceId: {activity.SourceId}. This Activity will be lost. Please investigate."); + } + } + + public async Task UpdateProcessorHistory(Process process, long lastProcessedId, DateTime lastProcessedDateTime) + { + await _processorHistoryService.SaveProcessorHistory(new ProcessorHistoryRecord() + { + Process = process, + LastSuccessfulRunTime = DateTime.UtcNow, + LastProcessedRecordId = lastProcessedId, + LastProcessedRecordTimestamp = lastProcessedDateTime, + }); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/DpEligibleLoanSalesActivityService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/DpEligibleLoanSalesActivityService.cs new file mode 100644 index 0000000..45032b8 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/DpEligibleLoanSalesActivityService.cs @@ -0,0 +1,96 @@ +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Results; +using LCU.API.Internal.Operations.IncentiveApi.Services.IncentiveProcesses; +using LCU.Library.Internal.LCUServiceFactory.Configurations; +using Microsoft.Extensions.DependencyInjection; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.DnaActivityServices; + +public class DpEligibleLoanSalesActivityService : IncentiveProcessesService, IIncentiveProcessesService +{ + private readonly ICoreApiWrapperService _coreApiWrapperService; + private readonly IServiceScopeFactory _serviceScopeFactory; + private readonly ProcessResult _incentiveProcessResult; + + public DpEligibleLoanSalesActivityService(ServicesFactoryConfiguration servicesFactoryConfiguration, + ICoreApiWrapperService coreApiWrapperService, IServiceScopeFactory serviceScopeFactory) + : base(servicesFactoryConfiguration) + { + _coreApiWrapperService = coreApiWrapperService; + _serviceScopeFactory = serviceScopeFactory; + _incentiveProcessResult = new ProcessResult(); + } + + public async Task SyncLatestActivitiesFromDna(IncentiveProcessRequest request) + { + try + { + var externalActivities = await ProcessLatestActivity(); + var message = $"Added {externalActivities.Count()} Debt Protection Eligible Loan Sales from DNA"; + Log.Information(message); + _incentiveProcessResult.MessageStack.Add(message); + } + catch(Exception exception) + { + var message = $"There was an issue with Debt Protection Eligible Loan Sales from DNA process. Exception Message: {exception.Message}."; + _incentiveProcessResult.MessageStack.Add(message); + _incentiveProcessResult.Success = false; + } + + return _incentiveProcessResult; + } + + public async Task> ProcessLatestActivity() + { + try + { + using var scope = _serviceScopeFactory.CreateScope(); + var lastUpdate = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().GetLastActivityProcessedData(Process.ImportDpEligibleLoanSalesFromDna) ?? throw new MissingProcessorRunHistoryException(Process.ImportDpEligibleLoanSalesFromDna)); + var allActivities = (await _coreApiWrapperService.GetNewDpEligibleSaleActivities(lastUpdate.LastProcessedRecordTimestamp)).ToList(); + var filteredActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().GetFilteredExternalActivities(allActivities.Cast().ToList())); + var mappedActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().HandleMapExternalActivities(filteredActivities.ToList(), Product.DebtProtectionEligibleLoan, ActivityType.Sale)); + + if (mappedActivities.Count > 0) + { + foreach (var activity in mappedActivities) + { + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().HandleIncomingActivityDecision(activity, Product.DebtProtectionEligibleLoan, ActivityType.Sale)); + } + } + + if (allActivities.Count < 1) + { + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().UpdateProcessorHistory(Process.ImportDpEligibleLoanSalesFromDna, lastUpdate.LastProcessedRecordId, lastUpdate.LastProcessedRecordTimestamp)); + return filteredActivities; + } + + var latestActivity = allActivities.Where(activity => activity.ActivityDateTime == allActivities.Select(ca => ca.ActivityDateTime).Max()).OrderByDescending(x => x.ActivityNbr).First(); + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().UpdateProcessorHistory(Process.ImportDpEligibleLoanSalesFromDna, latestActivity.ActivityNbr, latestActivity.ActivityDateTime)); + + return filteredActivities; + } + + catch (ServiceException serviceException) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Error(serviceException, $"Import Debt Protection Eligible Loans Failure. " + + $"Failed to Process Debt Protection Eligible Loans. Will try again from same starting position on next run."); + throw; + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Import Debt Protection Eligible Loans Failure. " + + $"Failed to Process Debt Protection Eligible Loans. Will try again from same starting position on next run."); + throw; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/IDnaActivityProcessingService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/IDnaActivityProcessingService.cs new file mode 100644 index 0000000..bc5fa10 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/IDnaActivityProcessingService.cs @@ -0,0 +1,14 @@ +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public interface IDnaActivityProcessingService +{ + Task GetLastActivityProcessedData(Process processName); + Task> GetFilteredExternalActivities(List activityList); + Task HandleIncomingActivityDecision(Activity activity, Product productName, ActivityType activityType); + Task> HandleMapExternalActivities(List externalActivityList, Product productName, ActivityType activityType); + Task UpdateProcessorHistory(Process process, long lastProcessedId, DateTime lastProcessedDateTime); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/MortgageSaleActivityService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/MortgageSaleActivityService.cs new file mode 100644 index 0000000..835e4c6 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/MortgageSaleActivityService.cs @@ -0,0 +1,93 @@ +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Results; +using LCU.Library.Internal.LCUServiceFactory.Configurations; +using LCU.API.Internal.Operations.IncentiveApi.Services.IncentiveProcesses; +using Microsoft.Extensions.DependencyInjection; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.DnaActivityServices; + +public class MortgageSaleActivityService : IncentiveProcessesService, IIncentiveProcessesService +{ + private readonly ICoreApiWrapperService _coreApiWrapperService; + private readonly IServiceScopeFactory _serviceScopeFactory; + private readonly ProcessResult _incentiveProcessResult; + + public MortgageSaleActivityService(ServicesFactoryConfiguration servicesFactoryConfiguration, + ICoreApiWrapperService coreApiWrapperService, IServiceScopeFactory serviceScopeFactory) + : base(servicesFactoryConfiguration) + { + _coreApiWrapperService = coreApiWrapperService; + _serviceScopeFactory = serviceScopeFactory; + _incentiveProcessResult = new ProcessResult(); + } + + public async Task SyncLatestActivitiesFromDna(IncentiveProcessRequest request) + { + try + { + var externalActivities = await ProcessLatestActivity(); + var message = $"Added {externalActivities.Count()} Mortgage Accounts from DNA"; + Log.Information(message); + _incentiveProcessResult.MessageStack.Add(message); + } + catch (Exception exception) + { + var message = $"There was an issue with Mortgage Loan Accounts from DNA process. Exception Message: {exception.Message}."; + _incentiveProcessResult.MessageStack.Add(message); + _incentiveProcessResult.Success = false; + } + return _incentiveProcessResult; + } + + public async Task> ProcessLatestActivity() + { + try + { + using var scope = _serviceScopeFactory.CreateScope(); + var lastUpdate = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().GetLastActivityProcessedData(Process.ImportMortgageSalesFromDna) ?? throw new MissingProcessorRunHistoryException(Process.ImportMortgageSalesFromDna)); + var allActivities = (await _coreApiWrapperService.GetNewMortgageSaleActivities(lastUpdate.LastProcessedRecordTimestamp)).ToList(); + var filteredActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().GetFilteredExternalActivities(allActivities.Cast().ToList())); + var mappedActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().HandleMapExternalActivities(filteredActivities.ToList(), Product.Mortgage, ActivityType.Sale)); + + if (mappedActivities.Count > 0) + { + foreach (var activity in mappedActivities) + { + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().HandleIncomingActivityDecision(activity, Product.Mortgage, ActivityType.Sale)); + } + } + + if (allActivities.Count < 1) + { + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().UpdateProcessorHistory(Process.ImportMortgageSalesFromDna, lastUpdate.LastProcessedRecordId, lastUpdate.LastProcessedRecordTimestamp)); + return filteredActivities; + } + + var latestActivity = allActivities.Where(activity => activity.ActivityDateTime == allActivities.Select(ca => ca.ActivityDateTime).Max()).OrderByDescending(x => x.ActivityNbr).First(); + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().UpdateProcessorHistory(Process.ImportMortgageSalesFromDna, latestActivity.ActivityNbr, latestActivity.ActivityDateTime)); + + return filteredActivities; + } + + catch (ServiceException serviceException) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Error(serviceException, $"Import Dna Mortgage Accounts Failure. " + + $"Failed to Process Latest Dna Mortgage Accounts. Will try again from same starting position on next run."); + throw; + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Import Dna Mortgage Accounts Failure. " + + $"Failed to Process Latest Dna Mortgage Accounts. Will try again from same starting position on next run."); + throw; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/NewCheckingAccountActivityService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/NewCheckingAccountActivityService.cs new file mode 100644 index 0000000..5ae3ec4 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/NewCheckingAccountActivityService.cs @@ -0,0 +1,95 @@ +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Results; +using LCU.Library.Internal.LCUServiceFactory.Configurations; +using LCU.API.Internal.Operations.IncentiveApi.Services.IncentiveProcesses; +using Microsoft.Extensions.DependencyInjection; +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.DnaActivityServices; + +public class NewCheckingAccountActivityService : IncentiveProcessesService, IIncentiveProcessesService +{ + private readonly ICoreApiWrapperService _coreApiWrapperService; + private readonly IServiceScopeFactory _serviceScopeFactory; + private readonly ProcessResult _incentiveProcessResult; + + public NewCheckingAccountActivityService(ServicesFactoryConfiguration servicesFactoryConfiguration, + ICoreApiWrapperService coreApiWrapperService, + IServiceScopeFactory serviceScopeFactory) + : base(servicesFactoryConfiguration) + { + _coreApiWrapperService = coreApiWrapperService; + _serviceScopeFactory = serviceScopeFactory; + _incentiveProcessResult = new ProcessResult(); + } + + public async Task SyncLatestActivitiesFromDna(IncentiveProcessRequest request) + { + try + { + var externalActivities = await ProcessLatestActivity(); + var message = $"Added {externalActivities.Count()} Checking Accounts from DNA"; + Log.Information(message); + _incentiveProcessResult.MessageStack.Add(message); + } + catch (Exception exception) + { + var message = $"There was an issue with Checking Accounts from DNA process. Exception Message: {exception.Message}."; + _incentiveProcessResult.MessageStack.Add(message); + _incentiveProcessResult.Success = false; + } + return _incentiveProcessResult; + } + + public async Task> ProcessLatestActivity() + { + try + { + using var scope = _serviceScopeFactory.CreateScope(); + var lastUpdate = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().GetLastActivityProcessedData(Process.ImportCheckingAccountSalesFromDna) ?? throw new MissingProcessorRunHistoryException(Process.ImportCheckingAccountSalesFromDna)); + var allActivities = (await _coreApiWrapperService.GetNewCheckingAccountSalesActivity(lastUpdate.LastProcessedRecordTimestamp)).ToList(); + var filteredActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().GetFilteredExternalActivities(allActivities.Cast().ToList())); + var mappedActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().HandleMapExternalActivities(filteredActivities.ToList(), Product.NewChecking, ActivityType.Sale)); + + if (mappedActivities.Count > 0) + { + foreach (var activity in mappedActivities) + { + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().HandleIncomingActivityDecision(activity, Product.NewChecking, ActivityType.Sale)); + } + } + + if (allActivities.Count < 1) + { + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().UpdateProcessorHistory(Process.ImportCheckingAccountSalesFromDna, lastUpdate.LastProcessedRecordId, lastUpdate.LastProcessedRecordTimestamp)); + return filteredActivities; + } + + var latestActivity = allActivities.Where(activity => activity.ActivityDateTime == allActivities.Select(ca => ca.ActivityDateTime).Max()).OrderByDescending(x => x.ActivityNbr).First(); + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().UpdateProcessorHistory(Process.ImportCheckingAccountSalesFromDna, latestActivity.ActivityNbr, latestActivity.ActivityDateTime)); + + return filteredActivities; + } + catch (ServiceException serviceException) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Error(serviceException, $"Import Checking Accounts Failure. " + + $"Failed to Process Latest Checking Accounts. Will try again from same starting position on next run."); + throw; + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Import Checking Accounts Failure. " + + $"Failed to Process Latest Checking Accounts. Will try again from same starting position on next run."); + throw; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/NewDigitalCheckingAccountActivityService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/NewDigitalCheckingAccountActivityService.cs new file mode 100644 index 0000000..19d7ff2 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/NewDigitalCheckingAccountActivityService.cs @@ -0,0 +1,93 @@ +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Results; +using LCU.API.Internal.Operations.IncentiveApi.Services.IncentiveProcesses; +using LCU.Library.Internal.LCUServiceFactory.Configurations; +using Microsoft.Extensions.DependencyInjection; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.DnaActivityServices; + +public class NewDigitalCheckingAccountActivityService : IncentiveProcessesService, IIncentiveProcessesService +{ + private readonly ICoreApiWrapperService _coreApiWrapperService; + private readonly IServiceScopeFactory _serviceScopeFactory; + private readonly ProcessResult _incentiveProcessResult; + + public NewDigitalCheckingAccountActivityService(ServicesFactoryConfiguration servicesFactoryConfiguration, + ICoreApiWrapperService coreApiWrapperService, IServiceScopeFactory serviceScopeFactory) + : base(servicesFactoryConfiguration) + { + _coreApiWrapperService = coreApiWrapperService; + _serviceScopeFactory = serviceScopeFactory; + _incentiveProcessResult = new ProcessResult(); + } + + public async Task SyncLatestActivitiesFromDna(IncentiveProcessRequest request) + { + try + { + var externalActivities = await ProcessLatestActivity(); + var message = $"Added {externalActivities.Count()} Digital Checking Accounts from DNA"; + Log.Information(message); + _incentiveProcessResult.MessageStack.Add(message); + } + catch (Exception exception) + { + var message = $"There was an issue with Digital Checking Accounts from DNA process. Exception Message: {exception.Message}."; + _incentiveProcessResult.MessageStack.Add(message); + _incentiveProcessResult.Success = false; + } + return _incentiveProcessResult; + } + + public async Task> ProcessLatestActivity() + { + try + { + using var scope = _serviceScopeFactory.CreateScope(); + var lastUpdate = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().GetLastActivityProcessedData(Process.ImportCheckingAccountSalesFromDigitalBanking) ?? throw new MissingProcessorRunHistoryException(Process.ImportCheckingAccountSalesFromDigitalBanking)); + var allActivities = (await _coreApiWrapperService.GetNewCheckingDigitalBankingAccountSalesActivity(lastUpdate.LastProcessedRecordTimestamp)).ToList(); + var filteredActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().GetFilteredExternalActivities(allActivities.Cast().ToList())); + var mappedActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().HandleMapExternalActivities(filteredActivities.ToList(), Product.NewChecking, ActivityType.Sale)); + + if (mappedActivities.Count > 0) + { + foreach (var activity in mappedActivities) + { + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().HandleIncomingActivityDecision(activity, Product.NewChecking, ActivityType.Sale)); + } + } + + if (allActivities.Count < 1) + { + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().UpdateProcessorHistory(Process.ImportCheckingAccountSalesFromDigitalBanking, lastUpdate.LastProcessedRecordId, lastUpdate.LastProcessedRecordTimestamp)); + return filteredActivities; + } + + var latestActivity = allActivities.Where(activity => activity.ActivityDateTime == allActivities.Select(ca => ca.ActivityDateTime).Max()).OrderByDescending(x => x.ActivityNbr).First(); + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().UpdateProcessorHistory(Process.ImportCheckingAccountSalesFromDigitalBanking, latestActivity.ActivityNbr, latestActivity.ActivityDateTime)); + + return filteredActivities; + } + + catch (ServiceException serviceException) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Error(serviceException, $"Import Digital Checking Accounts Failure. " + + $"Failed to Process Latest Digital Checking Accounts. Will try again from same starting position on next run."); + throw; + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Import Digital Checking Accounts Failure. " + + $"Failed to Process Latest Digital Checking Accounts. Will try again from same starting position on next run."); + throw; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/ReferralActivityService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/ReferralActivityService.cs new file mode 100644 index 0000000..7d162c6 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/ReferralActivityService.cs @@ -0,0 +1,93 @@ +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Results; +using LCU.API.Internal.Operations.IncentiveApi.Services.IncentiveProcesses; +using LCU.Library.Internal.LCUServiceFactory.Configurations; +using Microsoft.Extensions.DependencyInjection; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.DnaActivityServices; + +public class ReferralActivityService : IncentiveProcessesService, IIncentiveProcessesService +{ + private readonly ICoreApiWrapperService _coreApiWrapperService; + private readonly IServiceScopeFactory _serviceScopeFactory; + private readonly ProcessResult _incentiveProcessResult; + + public ReferralActivityService(ServicesFactoryConfiguration servicesFactoryConfiguration, + ICoreApiWrapperService coreApiWrapperService, IServiceScopeFactory serviceScopeFactory) + : base(servicesFactoryConfiguration) + { + _coreApiWrapperService = coreApiWrapperService; + _serviceScopeFactory = serviceScopeFactory; + _incentiveProcessResult = new ProcessResult(); + } + + public async Task SyncLatestActivitiesFromDna(IncentiveProcessRequest request) + { + try + { + var externalActivities = await ProcessLatestActivity(); + var message = $"Added {externalActivities.Count()} Referrals from DNA"; + Log.Information(message); + _incentiveProcessResult.MessageStack.Add(message); + } + catch (Exception exception) + { + var message = $"There was an issue with Referrals from DNA process. Exception Message: {exception.Message}."; + _incentiveProcessResult.MessageStack.Add(message); + _incentiveProcessResult.Success = false; + } + + return _incentiveProcessResult; + } + + public async Task> ProcessLatestActivity() + { + try + { + using var scope = _serviceScopeFactory.CreateScope(); + var lastUpdate = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().GetLastActivityProcessedData(Process.ImportActivityManagerReferralsFromDna) ?? throw new MissingProcessorRunHistoryException(Process.ImportActivityManagerReferralsFromDna)); + var allActivities = (await _coreApiWrapperService.GetNewActivityManagerReferralsActivity(lastUpdate.LastProcessedRecordTimestamp)).ToList(); + var filteredActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().GetFilteredExternalActivities(allActivities.Cast().ToList())); + var mappedActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().HandleMapExternalActivities(filteredActivities.ToList(), Product.Unknown, ActivityType.Referral)); + + if (mappedActivities.Count > 0) + { + foreach (var activity in mappedActivities) + { + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().HandleIncomingActivityDecision(activity, Product.Unknown, ActivityType.Referral)); + } + } + + if (allActivities.Count < 1) + { + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().UpdateProcessorHistory(Process.ImportActivityManagerReferralsFromDna, lastUpdate.LastProcessedRecordId, lastUpdate.LastProcessedRecordTimestamp)); + return filteredActivities; + } + + var latestActivity = allActivities.Where(activity => activity.OriginationDateTime == allActivities.Select(ca => ca.OriginationDateTime).Max()).OrderByDescending(x => x.ActivityId).First(); + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().UpdateProcessorHistory(Process.ImportActivityManagerReferralsFromDna, latestActivity.ActivityId, latestActivity.OriginationDateTime)); + + return filteredActivities; + } + catch (ServiceException serviceException) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Error(serviceException, $"Import Referrals Failure. " + + $"Failed to Process Referrals. Will try again from same starting position on next run."); + throw; + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Import Referrals Failure. " + + $"Failed to Process Referrals. Will try again from same starting position on next run."); + throw; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/VehicleSalesActivityService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/VehicleSalesActivityService.cs new file mode 100644 index 0000000..5b987f7 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivityServices/VehicleSalesActivityService.cs @@ -0,0 +1,93 @@ +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.Exceptions; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.IncentiveApiModels.Results; +using LCU.API.Internal.Operations.IncentiveApi.Services.IncentiveProcesses; +using LCU.Library.Internal.LCUServiceFactory.Configurations; +using Microsoft.Extensions.DependencyInjection; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.DnaActivityServices; + +public class VehicleSalesActivityService : IncentiveProcessesService, IIncentiveProcessesService +{ + private readonly ICoreApiWrapperService _coreApiWrapperService; + private readonly IServiceScopeFactory _serviceScopeFactory; + private readonly ProcessResult _incentiveProcessResult; + + public VehicleSalesActivityService(ServicesFactoryConfiguration servicesFactoryConfiguration, + ICoreApiWrapperService coreApiWrapperService, IServiceScopeFactory serviceScopeFactory) + : base(servicesFactoryConfiguration) + { + _coreApiWrapperService = coreApiWrapperService; + _serviceScopeFactory = serviceScopeFactory; + _incentiveProcessResult = new ProcessResult(); + } + + public async Task SyncLatestActivitiesFromDna(IncentiveProcessRequest request) + { + try + { + var externalActivities = await ProcessLatestActivity(); + var message = $"Added {externalActivities.Count()} Vehicle Accounts from DNA"; + Log.Information(message); + _incentiveProcessResult.MessageStack.Add(message); + } + catch(Exception exception) + { + var message = $"There was an issue with Vehicle Accounts from DNA process. Exception Message: {exception.Message}."; + _incentiveProcessResult.MessageStack.Add(message); + _incentiveProcessResult.Success = false; + } + + return _incentiveProcessResult; + } + + public async Task> ProcessLatestActivity() + { + try + { + using var scope = _serviceScopeFactory.CreateScope(); + var lastUpdate = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().GetLastActivityProcessedData(Process.ImportVehicleSalesFromDna) ?? throw new MissingProcessorRunHistoryException(Process.ImportVehicleSalesFromDna)); + var allActivities = (await _coreApiWrapperService.GetNewVehicleSaleActivities(lastUpdate.LastProcessedRecordTimestamp)).ToList(); + var filteredActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().GetFilteredExternalActivities(allActivities.Cast().ToList())); + var mappedActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().HandleMapExternalActivities(filteredActivities.ToList(), Product.Vehicle, ActivityType.Sale)); + + if (mappedActivities.Count > 0) + { + foreach (var activity in mappedActivities) + { + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().HandleIncomingActivityDecision(activity, Product.Vehicle, ActivityType.Sale)); + } + } + + if (allActivities.Count < 1) + { + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().UpdateProcessorHistory(Process.ImportVehicleSalesFromDna, lastUpdate.LastProcessedRecordId, lastUpdate.LastProcessedRecordTimestamp)); + return filteredActivities; + } + + var latestActivity = allActivities.Where(activity => activity.ActivityDateTime == allActivities.Select(ca => ca.ActivityDateTime).Max()).OrderByDescending(x => x.ActivityNbr).First(); + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().UpdateProcessorHistory(Process.ImportVehicleSalesFromDna, latestActivity.ActivityNbr, latestActivity.ActivityDateTime)); + + return filteredActivities; + } + catch (ServiceException serviceException) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Error(serviceException, $"Import Dna Vehicle Sales Failure. " + + $"Failed to Process Latest Dna Vehicle Sales. Will try again from same starting position on next run."); + throw; + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Import Dna Vehicle Sales Failure. " + + $"Failed to Process Latest Dna Vehicle Sales. Will try again from same starting position on next run."); + throw; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivitySyncService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivitySyncService.cs new file mode 100644 index 0000000..6da40f3 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaActivitySyncService.cs @@ -0,0 +1,52 @@ +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Results; +using LCU.Library.Internal.LCUServiceFactory.Services; +using LCU.API.Internal.Operations.IncentiveApi.Services.IncentiveProcesses; +using LCU.Library.Internal.ApiResponse.Services; +using LCU.Library.Internal.Exceptions; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public class DnaActivitySyncService : IDnaActivitySyncService +{ + private readonly ILcuServiceFactory _lcuServiceFactory; + private IIncentiveProcessesService _incentiveProcessesService; + private ProcessResult _incentiveProcessResult; + + public DnaActivitySyncService(ILcuServiceFactory lcuServiceFactory) + { + _lcuServiceFactory = lcuServiceFactory; + _incentiveProcessesService = null; + _incentiveProcessResult = new ProcessResult(); + } + + public async Task HandleProcessRequest(IncentiveProcessRequest request) + { + try + { + ArgumentNullException.ThrowIfNull(request); + _incentiveProcessesService = _lcuServiceFactory.GetService(request.IncentiveProcessType); + ArgumentNullException.ThrowIfNull(_incentiveProcessesService); + + var message = $"Queued Request for {request.IncentiveProcessType}"; + Log.Information(message); + + _incentiveProcessResult = await _incentiveProcessesService.SyncLatestActivitiesFromDna(request); + await QueueReturnResponse.QueueResponse(request, _incentiveProcessResult.Success, _incentiveProcessResult.MessageStack); + } + catch (Exception exception) + { + var message = $"There was an issue with {request.IncentiveProcessType}. Exception Message: {exception.Message}."; + _incentiveProcessResult.MessageStack.Add(message); + _incentiveProcessResult.Success = false; + await QueueReturnResponse.QueueResponse(request, _incentiveProcessResult.Success, _incentiveProcessResult.MessageStack); + throw new ServiceException(message); + } + } + + [Obsolete("01/01/2025 - Removed and located in each activity type service.")] + public async Task SyncLatestActivitiesFromDna() + { + + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaService.cs new file mode 100644 index 0000000..1a4c5cb --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DnaService.cs @@ -0,0 +1,42 @@ +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Accounts; +using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Activity; +using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Transaction; +using Activity = LCU.Library.Internal.IncentiveApiModels.Entities.Activity; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public class DnaService : IDnaService +{ + private readonly ICoreApiWrapperService _coreApiWrapperService; + + public DnaService(ICoreApiWrapperService coreApiWrapperService) + { + _coreApiWrapperService = coreApiWrapperService; + } + + public async Task> GetNewAccountDetailsForActivities(List activities) + { + var accountNumbers = activities.Select(x => x.Detail.AccountNumber) + .Where(x => !string.IsNullOrEmpty(x) && x.All(char.IsDigit)) + .Select(long.Parse) + .ToList(); + return await _coreApiWrapperService.GetUpdatedAccountInformation(accountNumbers) as List; + } + + public async Task> QueryActivityManagerForActivities(List activityManagerIds) + { + var sourceIds = activityManagerIds + .Where(x => !string.IsNullOrEmpty(x) && x.All(char.IsDigit)) + .Select(long.Parse) + .ToList(); + return await _coreApiWrapperService.GetActivityManagerActivities(new ActivityQueryRequest(){ActivityIds = sourceIds}) as List; + } + + public async Task> GetTransactionHistory(string accountNumber, DateTime startDate, DateTime endDate, List transactionCodes) + { + var res = await _coreApiWrapperService.GetTransactionHistory(accountNumber, startDate, endDate, transactionCodes); + return res?.ToList(); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DuplicateDetection/DefaultDuplicateDetectionStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DuplicateDetection/DefaultDuplicateDetectionStrategy.cs new file mode 100644 index 0000000..389d74b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DuplicateDetection/DefaultDuplicateDetectionStrategy.cs @@ -0,0 +1,33 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.DuplicateDetection; + +public class DefaultDuplicateDetectionStrategy : IDuplicateDetectionStrategy +{ + + private readonly IActivityRepository _activityRepository; + + public DefaultDuplicateDetectionStrategy(IActivityRepository activityRepository) + { + _activityRepository = activityRepository; + } + + public bool CanDecideActivity(Activity activity) + { + return activity.Type == ActivityType.Sale || activity.Type == ActivityType.Other; + } + + public async Task> FindDuplicates(Activity activity) + { + var duplicates = new List(); + var duplicate = await _activityRepository.FindDuplicateSaleAsync(activity); + if (duplicate != null) + { + duplicates.Add(duplicate); + } + + return duplicates; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DuplicateDetection/DuplicateDetectionService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DuplicateDetection/DuplicateDetectionService.cs new file mode 100644 index 0000000..06b16e0 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DuplicateDetection/DuplicateDetectionService.cs @@ -0,0 +1,28 @@ +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.DuplicateDetection; + +public class DuplicateDetectionService : IDuplicateDetectionService +{ + private readonly IEnumerable _strategies; + + public DuplicateDetectionService(IEnumerable strategies, IActivityRepository activityRepository) + { + _strategies = strategies; + } + + public async Task> TryFindDuplicates(Activity activity) + { + var strategy = _strategies.FirstOrDefault(x => x.CanDecideActivity(activity)); + + if (strategy != null) + { + return await strategy.FindDuplicates(activity); + } + + throw new NoAvailableStrategy( + $"No Duplicate Detection Strategy available, {activity.Id} - {activity.Product}:{activity.Type}"); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DuplicateDetection/IDuplicateDetectionService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DuplicateDetection/IDuplicateDetectionService.cs new file mode 100644 index 0000000..e406a64 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DuplicateDetection/IDuplicateDetectionService.cs @@ -0,0 +1,8 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.DuplicateDetection; + +public interface IDuplicateDetectionService +{ + Task> TryFindDuplicates(Activity activity); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DuplicateDetection/IDuplicateDetectionStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DuplicateDetection/IDuplicateDetectionStrategy.cs new file mode 100644 index 0000000..c97da89 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DuplicateDetection/IDuplicateDetectionStrategy.cs @@ -0,0 +1,9 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.DuplicateDetection; + +public interface IDuplicateDetectionStrategy +{ + public bool CanDecideActivity(Activity activity); + Task> FindDuplicates(Activity activity); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DuplicateDetection/ReferralDuplicateDetectionStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DuplicateDetection/ReferralDuplicateDetectionStrategy.cs new file mode 100644 index 0000000..1ecab1b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DuplicateDetection/ReferralDuplicateDetectionStrategy.cs @@ -0,0 +1,33 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.DuplicateDetection; + +public class ReferralDuplicateDetectionStrategy : IDuplicateDetectionStrategy +{ + + private readonly IActivityRepository _activityRepository; + + public ReferralDuplicateDetectionStrategy(IActivityRepository activityRepository) + { + _activityRepository = activityRepository; + } + + public bool CanDecideActivity(Activity activity) + { + return activity.Type == ActivityType.Referral && activity.SourceSystem != SystemType.Action; + } + + public async Task> FindDuplicates(Activity activity) + { + var duplicates = new List(); + var duplicate = await _activityRepository.FindDuplicateReferralAsync(activity); + if (duplicate != null) + { + duplicates.Add(duplicate); + } + + return duplicates; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DuplicateDetection/SourceIdDuplicateDetectionStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DuplicateDetection/SourceIdDuplicateDetectionStrategy.cs new file mode 100644 index 0000000..6983b2f --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/DuplicateDetection/SourceIdDuplicateDetectionStrategy.cs @@ -0,0 +1,32 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.DuplicateDetection; + +public class SourceIdDuplicateDetectionStrategy : IDuplicateDetectionStrategy +{ + private readonly IActivityRepository _activityRepository; + + public SourceIdDuplicateDetectionStrategy(IActivityRepository activityRepository) + { + _activityRepository = activityRepository; + } + + public bool CanDecideActivity(Activity activity) + { + return activity.Type == ActivityType.Referral && activity.SourceSystem == SystemType.Action; + } + + public async Task> FindDuplicates(Activity activity) + { + var duplicates = new List(); + var duplicate = await _activityRepository.FindDuplicateReferralBySourceIdAsync(activity); + if (duplicate != null) + { + duplicates.Add(duplicate); + } + + return duplicates; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/EStatementService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/EStatementService.cs new file mode 100644 index 0000000..0fb908c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/EStatementService.cs @@ -0,0 +1,127 @@ +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public class EStatementService : IEStatementService +{ + private readonly IProcessorHistoryService _processorHistoryService; + private readonly ICoreApiWrapperService _coreApiWrapperService; + private readonly IActivityService _activityService; + private readonly IActivityRepository _activityRepository; + + public EStatementService( + IProcessorHistoryService processorHistoryService, + ICoreApiWrapperService coreApiWrapperService, + IActivityService activityService, + IActivityRepository activityRepository + ) + { + _processorHistoryService = processorHistoryService; + _coreApiWrapperService = coreApiWrapperService; + _activityService = activityService; + _activityRepository = activityRepository; + } + + public async Task> UpdateOrExpireEStatementReferral() + { + var deliveryMethods = await GetEStatementDeliveryMethods(); + var eStatementReferrals = await _activityRepository.FindPendingEStatementsReferrals(); + var changes = new List(); + var matchedDeliveryMethods = new List(); + + foreach (var referral in eStatementReferrals) + { + var matchedDeliveryMethod = _activityRepository.FindDeliveryMethodForEStatement(referral, deliveryMethods); + + if (matchedDeliveryMethod != null) + { + var change = new ActivityStateChange + { + ActivityId = referral.Id, + State = ActivityState.Accepted, + Note = $"Delivery method is www for account number {matchedDeliveryMethod.AccountNumber}" + }; + + await _activityService.ApplyStateChange(change); + changes.Add(change); + matchedDeliveryMethods.Add(matchedDeliveryMethod); + await UpdateReferralProperties(referral, matchedDeliveryMethod); + + continue; + } + + var expireRange = DateTime.Today.Subtract(TimeSpan.FromDays(60)); + if (referral.SourceCreated < expireRange) + { + var change = new ActivityStateChange + { + ActivityId = referral.Id, + State = ActivityState.Rejected, + Note = "Referral has expired." + }; + await _activityService.ApplyStateChange(change); + changes.Add(change); + } + } + + if (matchedDeliveryMethods.Count > 0) + { + await UpdateProcessorHistory(matchedDeliveryMethods); + } + + return changes; + } + + private async Task UpdateProcessorHistory(List matchedDeliveryMethods) + { + await _processorHistoryService.SaveProcessorHistory(new ProcessorHistoryRecord() + { + Process = Process.GetDeliveryMethodsFromDna, + LastSuccessfulRunTime = DateTime.UtcNow, + LastProcessedRecordId = matchedDeliveryMethods.OrderByDescending(x => x.ActivityNumber).First().ActivityNumber, + LastProcessedRecordTimestamp = matchedDeliveryMethods.OrderByDescending(x => x.ActivityDateTime).First().ActivityDateTime + }); + } + + private async Task UpdateReferralProperties(Activity referral, EStatementDeliveryMethod matchedDeliveryMethod) + { + referral.MetaData = matchedDeliveryMethod.MetaData.UnionBy(referral.MetaData, x => x.MetaData).ToList(); + + referral.Detail.AccountNumber = string.IsNullOrEmpty(referral.Detail.AccountNumber) + ? matchedDeliveryMethod.AccountNumber.ToString() : referral.Detail.AccountNumber; + + _activityRepository.UpdateActivityRangeAsync(new List() { referral }); + } + + private async Task> GetEStatementDeliveryMethods() + { + var lastRun = await _processorHistoryService.GetProcessorHistory(Process.GetDeliveryMethodsFromDna); + var listDeliveryMethods = new List(); + + if (lastRun == null) + { + throw new MissingProcessorRunHistoryException(Process.GetDeliveryMethodsFromDna); + } + + var deliveryMethods = await _coreApiWrapperService.GetEStatmentDeliveryMethods(lastRun.LastProcessedRecordTimestamp); + foreach ( var deliveryMethod in deliveryMethods ) + { + if ( deliveryMethod != null ) + { + deliveryMethod.MetaData = SetMetaData(deliveryMethod.PhoneNumbers, deliveryMethod.EmailAddresses); + listDeliveryMethods.Add( deliveryMethod ); + } + } + return listDeliveryMethods; + } + + public List SetMetaData(string phoneNumbers, string emailAddresses) + { + return DataParsingService.MapActivityMetaData(new List { phoneNumbers, emailAddresses }); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/ExternalActivityService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/ExternalActivityService.cs new file mode 100644 index 0000000..6429ed1 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/ExternalActivityService.cs @@ -0,0 +1,28 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity; + +public class ExternalActivityService : IExternalActivityService +{ + private readonly IEnumerable _strategies; + + public ExternalActivityService(IEnumerable strategies) + { + _strategies = strategies; + } + + public async Task TryMapExternalActivity(IExternalActivity externalActivity) + { + var strategy = _strategies.FirstOrDefault(x => x.CanMapActivity(externalActivity)); + + if (strategy != null) + { + return await strategy.MapToActivityManagerActivity(externalActivity); + } + + Log.Error($"Failed to find strategy for External Activity for product {externalActivity.GetProductType()}. Falling back to basic method. Some data may be missing."); + var activity = externalActivity.MapToActivity(); + return activity; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/IExternalActivityService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/IExternalActivityService.cs new file mode 100644 index 0000000..d294a86 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/IExternalActivityService.cs @@ -0,0 +1,9 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity; + +public interface IExternalActivityService +{ + public Task TryMapExternalActivity(IExternalActivity externalActivity); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/IExternalActivityStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/IExternalActivityStrategy.cs new file mode 100644 index 0000000..9ed1c30 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/IExternalActivityStrategy.cs @@ -0,0 +1,11 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity; + +public interface IExternalActivityStrategy +{ + public bool CanMapActivity(IExternalActivity activity); + public Task MapToActivityManagerActivity(IExternalActivity activity); + +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/NewActivityManagerActivityStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/NewActivityManagerActivityStrategy.cs new file mode 100644 index 0000000..e2b696f --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/NewActivityManagerActivityStrategy.cs @@ -0,0 +1,74 @@ +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity; + +public class NewIncentiveManagerActivityStrategy : IExternalActivityStrategy +{ + private readonly IUserService _userService; + private readonly IDepartmentRepository _departmentRepository; + + public NewIncentiveManagerActivityStrategy(IUserService userService, IDepartmentRepository departmentRepository) + { + _userService = userService; + _departmentRepository = departmentRepository; + } + + public bool CanMapActivity(IExternalActivity activity) + { + return activity.GetActivityType() == ActivityType.Referral && + (activity.GetProductType() == Product.ATMCard || + activity.GetProductType() == Product.CheckCard || + activity.GetProductType() == Product.NewChecking || + activity.GetProductType() == Product.Mortgage || + activity.GetProductType() == Product.DirectDeposit || + activity.GetProductType() == Product.CreditCardBalanceTransfer || + activity.GetProductType() == Product.LIC || + activity.GetProductType() == Product.FirstMortgageReferral || + activity.GetProductType() == Product.eStatement); + } + + public async Task MapToActivityManagerActivity(IExternalActivity activity) + { + AppUser appUser = null; + Department department = null; + try + { + if (activity is NewActivityManagerReferral newActivityManagerReferral) + { + if (string.IsNullOrWhiteSpace(newActivityManagerReferral.EmployeeEmail)) + { + appUser = await _userService.TryFindSingleUserByName(new UserNameSearchRequest() + { + FirstName = newActivityManagerReferral.OriginatorFirstName, + MiddleInit = newActivityManagerReferral.OriginatorMiddleInitial, + LastName = newActivityManagerReferral.OriginatorLastName + }); + } + else + { + appUser = await _userService.GetUserByAdEmailAsync(newActivityManagerReferral.EmployeeEmail); + } + + department = + await _departmentRepository.FindDepartmentByBranchName(newActivityManagerReferral + .OriginatingBranchName); + } + } + catch (Exception ex) + { + Log.Error(ex, + $"Failed to get data for NewIncentiveManagerActivity with sourceId: {activity.MapToActivity().SourceId}. Falling back to basic mapping."); + } + + var mappedActivity = activity.MapToActivity(); + mappedActivity.AppUserId = appUser?.Id ?? Guid.Empty; + mappedActivity.EmployeeId = appUser?.EmployeeId; + mappedActivity.DepartmentId = department?.Id ?? -1; + return mappedActivity; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/NewAtmCheckCardActivityStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/NewAtmCheckCardActivityStrategy.cs new file mode 100644 index 0000000..f696ada --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/NewAtmCheckCardActivityStrategy.cs @@ -0,0 +1,69 @@ +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity; + +public class NewAtmCheckCardActivityStrategy : IExternalActivityStrategy +{ + private readonly IUserService _userService; + private readonly IDepartmentRepository _departmentRepository; + + public NewAtmCheckCardActivityStrategy(IUserService userService, IDepartmentRepository departmentRepository) + { + _userService = userService; + _departmentRepository = departmentRepository; + } + + public bool CanMapActivity(IExternalActivity activity) + { + return activity.GetActivityType() == ActivityType.Other && (activity.GetProductType() == Product.ATMCard || + activity.GetProductType() == Product.CheckCard); + } + + public async Task MapToActivityManagerActivity(IExternalActivity activity) + { + AppUser appUser = null; + Department department = null; + try + { + if (activity is NewAtmCheckCardActivity atmCheckCardActivity) + { + if (string.IsNullOrWhiteSpace(atmCheckCardActivity.EmployeeEmail)) + { + appUser = await _userService.TryFindSingleUserByName(new UserNameSearchRequest() + { + FirstName = atmCheckCardActivity.AssociateFirst, + MiddleInit = atmCheckCardActivity.AssociateMiddleInit, + LastName = atmCheckCardActivity.AssociateLast + }); + } + else + { + appUser = await _userService.GetUserByAdEmailAsync(atmCheckCardActivity.EmployeeEmail); + } + } + + if (appUser == null) + { + return activity.MapToActivity(); + } + + department = await _departmentRepository.FindUserHomeDepartment(appUser); + } + catch (Exception ex) + { + Log.Error(ex, + $"Failed to get data for NewAtmCheckCardActivity with sourceId: {activity.MapToActivity().SourceId}. Falling back to basic mapping."); + } + + var mappedActivity = activity.MapToActivity(); + mappedActivity.AppUserId = appUser?.Id ?? Guid.Empty; + mappedActivity.EmployeeId = appUser?.EmployeeId; + mappedActivity.DepartmentId = department?.Id ?? -1; + return mappedActivity; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/NewCheckingAccountActivityStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/NewCheckingAccountActivityStrategy.cs new file mode 100644 index 0000000..03011a2 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/NewCheckingAccountActivityStrategy.cs @@ -0,0 +1,72 @@ +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity; + +public class NewCheckingAccountActivityStrategy : IExternalActivityStrategy +{ + private readonly IUserService _userService; + private readonly IDepartmentRepository _departmentRepository; + + public NewCheckingAccountActivityStrategy(IUserService userService, IDepartmentRepository departmentRepository) + { + _userService = userService; + _departmentRepository = departmentRepository; + } + + public bool CanMapActivity(IExternalActivity activity) + { + return activity.GetActivityType() == ActivityType.Sale && + activity.GetProductType() == Product.NewChecking; + } + + public async Task MapToActivityManagerActivity(IExternalActivity activity) + { + AppUser appUser = null; + Department department = null; + try + { + if (activity is NewCheckingAccountActivity newCheckingActivity) + { + if (string.IsNullOrWhiteSpace(newCheckingActivity.EmployeeEmail)) + { + appUser = await _userService.TryFindSingleUserByName(new UserNameSearchRequest() + { + FirstName = newCheckingActivity.AssociateFirst, + LastName = newCheckingActivity.AssociateLast, + MiddleInit = newCheckingActivity.AssociateMiddleInit, + DnaOrgNbr = newCheckingActivity.BranchOrgNbr.ToString() + + }); + } + else + { + appUser = await _userService.GetUserByAdEmailAsync(newCheckingActivity.EmployeeEmail); + } + + if (appUser == null) + { + return activity.MapToActivity(); + } + + department = await _departmentRepository.FindUserHomeDepartment(appUser); + } + } + catch (Exception ex) + { + Log.Error(ex, + $"Failed to get data for NewCheckingAccountActivity with sourceId: {activity.GetSourceId()}. Falling back to basic mapping."); + } + + var mappedActivity = activity.MapToActivity(); + mappedActivity.AppUserId = appUser?.Id ?? Guid.Empty; + mappedActivity.EmployeeId = appUser?.EmployeeId; + mappedActivity.DepartmentId = department?.Id ?? -1; + return mappedActivity; + } + +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/NewDebtProtectionEligibleLoanStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/NewDebtProtectionEligibleLoanStrategy.cs new file mode 100644 index 0000000..96fb7e3 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/NewDebtProtectionEligibleLoanStrategy.cs @@ -0,0 +1,97 @@ +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity; + +public class NewDebtProtectionEligibleLoanStrategy : IExternalActivityStrategy +{ + private readonly IUserService _userService; + private readonly IDepartmentRepository _departmentRepository; + + public NewDebtProtectionEligibleLoanStrategy(IUserService userService, IDepartmentRepository departmentRepository) + { + _userService = userService; + _departmentRepository = departmentRepository; + } + + public bool CanMapActivity(IExternalActivity activity) + { + return activity.GetActivityType() == ActivityType.Sale && activity.GetProductType() == Product.DebtProtectionEligibleLoan; + } + + public async Task MapToActivityManagerActivity(IExternalActivity activity) + { + AppUser appUser = null; + Department department = null; + try + { + if (activity is NewLoanSaleActivity newDpEligibleLoanSale) + { + if (string.IsNullOrWhiteSpace(newDpEligibleLoanSale.EmployeeEmail)) + { + appUser = await _userService.TryFindSingleUserByName(new UserNameSearchRequest() + { + FirstName = newDpEligibleLoanSale.AssociateFirst, + LastName = newDpEligibleLoanSale.AssociateLast, + MiddleInit = newDpEligibleLoanSale.AssociateMiddleInit, + DnaOrgNbr = newDpEligibleLoanSale.BranchOrgNbr.ToString() + + }); + } + else + { + appUser = await _userService.GetUserByAdEmailAsync(newDpEligibleLoanSale.EmployeeEmail); + } + + if (appUser == null) + { + return activity.MapToActivity(); + } + + department = await _departmentRepository.FindUserHomeDepartment(appUser); + } + } + catch (Exception ex) + { + Log.Error(ex, + $"Failed to get data for NewDPEligibleLoanSaleActivity with sourceId: {activity.MapToActivity().SourceId}. Falling back to basic mapping."); + } + + var mappedActivity = activity.MapToActivity(); + mappedActivity.AppUserId = appUser?.Id ?? Guid.Empty; + mappedActivity.EmployeeId = appUser?.EmployeeId; + mappedActivity.DepartmentId = department?.Id ?? -1; + return mappedActivity; + } + + public async Task SearchForUser(UserSearchRequest userSearchRequest) + { + var foundUsers = await _userService.GetAllUsersAsync(userSearchRequest); + + if (foundUsers.Count < 1 && !string.IsNullOrWhiteSpace(userSearchRequest.MiddleInit)) + { + foundUsers = await _userService.GetAllUsersAsync(new UserSearchRequest() + { + FirstName = userSearchRequest.FirstName, + LastName = userSearchRequest.LastName, + DnaOrgNbr = userSearchRequest.DnaOrgNbr + }); + } + + if (foundUsers.Count < 1) + { + foundUsers = await _userService.GetAllUsersAsync(new UserSearchRequest() + { + FirstName = userSearchRequest.FirstName, + LastName = userSearchRequest.LastName, + }); + } + + return foundUsers.Single(); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/NewMortgageActivityStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/NewMortgageActivityStrategy.cs new file mode 100644 index 0000000..25a75af --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/NewMortgageActivityStrategy.cs @@ -0,0 +1,114 @@ +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.API.Internal.Operations.IncentiveApi.Models.Constants; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity; + +public class NewMortgageActivityStrategy : IExternalActivityStrategy +{ + private readonly IUserService _userService; + private readonly IDepartmentRepository _departmentRepository; + + public NewMortgageActivityStrategy(IUserService userService, IDepartmentRepository departmentRepository) + { + _userService = userService; + _departmentRepository = departmentRepository; + } + + public bool CanMapActivity(IExternalActivity activity) + { + return + activity.GetActivityType() == ActivityType.Sale + && (activity.GetProductType() == Product.Mortgage || activity.GetProductType() == Product.FirstMortgageReferral); + } + + public async Task MapToActivityManagerActivity(IExternalActivity activity) + { + AppUser appUser = null; + Department department = null; + + try + { + if (activity is NewLoanSaleActivity newMortgageActivity) + { + if (IsFirstMortgageSale(newMortgageActivity)) + { + appUser = await _userService.GetUserByAdEmailAsync(SystemAppUserConstants.FirstMortgageEmail); + } + else if (string.IsNullOrWhiteSpace(newMortgageActivity.EmployeeEmail)) + { + appUser = await _userService.TryFindSingleUserByName(new UserNameSearchRequest() + { + FirstName = newMortgageActivity.AssociateFirst, + LastName = newMortgageActivity.AssociateLast, + MiddleInit = newMortgageActivity.AssociateMiddleInit, + DnaOrgNbr = newMortgageActivity.BranchOrgNbr.ToString() + }); + } + else + { + appUser = await _userService.GetUserByAdEmailAsync(newMortgageActivity.EmployeeEmail); + } + + if (appUser == null) + { + return activity.MapToActivity(); + } + + department = await _departmentRepository.FindUserHomeDepartment(appUser); + } + } + catch (Exception ex) + { + Log.Error(ex, + $"Failed to get data for NewMortgageSaleActivity with sourceId: {activity.MapToActivity().SourceId}. Falling back to basic mapping."); + } + + var mappedActivity = activity.MapToActivity(); + mappedActivity.AppUserId = appUser?.Id ?? Guid.Empty; + mappedActivity.EmployeeId = appUser?.EmployeeId; + mappedActivity.DepartmentId = department?.Id ?? -1; + return mappedActivity; + } + + private bool IsFirstMortgageSale(NewLoanSaleActivity newMortgageActivity) + { + if (newMortgageActivity.GetProductType() == Product.FirstMortgageReferral + && newMortgageActivity.GetActivityType() == ActivityType.Sale) + { + return true; + } + + return false; + } + + public async Task SearchForUser(UserSearchRequest userSearchRequest) + { + var foundUsers = await _userService.GetAllUsersAsync(userSearchRequest); + + if (foundUsers.Count < 1 && !string.IsNullOrWhiteSpace(userSearchRequest.MiddleInit)) + { + foundUsers = await _userService.GetAllUsersAsync(new UserSearchRequest() + { + FirstName = userSearchRequest.FirstName, + LastName = userSearchRequest.LastName, + DnaOrgNbr = userSearchRequest.DnaOrgNbr + }); + } + + if (foundUsers.Count < 1) + { + foundUsers = await _userService.GetAllUsersAsync(new UserSearchRequest() + { + FirstName = userSearchRequest.FirstName, + LastName = userSearchRequest.LastName, + }); + } + return foundUsers.Single(); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/NewVehicleActivityStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/NewVehicleActivityStrategy.cs new file mode 100644 index 0000000..8a34c48 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ExternalActivity/NewVehicleActivityStrategy.cs @@ -0,0 +1,77 @@ +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity; + +public class NewVehicleActivityStrategy : IExternalActivityStrategy +{ + private readonly IUserService _userService; + private readonly IDepartmentRepository _departmentRepository; + + public NewVehicleActivityStrategy(IUserService userService, IDepartmentRepository departmentRepository) + { + _userService = userService; + _departmentRepository = departmentRepository; + } + + public bool CanMapActivity(IExternalActivity activity) + { + return activity.GetActivityType() == ActivityType.Sale && activity.GetProductType() == Product.Vehicle; + } + + public async Task MapToActivityManagerActivity(IExternalActivity activity) + { + AppUser appUser = null; + Department department = null; + try + { + if (activity is NewLoanSaleActivity newVehicleActivity) + { + if (string.IsNullOrWhiteSpace(newVehicleActivity.EmployeeEmail)) + { + try + { + appUser = await _userService.TryFindSingleUserByName(new UserNameSearchRequest() + { + FirstName = newVehicleActivity.AssociateFirst, + LastName = newVehicleActivity.AssociateLast, + MiddleInit = newVehicleActivity.AssociateMiddleInit, + DnaOrgNbr = newVehicleActivity.BranchOrgNbr.ToString() + }); + } + catch (InvalidOperationException ioe) + { + Log.Error(ioe, + $"More than one user found for name query {newVehicleActivity.AssociateFirst}, {newVehicleActivity.AssociateLast}, will try employee email."); + } + } + else + { + appUser = await _userService.GetUserByAdEmailAsync(newVehicleActivity.EmployeeEmail); + } + + if (appUser == null) + { + return activity.MapToActivity(); + } + + department = await _departmentRepository.FindUserHomeDepartment(appUser); + } + } + catch (Exception ex) + { + Log.Error(ex, + $"Failed to get data for NewVehicleActivityStrategy with sourceId: {activity.MapToActivity().SourceId}. Falling back to basic mapping."); + } + + var mappedActivity = activity.MapToActivity(); + mappedActivity.AppUserId = appUser?.Id ?? Guid.Empty; + mappedActivity.EmployeeId = appUser?.EmployeeId; + mappedActivity.DepartmentId = department?.Id ?? -1; + return mappedActivity; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/GapInsuranceActivityService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/GapInsuranceActivityService.cs new file mode 100644 index 0000000..29931ce --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/GapInsuranceActivityService.cs @@ -0,0 +1,243 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public class GapInsuranceActivityService : IGapInsuranceActivityService +{ + private readonly IActivityDetailService _activityDetailService; + private readonly IActivityRepository _activityRepository; + private readonly IActivityService _activityService; + private readonly IUnitOfWork _unitOfWork; + + public GapInsuranceActivityService( + IActivityDetailService activityDetailService, + IActivityRepository activityRepository, + IActivityService activityService, + IUnitOfWork unitOfWork + ) + { + _activityDetailService = activityDetailService; + _activityRepository = activityRepository; + _activityService = activityService; + _unitOfWork = unitOfWork; + } + + public async Task MatchOrRemoveGapAccessories(MonthRange monthRange = null) + { + var updateCount = 0; + monthRange ??= new MonthRange(DateTime.UtcNow.AddMonths(-3), DateTime.UtcNow); + var gapEligibleActivities = await _activityRepository.FindGapInsuranceEligible(monthRange); + + if (!gapEligibleActivities.Any()) + { + return updateCount; + } + + var matchedGapEligibleActivities = gapEligibleActivities.Where(x => x.HasActiveGapAccessory()).ToList(); + var matchedGapAccessoryActivities = await GetMatchingGapAccessoryActivities(matchedGapEligibleActivities); + matchedGapEligibleActivities = await ValidateMatchedActivityAccessories(matchedGapEligibleActivities, matchedGapAccessoryActivities); + + var unmatchedGapEligibleActivities = gapEligibleActivities.Except(matchedGapEligibleActivities).ToList(); + var gapActivities = await _activityRepository.GetFilteredActivities(monthRange, Product.GAPInsurance, ActivityState.Accepted); + + foreach (var activity in unmatchedGapEligibleActivities) + { + try + { + updateCount += await TryMatchGapEligibleActivityWithAccessory(activity, gapActivities) != null ? 2 : 0; + } + catch (Exception error) + { + Log.Error(error, $"MatchOrRemoveGapAccessories failed processing Activity Id: {activity.Id}"); + } + } + + return updateCount; + } + + private async Task TryMatchGapEligibleActivityWithAccessory(Activity gapEligibleActivity, List gapActivities) + { + if (gapActivities.Count <= 0) + { + return null; + } + + var matchingGapAccessories = gapActivities + .Where(x => x.Detail.AccountNumber.Equals(gapEligibleActivity.Detail.AccountNumber, StringComparison.CurrentCultureIgnoreCase)) + .ToList(); + + if (matchingGapAccessories.Count <= 0) + { + return null; + } + + if (matchingGapAccessories.Count > 1) + { + throw new Exception( + $"Multiple matching GAP Accessory Activities found for Activity Id: {gapEligibleActivity.Id} using AccountNumber: {gapEligibleActivity.Detail.AccountNumber}"); + } + + var matchedAccessoryActivity = matchingGapAccessories.First(); + return await MatchGapAccessory(gapEligibleActivity, matchedAccessoryActivity); + } + + private async Task MatchGapAccessory(Activity parentActivity, Activity accessoryActivity) + { + var addedDateTime = accessoryActivity.Accepted.Date.AddHours(12); + + await ApplyMatchedChangeUpdates(parentActivity, accessoryActivity, addedDateTime); + await ApplyMatchedChangeUpdates(accessoryActivity, parentActivity, addedDateTime); + await _unitOfWork.CompleteAsync(); + + return parentActivity; + } + + private async Task ApplyMatchedChangeUpdates(Activity targetActivity, Activity linkedActivity, DateTime addedDateTime) + { + targetActivity.Detail.GapInsuranceAddDate = addedDateTime; + targetActivity.MatchingGapInsuranceActivityId = linkedActivity.Id; + await _activityDetailService.SaveActivityDetailUpdate(new ActivityDetailUpdate + { + ActivityDetailId = targetActivity.Detail.Id, + Note = $"GapInsuranceAddDate has changed from NULL to {addedDateTime}" + }); + + if (targetActivity.Detail.GapInsuranceRemoveDate != null) + { + targetActivity.Detail.GapInsuranceRemoveDate = null; + await _activityDetailService.SaveActivityDetailUpdate(new ActivityDetailUpdate + { + ActivityDetailId = targetActivity.Detail.Id, + Note = $"GapInsuranceRemoveDate has changed from {targetActivity.Detail.GapInsuranceRemoveDate} to NULL" + }); + } + + await _activityService.ApplyStateChange(new ActivityStateChange + { + ActivityId = targetActivity.Id, + ActivityMatchId = linkedActivity.Id, + Note = $"Matched {linkedActivity.Product} Activity Id: {linkedActivity.Id} Added", + State = targetActivity.State + }, false); + } + + private async Task> ValidateMatchedActivityAccessories(List matchedActivities, List accessoryActivities) + { + var accessoryRemovedActivities = new List(); + foreach (var parentActivity in matchedActivities) + { + try + { + var accessoryActivity = accessoryActivities.FirstOrDefault(x => x.MatchingGapInsuranceActivityId == parentActivity.Id) == null + ? await _activityRepository.GetActivityById(parentActivity.MatchingGapInsuranceActivityId) + : accessoryActivities.FirstOrDefault(x => x.MatchingGapInsuranceActivityId == parentActivity.Id); + + if (accessoryActivity == null && parentActivity.Detail.GapInsuranceRemoveDate == null) + { + accessoryRemovedActivities.Add(await RemoveMissingGapAccessory(parentActivity)); + continue; + } + + if (parentActivity?.State == ActivityState.Accepted && accessoryActivity?.State == ActivityState.Accepted) + { + continue; + } + + accessoryRemovedActivities.Add(await RemoveGapAccessory(parentActivity, accessoryActivity)); + } + catch (Exception ex) + { + var parentActivityId = parentActivity != null ? parentActivity.Id.ToString() : "NULL"; + Log.Error(ex, $"Failed to validate matched GAP accessory for Activity Id:{parentActivityId}"); + } + } + + Log.Information($"Removed GAP Accessory From {accessoryRemovedActivities.Count} Activities"); + + return matchedActivities.Except(accessoryRemovedActivities).ToList(); + } + + private async Task RemoveGapAccessory(Activity parentActivity, Activity accessoryActivity) + { + var removedDate = await DetermineRemovedDate(parentActivity, accessoryActivity); + + await ApplyRemovalChangeUpdates(parentActivity, accessoryActivity, removedDate); + await ApplyRemovalChangeUpdates(accessoryActivity, parentActivity, removedDate); + await _unitOfWork.CompleteAsync(); + return parentActivity.State == ActivityState.Accepted ? parentActivity : null; + } + + private async Task ApplyRemovalChangeUpdates(Activity targetActivity, Activity linkedActivity, DateTime removedDateTime) + { + targetActivity.Detail.GapInsuranceRemoveDate = removedDateTime; + targetActivity.MatchingGapInsuranceActivityId = 0; + await _activityDetailService.SaveActivityDetailUpdate(new ActivityDetailUpdate + { + ActivityDetailId = targetActivity.Detail.Id, + Note = $"GapInsuranceRemoveDate has changed from NULL to {removedDateTime}" + }); + await _activityService.ApplyStateChange(new ActivityStateChange + { + ActivityId = targetActivity.Id, + ActivityMatchId = linkedActivity.Id, + Note = $"Matched {linkedActivity.Product} Activity Id: {linkedActivity.Id} Removed", + State = targetActivity.State + }, false); + } + + private static async Task DetermineRemovedDate(Activity parentActivity, Activity accessoryActivity) + { + if (parentActivity.State == ActivityState.Accepted && accessoryActivity.State != ActivityState.Accepted) + { + return await Task.FromResult(accessoryActivity.Accepted.Date.AddHours(12)); + } + + if (parentActivity.State != ActivityState.Accepted && accessoryActivity.State == ActivityState.Accepted) + { + return await Task.FromResult(parentActivity.Accepted.Date.AddHours(12)); + } + + return await Task.FromResult(parentActivity.LastUpdated <= accessoryActivity.LastUpdated + ? parentActivity.Accepted.Date.AddHours(12) + : accessoryActivity.Accepted.Date.AddHours(12)); + } + + private async Task RemoveMissingGapAccessory(Activity activity) + { + Log.Warning($"Unable To Find GAP Accessory Activity Id: {activity.MatchingGapInsuranceActivityId}, Removing GAP Accessory From Activity Id: {activity.Id}"); + var removeDate = DateTime.Now.Date.AddHours(12); + activity.MatchingGapInsuranceActivityId = 0; + activity.Detail.GapInsuranceRemoveDate = removeDate; + await _activityDetailService.SaveActivityDetailUpdate(new ActivityDetailUpdate + { + ActivityDetailId = activity.Detail.Id, + Note = $"GapInsuranceRemoveDate has changed from NULL to {removeDate}" + }); + var activityStateChange = new ActivityStateChange + { + ActivityId = activity.Id, + Note = $"{Product.GAPInsurance} Accessory Activity Removed, Accessory Activity Not Found", + State = activity.State + }; + + await _activityService.ApplyStateChange(activityStateChange); + return activity.State == ActivityState.Accepted ? activity : null; + } + + private async Task> GetMatchingGapAccessoryActivities(IReadOnlyCollection matchedActivities) + { + if (!matchedActivities.Any()) + { + return new List(); + } + + var ids = matchedActivities + .Select(activity => activity.MatchingGapInsuranceActivityId) + .ToList(); + + return await _activityRepository.FindById(ids); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/GoalDispersalService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/GoalDispersalService.cs new file mode 100644 index 0000000..5736083 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/GoalDispersalService.cs @@ -0,0 +1,215 @@ +using LCU.API.Internal.Operations.IncentiveApi.Services.DispersalStrategy; +using LCU.API.Internal.Operations.IncentiveApi.Models.GoalDispersal; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.Library.Internal.ApiResponse.Services; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Results; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public class GoalDispersalService : IGoalDispersalService +{ + private readonly IUserService _userService; + private readonly IGoalService _goalService; + private readonly IDepartmentRepository _departmentRepository; + private readonly INotificationApiService _notificationService; + private readonly IDispersalStrategyService _dispersalStrategyService; + private readonly ProcessResult _goalDispersalProcessResult; + + public GoalDispersalService( + IUserService userService, + IGoalService goalService, + IDepartmentRepository departmentRepository, + INotificationApiService notificationService, + IDispersalStrategyService dispersalStrategyService + ) + { + _userService = userService; + _goalService = goalService; + _departmentRepository = departmentRepository; + _notificationService = notificationService; + _dispersalStrategyService = dispersalStrategyService; + _goalDispersalProcessResult = new ProcessResult(); + } + + public async Task DisperseGoalsByExtrapolatingLastMonth(int departmentId, int dispersalMonth, int dispersalYear) + { + try + { + var lastMonth = dispersalMonth == 1 ? 12 : dispersalMonth - 1; + var lastMonthCorrectedYear = dispersalMonth == 1 ? dispersalYear - 1 : dispersalYear; + var dispersalMonthStartDateTime = DateTime.Parse($"{dispersalYear}-{dispersalMonth}-01"); + var departmentGoals = await _goalService.GetAnnualDepartmentGoalsAsync(departmentId, dispersalYear); + var dispersalMonthDepartmentGoals = departmentGoals.MonthlyGoals[dispersalMonth - 1]; + + var dispersalMonthPersonnel = await _userService.FindDepartmentAssignments( + new DepartmentPersonnelSearchRequest() + { + DepartmentId = departmentId, + Month = dispersalMonth, + Year = dispersalYear, + IncludeAllPersonnel = true + } + ); + + var dispersalMonthPersonnelGoals = await _goalService.GetMonthlyPersonnelGoalsAsync( + new DepartmentPersonnelSearchRequest() + { + DepartmentId = departmentId, + Month = dispersalMonth, + Year = dispersalYear, + IncludeAllPersonnel = true + } + ); + + var modelMonthPersonnelGoals = await _goalService.GetMonthlyPersonnelGoalsAsync( + new DepartmentPersonnelSearchRequest() + { + DepartmentId = departmentId, + Month = lastMonth, + Year = lastMonthCorrectedYear, + IncludeAllPersonnel = true + } + ); + + if (modelMonthPersonnelGoals.GoalAudit.All(x => x.Value < 1)) + { + var weightedDispersalRequest = new DefaultWeightedGoalDispersalData() + { + DepartmentId = departmentId, + DispersalMonthStartDate = dispersalMonthStartDateTime, + DispersalMonthPersonnel = dispersalMonthPersonnel, + DispersalMonthDepartmentGoals = dispersalMonthDepartmentGoals + }; + var weightedReceipt = _dispersalStrategyService.DefaultWeightedDispersal(weightedDispersalRequest); + await _goalService.SaveGoalRangeAsync(weightedReceipt.GoalChanges); + return weightedReceipt; + } + + departmentGoals = await _goalService.GetAnnualDepartmentGoalsAsync(departmentId, lastMonthCorrectedYear); + + var lastMonthDepartmentGoals = departmentGoals.MonthlyGoals[lastMonth - 1]; + var extrapolateGoalsDispersalRequest = new ExtrapolationGoalDispersalData() + { + DepartmentId = departmentId, + DispersalMonthStartDate = dispersalMonthStartDateTime, + DispersalMonthDepartmentGoals = dispersalMonthDepartmentGoals, + DispersalMonthPersonnelGoals = dispersalMonthPersonnelGoals, + ModelDepartmentGoals = lastMonthDepartmentGoals, + ModelPersonnelGoals = modelMonthPersonnelGoals, + DispersalMonthPersonnel = dispersalMonthPersonnel + }; + + var receipt = _dispersalStrategyService.ExtrapolateFromExistingGoals(extrapolateGoalsDispersalRequest); + await _goalService.SaveGoalRangeAsync(receipt.GoalChanges); + return receipt; + } + catch (Exception ex) + { + Log.Error(ex, $"Failed to DisperseGoalsForNextMonth for dept:{departmentId} month:{dispersalMonth} dispersalYear: {dispersalYear}"); + throw new Exceptions.ServerErrorException(); + } + } + + public async Task> DisperseAllDepartmentGoalsForMonth(int month, int year) + { + var results = new Dictionary(); + var departments = await _departmentRepository.GetAllDepartmentsAsync(); + foreach (var department in departments) + { + try + { + if (department.Id == null) + { + throw new Exception($"Department Has No Id. Skipping Dispersal: {department.DepartmentName}"); + } + + var departmentKey = $"{department.Id} - {department.DepartmentName}"; + + Log.Debug($"Dispersing Goals for Department: {departmentKey}"); + var receipt = await DisperseGoalsByExtrapolatingLastMonth((int)department.Id, month, year); + results.Add(departmentKey, receipt); + Log.Debug($"Completed Dispersal for: {departmentKey}"); + } + catch (Exception ex) + { + var departmentKey = $"{department.Id} - {department.DepartmentName}"; + Log.Error(ex, $"Failed Month Start Auto-Disperse of Personnel Goals for Department: {departmentKey}"); + results.Add(departmentKey, null); + } + } + + return results; + } + + public async Task ExecuteMonthStartDispersal(GoalDispersalRequest request) + { + try + { + Log.Information("Executing Monthly Goal Dispersal"); + var result = await DisperseAllDepartmentGoalsForMonth(request.StartDate.Month, request.StartDate.Year); + + Log.Information("Finished Monthly Goal Dispersal. Sending Report Email."); + await _notificationService.SendMonthStartGoalDispersalReceipt(result, request.GoalDispersalNotification); + + _goalDispersalProcessResult.MessageStack.Add($"Goal Dispersal Service Processed for {request.StartDate.Month}{request.StartDate.Year} records."); + Log.Information("Successfully Sent Report Email."); + await QueueReturnResponse.QueueResponse(request, _goalDispersalProcessResult.Success, _goalDispersalProcessResult.MessageStack); + } + catch (Exception exception) + { + Log.Fatal(exception, "There was an Unhandled error Executing Month Start Dispersal"); + var message = $"There was an issue with handling the Goal Dispersal Service Process {request.StartDate.Month}{request.StartDate.Year}. Exception Message: {exception.Message}."; + _goalDispersalProcessResult.MessageStack.Add(message); + _goalDispersalProcessResult.Success = false; + await QueueReturnResponse.QueueResponse(request, _goalDispersalProcessResult.Success, _goalDispersalProcessResult.MessageStack); + throw new ServiceException(message); + } + } + + public async Task DepartmentGoalChangeReDispersal(DepartmentGoalExtrapolationRequest request) + { + var personnelGoals = await _goalService.GetMonthlyPersonnelGoalsAsync(new DepartmentPersonnelSearchRequest() + { + DepartmentId = request.DepartmentId, + IncludeAllPersonnel = true, + Year = request.DepartmentGoalStartDate.Year, + Month = request.DepartmentGoalStartDate.Month, + }); + + var receipt = _dispersalStrategyService.ExtrapolateSingleDepartmentProduct(request, personnelGoals); + return receipt; + } + + public async Task CustomWeightedDispersal(CustomWeightedDispersalRequest request) + { + try + { + var departmentGoals = await _goalService.GetAnnualDepartmentGoalsAsync(request.DepartmentId, request.year); + var productDepartmentGoal = departmentGoals.MonthlyGoals[request.month - 1].ProductGoals[request.DispersalProduct]; + + var departmentPersonnel = await _userService.FindDepartmentAssignments( + new DepartmentPersonnelSearchRequest() + { + DepartmentId = request.DepartmentId, + Month = request.month, + Year = request.year, + IncludeAllPersonnel = true + } + ); + + var receipt = _dispersalStrategyService.CustomWeightedDispersal(request, departmentPersonnel, productDepartmentGoal); + await _goalService.SaveGoalRangeAsync(receipt.GoalChanges); + + return receipt; + } + catch (Exception ex) + { + Log.Error(ex, "Custom Weighted Dispersal Failed"); + throw new Exceptions.ServerErrorException(); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/GoalService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/GoalService.cs new file mode 100644 index 0000000..0071672 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/GoalService.cs @@ -0,0 +1,254 @@ +using LCU.API.Internal.Operations.IncentiveApi.Extensions; +using LCU.API.Internal.Operations.IncentiveApi.Services.DispersalStrategy; +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.API.Internal.Operations.IncentiveApi.Models.GoalDispersal; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public class GoalService : IGoalService +{ + private readonly IGoalRepository _goalRepository; + private readonly IGoalChangeRepository _goalChangeRepository; + private readonly ICurrentUserService _currentUserService; + private readonly IDispersalStrategyService _dispersalStrategyService; + private readonly IDepartmentRepository _departmentRepository; + private readonly IUserService _userService; + private readonly IUnitOfWork _unitOfWork; + + public GoalService( + IGoalRepository goalRepository, + IGoalChangeRepository goalChangeRepository, + ICurrentUserService currentUserService, + IDispersalStrategyService dispersalStrategyService, + IUnitOfWork unitOfWork, + IUserService userService, + IDepartmentRepository departmentRepository + ) + { + _goalRepository = goalRepository; + _goalChangeRepository = goalChangeRepository; + _currentUserService = currentUserService; + _unitOfWork = unitOfWork; + _userService = userService; + _departmentRepository = departmentRepository; + _dispersalStrategyService = dispersalStrategyService; + } + + public async Task> SaveGoalAsync(GoalChange goalChange) + { + return await SaveGoalRangeAsync(new List() { goalChange }); + } + + private async Task ConfirmAppUserIdAndSaveGoalChange(GoalChange goalChange) + { + if (!goalChange.IsDepartmentGoalChange()) + { + var appUser = await _userService.GetUserByEmployeeIdAsync(goalChange.PersonId); + goalChange.AppUserId = appUser.Id; + } + + await _goalChangeRepository.SaveGoalChangeAsync(goalChange); + return goalChange; + } + + public async Task> SaveGoalRangeAsync(List goalChanges) + { + var goalsToSave = new List(); + foreach (var goalChange in goalChanges) + { + await ConfirmAppUserIdAndSaveGoalChange(goalChange); + + var goal = await _goalRepository.FindExistingGoal(goalChange); + if (goal != null) + { + var originalGoalValue = goal.Value; + goal.Value = goalChange.Value; + + if (goal.IsDepartmentGoal() && originalGoalValue != goal.Value) + { + goalsToSave.AddRange(await CreatePersonnelGoalChangesFromDepartmentGoalChange(goalChange, originalGoalValue)); + } + } + else + { + goal = new Goal() + { + AppUserId = goalChange.AppUserId, + Product = goalChange.Product, + EmployeeId = goalChange.PersonId, + DepartmentId = goalChange.DepartmentId, + StartDate = goalChange.StartDate.StartOfMonth(), + EndDate = goalChange.EndDate.EndOfMonth(), + Value = goalChange.Value + }; + await _goalRepository.SaveGoalAsync(goal); + } + + goalsToSave.Add(goal); + } + + await _unitOfWork.CompleteAsync(); + return goalsToSave; + } + + private async Task> CreatePersonnelGoalChangesFromDepartmentGoalChange(GoalChange goalChange, int originalGoalValue) + { + if (!goalChange.IsDepartmentGoalChange()) return new List(); + var personnelGoals = new List(); + var departmentPersonnelGoals = await GetMonthlyPersonnelGoalsAsync( + new DepartmentPersonnelSearchRequest() + { + DepartmentId = goalChange.DepartmentId, + IncludeAllPersonnel = true, + Year = goalChange.StartDate.Year, + Month = goalChange.StartDate.Month, + }); + var receipt = _dispersalStrategyService.ExtrapolateSingleDepartmentProduct( + new DepartmentGoalExtrapolationRequest() + { + DepartmentGoalEndDate = goalChange.EndDate.EndOfMonth(), + DepartmentGoalStartDate = goalChange.StartDate.StartOfMonth(), + DepartmentId = goalChange.DepartmentId, + NewDepartmentGoal = goalChange.Value, + OriginalDepartmentGoal = originalGoalValue, + Product = goalChange.Product + }, departmentPersonnelGoals); + + foreach (var reDispersedGoalChange in receipt.GoalChanges) + { + await ConfirmAppUserIdAndSaveGoalChange(reDispersedGoalChange); + var reDispersedGoal = await CreateOrUpdateGoalFromGoalChange(reDispersedGoalChange); + personnelGoals.Add(reDispersedGoal); + } + + return personnelGoals; + } + + private async Task CreateOrUpdateGoalFromGoalChange(GoalChange goalChange) + { + var goal = await _goalRepository.FindExistingGoal(goalChange); + if (goal != null) + { + goal.Value = goalChange.Value; + } + else + { + goal = new Goal() + { + Product = goalChange.Product, + AppUserId = goalChange.AppUserId, + EmployeeId = goalChange.PersonId, + DepartmentId = goalChange.DepartmentId, + StartDate = goalChange.StartDate.StartOfMonth(), + EndDate = goalChange.EndDate.EndOfMonth(), + Value = goalChange.Value + }; + await _goalRepository.SaveGoalAsync(goal); + } + + return goal; + } + + public async Task GetAnnualDepartmentGoalsAsync(int departmentId, int year) + { + var goals = new DepartmentGoals(departmentId, year); + + goals.ApplyAll(await _goalRepository.FindAnnualDepartmentGoals(departmentId, year)); + return goals; + } + + public async Task> GetAllAnnualDepartmentGoalsAsync(int year) + { + var departments = await _departmentRepository.GetAllDepartmentsAsync(); + var departmentGoals = new List(); + foreach (var department in departments) + { + if (department.Id != null) + { + departmentGoals.Add(await GetAnnualDepartmentGoalsAsync(department.Id.Value, year)); + } + } + + return departmentGoals; + } + + public async Task GetMonthlyPersonnelGoalsAsync(DepartmentPersonnelSearchRequest searchRequest) + { + var users = await _userService.FindDepartmentAssignments(searchRequest); + var goals = new PersonnelGoals(searchRequest.DepartmentId, searchRequest.Year, searchRequest.Month, users); + + goals.ApplyAll(await _goalRepository.FindMonthlyPersonnelGoals(searchRequest.DepartmentId, users, + searchRequest.Year, searchRequest.Month)); + return goals; + } + + public async Task> GetGoalsAsync(GoalSearchRequest goalSearchRequest) + { + return await _goalRepository.GetGoalsAsync(goalSearchRequest); + } + + public async Task> GetGoalChangesAsync() + { + return await _goalChangeRepository.GetGoalChangesAsync(); + } + + public async Task GetEmployeeGoalsForMonthRangeAsync(AppUser employee, MonthRange monthRange) + { + var users = new List() + { + employee + }; + var goals = new EmployeeGoals(employee, monthRange); + + + foreach (var dept in goals.Departments) + { + goals.ApplyAll( + await _goalRepository.FindPersonnelGoalsByMonthRange(dept.DepartmentId, users, monthRange)); + } + + return goals; + } + + public async Task> GetEmployeeDepartmentsGoalsForMonthRangeAsync(AppUser employee, MonthRange monthRange) + { + var allGoals = new List(); + + foreach (var dept in employee.Departments) + { + var isAssignedInMonthRange = dept.StartDate.Date < monthRange.EndTime.Date && + (dept.EndDate == null || + monthRange.StartTime.Date < dept.EndDate.Value.Date); + if (isAssignedInMonthRange) + { + var StartDate = dept.StartDate.Date < monthRange.StartTime.Date + ? monthRange.StartTime.Date + : dept.StartDate.Date; + var EndDate = dept.EndDate == null ? monthRange.EndTime.Date + : dept.EndDate.Value.Date > monthRange.EndTime.Date ? monthRange.EndTime.Date + : dept.EndDate.Value.Date; + var monthRangeOfAssignment = new MonthRange + ( + StartDate.Year, + StartDate.Month, + EndDate.Year, + EndDate.Month + ); + + var goals = new DepartmentGoalsForRange(dept.DepartmentId, monthRangeOfAssignment); + var deptGoals = + await _goalRepository.FindDepartmentGoalsByMonthRange(goals.DepartmentId, goals.MonthRange); + goals.ApplyAll(deptGoals); + allGoals.Add(goals); + } + } + + return allGoals; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IActivityDetailService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IActivityDetailService.cs new file mode 100644 index 0000000..454dc30 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IActivityDetailService.cs @@ -0,0 +1,13 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public interface IActivityDetailService +{ + Task> TryUpdateActivityAccountDetails(List activities); + Task> TryUpdateLicReferralStationDetail(List activities); + Task> TryUpdateDpEligibleLoanDetail(List activities); + Task TryUpdateDpAccessory(Activity saleActivity, Activity dpAccessoryActivity); + Task> GetAllActivityDetailUpdates(int activityDetailId); + Task SaveActivityDetailUpdate(ActivityDetailUpdate activityDetailUpdate); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IActivityService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IActivityService.cs new file mode 100644 index 0000000..93e75aa --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IActivityService.cs @@ -0,0 +1,34 @@ +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Results; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public interface IActivityService +{ + Task SaveActivityAsync(Activity activity); + Task SaveActivityRangeAsync(IReadOnlyCollection activity); + Task> GetActivitiesAsync(); + Task> ListByFilterAsync(ActivityFilter filter); + Task ApplyStateChange(ActivityStateChange activityStateChange, bool commitTransaction = true); + Task> GetActivityStateChanges(int activityId); + Task> GetDepartmentActivityForMonthRangeAsync(List userDepartments, MonthRange monthRange); + Task GetActivity(int activityId); + Task CreateAndRetrieveActivity(Activity activity); + Task GetActivityForStateUpdate(Activity queryActivity); + Task GetActivityForDetailUpdate(Activity queryActivity); + Task ValidateCanApplyActivityStateChange(AppUser appUser, ActivityStateChange activityStateChange); + Task RequestTransfer(ActivityTransferRequest activityTransferRequest); + Task TransferToOwnDepartment(ActivityTransferRequest activityTransferRequest, AppUser employee); + Task> MatchOrExpireReferrals(); + Task RunReferralStatusUpdates(); + Task ApproveTransfer(AppUser appUser, ActivityTransferApprovalRequest activityTransferApproval); + Task GetRequestedTransfer(int activityId); + Task CancelPendingTransfersBetween(MonthRange range); + Task> FilterExternalActivitiesByExistingSourceId(List incomingEternalActivities); + Task GetActivityAuthorizedCommands(AppUser appUser, int activityId); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IAuthorizationService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IAuthorizationService.cs new file mode 100644 index 0000000..0926bbd --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IAuthorizationService.cs @@ -0,0 +1,13 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public interface IAuthorizationService +{ + Task AppUserCanMakeStateChange(AppUser appUser, Activity activity, ActivityState targetState, int targetDepartmentId = 0); + Task AppUserCanViewReport(AppUser appUser, string permissionCode); + Task ValidateHierarchicalDepartmentPermission(AppUser appUser, IReadOnlyCollection allowedDirectRoles, int departmentId); + Task ValidateIsBusinessLineManager(AppUser appUser, int departmentId); + Task ValidateIsRegionalManager(AppUser appUser, int departmentId); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ICommissionService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ICommissionService.cs new file mode 100644 index 0000000..0c968db --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ICommissionService.cs @@ -0,0 +1,9 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public interface ICommissionService +{ + Task> GetCommissionsAsync(); + Task SaveCommissionsAsync(List commissions); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IDashboardService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IDashboardService.cs new file mode 100644 index 0000000..3b18ea4 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IDashboardService.cs @@ -0,0 +1,14 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public interface IDashboardService +{ + Task GetRegionProgressForMonthRangeAsync(Region region, MonthRange monthRange); + Task GetBusinessLineProgressForMonthRangeAsync(BusinessLine bl, MonthRange monthRange); + Task GetEmployeeProgressForMonthRangeAsync(AppUser employee, MonthRange monthRange); + Task> GetDepartmentProgressForMonthRangeAsync(List userDepartments, MonthRange monthRange); + Task GetDepartmentProgressForMonthRangeAsync(int departmentId, MonthRange monthRange); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IDeadActivityService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IDeadActivityService.cs new file mode 100644 index 0000000..dc8759b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IDeadActivityService.cs @@ -0,0 +1,13 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public interface IDeadActivityService +{ + public Task SaveDeadActivity(DeadActivity newDeadActivity); + Task> SaveDeadActivityRange(List newDeadActivities); + public Task GetDeadActivity(int activityId); + public Task> ListByFilterAsync(DeadActivityFilter filter); + Task ProcessDeadActivity(int activityId, int activityRef); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IDepartmentService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IDepartmentService.cs new file mode 100644 index 0000000..30ee0ac --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IDepartmentService.cs @@ -0,0 +1,19 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public interface IDepartmentService +{ + Task> GetAllDepartmentsAsync(); + Task GetDepartmentByIdAsync(int id); + Task SaveDepartmentAsync(Department department); + Task UpdateDepartmentAsync(Department department, int id); + Task> GetDepartmentsByRegionAsync(Region region); + Task> GetDepartmentsByBusinessLineAsync(BusinessLine businessLine); + Task GetDepartmentDetailsAsync(int id); + Task> GetHierarchicalDepartmentsForEmployeeAsync(AppUser employee, bool includeManagers); + Task GetDepartmentsByDayforceGlCodeAsync(int glCode); + Task> FindDepartmentAssignmentsForRange(int departmentId, MonthRange monthRange); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IDnaActivitySyncService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IDnaActivitySyncService.cs new file mode 100644 index 0000000..683339e --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IDnaActivitySyncService.cs @@ -0,0 +1,10 @@ +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public interface IDnaActivitySyncService +{ + [Obsolete("01/01/2025 - Removed and located in each activity type service.")] + Task SyncLatestActivitiesFromDna(); + Task HandleProcessRequest(IncentiveProcessRequest request); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IDnaService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IDnaService.cs new file mode 100644 index 0000000..dd88208 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IDnaService.cs @@ -0,0 +1,13 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Accounts; +using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Transaction; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public interface IDnaService +{ + Task> GetNewAccountDetailsForActivities(List accountNbrs); + Task> QueryActivityManagerForActivities(List activityManagerIds); + Task> GetTransactionHistory(string accountNumber, DateTime startDate, DateTime endDate, List transactionCodes); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IEStatementService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IEStatementService.cs new file mode 100644 index 0000000..fae375e --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IEStatementService.cs @@ -0,0 +1,8 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public interface IEStatementService +{ + Task> UpdateOrExpireEStatementReferral(); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IGapInsuranceActivityService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IGapInsuranceActivityService.cs new file mode 100644 index 0000000..2647c48 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IGapInsuranceActivityService.cs @@ -0,0 +1,8 @@ +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public interface IGapInsuranceActivityService +{ + Task MatchOrRemoveGapAccessories(MonthRange monthRange = null); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IGoalDispersalService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IGoalDispersalService.cs new file mode 100644 index 0000000..9c01804 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IGoalDispersalService.cs @@ -0,0 +1,13 @@ +using LCU.API.Internal.Operations.IncentiveApi.Models.GoalDispersal; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public interface IGoalDispersalService +{ + Task> DisperseAllDepartmentGoalsForMonth(int month, int year); + Task DisperseGoalsByExtrapolatingLastMonth(int departmentId, int dispersalMonth, int dispersalYear); + Task ExecuteMonthStartDispersal(GoalDispersalRequest request); + Task DepartmentGoalChangeReDispersal(DepartmentGoalExtrapolationRequest request); + Task CustomWeightedDispersal(CustomWeightedDispersalRequest request); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IGoalService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IGoalService.cs new file mode 100644 index 0000000..9f228c0 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IGoalService.cs @@ -0,0 +1,19 @@ +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public interface IGoalService +{ + Task> SaveGoalAsync(GoalChange goalChange); + Task GetAnnualDepartmentGoalsAsync(int departmentId, int year); + Task> GetAllAnnualDepartmentGoalsAsync( int year); + Task GetMonthlyPersonnelGoalsAsync(DepartmentPersonnelSearchRequest searchRequest); + Task> GetGoalsAsync(GoalSearchRequest goalSearchRequest); + Task> GetGoalChangesAsync(); + Task> SaveGoalRangeAsync(List goalChanges); + Task GetEmployeeGoalsForMonthRangeAsync(AppUser employee, MonthRange range); + Task> GetEmployeeDepartmentsGoalsForMonthRangeAsync(AppUser employee, MonthRange monthRange); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IProcessorHistoryService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IProcessorHistoryService.cs new file mode 100644 index 0000000..14bce83 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IProcessorHistoryService.cs @@ -0,0 +1,10 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public interface IProcessorHistoryService +{ + public Task GetProcessorHistory(Process process); + public Task SaveProcessorHistory(ProcessorHistoryRecord historyRecord); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ISettingsService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ISettingsService.cs new file mode 100644 index 0000000..26ff611 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ISettingsService.cs @@ -0,0 +1,14 @@ +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApi.Models; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public interface ISettingsService +{ + Task SaveRoleMappingAsync(RoleMappingUpdateRequest roleMapping); + Task SaveRoleMappingRangeAsync(IEnumerable roleMapping); + Task SaveNewUnknownRoleMappingRangeAsync(IEnumerable unknownRoles); + Task> GetRoleMappingsAsync(); + Task> GetAuthorizedReportSettings(AppUser appUser); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IUserAssignmentService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IUserAssignmentService.cs new file mode 100644 index 0000000..38f04b7 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IUserAssignmentService.cs @@ -0,0 +1,14 @@ +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services +{ + public interface IUserAssignmentService + { + Task> GetAllUserAssignmentsAsync(); + Task GetUserAssignment(AssignmentType assignmentType, string key); + Task SetRegionalManager(UserAssignmentRequest userAssignmentRequest); + } + +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IUserService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IUserService.cs new file mode 100644 index 0000000..4d58c61 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IUserService.cs @@ -0,0 +1,36 @@ +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; +using LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Results; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Services; + +public interface IUserService +{ + Task> GetAllUsersAsync(UserSearchRequest userSearchRequest); + Task GetUserByEmployeeIdAsync(string employeeId); + Task GetUserByAdEmailAsync(string adEmail); + Task GetActiveUserByEmployeeIdAsync(string employeeId); + Task GetActiveUserByAdEmailAsync(string adEmail); + Task FindUserAsync( string employeeId, string adEmail); + Task SaveUserAsync(AppUser newAppUser); + Task> SaveUserRangeAsync(List newUsers); + Task SyncDayForceEmployee(ActiveEmployeeResult activeEmployee); + Task TerminateDayForceEmployee(TerminatedEmployeeResult terminatedEmployee); + Task> AssignDepartments(string employeeId, DepartmentAssignmentRequest assignment); + Task UpdateDepartmentAssignment(DepartmentUpdateRequest request); + Task> FindDepartmentAssignments(DepartmentPersonnelSearchRequest searchRequest); + Task> AssignPermissions(string employeeId, List permCodes); + Task> RemovePermissions(string employeeId, List permCodes); + Task> UpdatePermissions(string employeeId, AdditionalPermissionRequest request); + public Task TryFindSingleUserByName(UserNameSearchRequest searchRequest); + Task FindManager(AppUser appUser); + Task> FindDepartmentManagers(int departmentId); + Task UserHasAnyRequiredPermission(string appUserEmail, List allowedPermissions); + Task SyncActiveUserIds(); + Task> GetAllUserSummariesAsync(); + IReadOnlyCollection GetTerminatedUsersAsync(MonthRange monthRange); + Task UpdateEmployeeDetails(EmployeeDetailsUpdate employeeDetailsUpdate); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IncentiveProcesses/IIncentiveProcessesService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IncentiveProcesses/IIncentiveProcessesService.cs new file mode 100644 index 0000000..f2fa337 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IncentiveProcesses/IIncentiveProcessesService.cs @@ -0,0 +1,12 @@ +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Results; +using LCU.Library.Internal.LCUServiceFactory.Services; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.IncentiveProcesses; + +public interface IIncentiveProcessesService : ILcuService +{ + Task SyncLatestActivitiesFromDna(IncentiveProcessRequest request); + Task> ProcessLatestActivity(); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IncentiveProcesses/IncentiveProcessesService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IncentiveProcesses/IncentiveProcessesService.cs new file mode 100644 index 0000000..726a9e1 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/IncentiveProcesses/IncentiveProcessesService.cs @@ -0,0 +1,13 @@ +using LCU.Library.Internal.LCUServiceFactory.Configurations; +using LCU.Library.Internal.LCUServiceFactory.Services; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.IncentiveProcesses; + +public abstract class IncentiveProcessesService : LcuAbstractService +{ + public IncentiveProcessesService(ServicesFactoryConfiguration servicesFactoryConfiguration) + : base(servicesFactoryConfiguration.GetServiceFactoryRegistrationByInterface()) + { + } +} + diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/MicroApiServices/CoreApiWrapperService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/MicroApiServices/CoreApiWrapperService.cs new file mode 100644 index 0000000..361e277 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/MicroApiServices/CoreApiWrapperService.cs @@ -0,0 +1,187 @@ +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.API.Internal.Operations.IncentiveApi.DataAccess; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using NewAtmCheckCardActivity = LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities.NewAtmCheckCardActivity; +using NewCheckingAccountActivity = LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities.NewCheckingAccountActivity; +using NewLoanSaleActivity = LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities.NewLoanSaleActivity; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Accounts; +using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Activity; +using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Transaction; +using System.Web; +using LCU.Library.Internal.Exceptions; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; + +public class CoreApiWrapperService : ICoreApiWrapperService +{ + private readonly ICoreApiWrapperDao _coreApiWrapperDao; + private readonly CoreApiWrapperConfiguration _coreApiWrapperConfiguration; + + public CoreApiWrapperService(CoreApiWrapperConfiguration coreApiWrapperConfiguration, ICoreApiWrapperDao coreApiWrapperDao) + { + _coreApiWrapperDao = coreApiWrapperDao; + _coreApiWrapperConfiguration = coreApiWrapperConfiguration; + } + + public async Task> GetNewCheckingAccountSalesActivity(DateTime activityDt) + { + try + { + + return await _coreApiWrapperDao.GetNewCheckingAccountSalesActivity(activityDt); + } + catch (Exception ex) + { + var error = $"Failed to get New Checking Account Sales from DNA after: {activityDt} Exception Message: {ex.Message}"; + Log.Error(error); + throw new ServiceException(error); + } + } + + public async Task> GetNewCheckingDigitalBankingAccountSalesActivity(DateTime activityDt) + { + try + { + return await _coreApiWrapperDao.GetNewCheckingDigitalBankingAccountSalesActivity(activityDt); + } + catch (Exception ex) + { + var error = $"Failed to get New Digital Banking Checking Account Sales from DNA after: {activityDt} Exception Message: {ex.Message}"; + Log.Error(error); + throw new ServiceException(error); + } + } + + public async Task> GetNewAtmCheckCardSalesActivity(DateTime activityDt) + { + try + { + return await _coreApiWrapperDao.GetNewAtmCheckCardSalesActivity(activityDt); + } + catch (Exception ex) + { + var error = $"Failed to get New ATM and Check Card Sales from DNA after: {activityDt} Exception Message: {ex.Message}"; + Log.Error(error); + throw new ServiceException(error); + } + } + + public async Task> GetNewActivityManagerReferralsActivity(DateTime activityDt) + { + try + { + return await _coreApiWrapperDao.GetNewActivityManagerReferralsActivity(activityDt); + } + catch (Exception ex) + { + var error = $"Failed to get New Activity Manager Referrals from DNA after: {activityDt} Exception Message: {ex.Message}"; + Log.Error(error); + throw new ServiceException(error); + } + } + + public async Task> GetNewMortgageSaleActivities(DateTime activityDt) + { + try + { + return await _coreApiWrapperDao.GetNewMortgageSaleActivities(activityDt); + } + catch (Exception ex) + { + var error = $"Failed to get New Mortgage Sales from DNA after: {activityDt} Exception Message: {ex.Message}"; + Log.Error(error); + throw new ServiceException(error); + } + } + + public async Task> GetNewVehicleSaleActivities(DateTime activityDt) + { + try + { + return await _coreApiWrapperDao.GetNewVehicleSaleActivities(activityDt); + } + catch (Exception ex) + { + var error = $"Failed to get New Vehicle Sales from DNA after: {activityDt} Exception Message: {ex.Message}"; + Log.Error(error); + throw new ServiceException(error); + } + } + + public async Task> GetNewDpEligibleSaleActivities(DateTime activityDt) + { + try + { + return await _coreApiWrapperDao.GetNewDpEligibleSaleActivities(activityDt); + } + catch (Exception ex) + { + var error = $"Failed to get New DP Eligible Sales and Actvities from DNA after: {activityDt} Exception Message: {ex.Message}"; + Log.Error(error); + throw new ServiceException(error); + } + } + + public async Task> GetUpdatedAccountInformation(List accountNumbers) + { + try + { + return await _coreApiWrapperDao.GetUpdatedAccountInformation(accountNumbers); + } + catch (Exception ex) + { + var error = $"Failed to get Updated Account Information from DNA after: {string.Join(",", accountNumbers)}" + $"Exception Message: {ex.Message}"; + Log.Error(error); + throw new ServiceException(error); + } + } + + public async Task> GetActivityManagerActivities(ActivityQueryRequest request) + { + try + { + return await _coreApiWrapperDao.GetActivityManagerActivities(request); + } + catch (Exception ex) + { + var error = $"Failed to get Activity Manager Activities from DNA after: {request.ActivityIds} Exception Message: {ex.Message}"; + Log.Error(error); + throw new ServiceException(error); + } + } + + public async Task> GetTransactionHistory(string accountNumber, DateTime startDate, DateTime endDate, List transactionCodes) + { + try + { + return await _coreApiWrapperDao.GetTransactionHistory(accountNumber, startDate, endDate, transactionCodes); + } + catch (Exception ex) + { + var error = $"Failed to get Transaction History for Account Number: {accountNumber} Exception Message: {ex.Message}"; + Log.Error(error); + throw new ServiceException(error); + } + } + + public string DateTimeToUrlString(DateTime dateTime) + { + var dtstring = dateTime.ToString("yyyy-MM-ddTHH:mm:ss"); + return HttpUtility.UrlEncode(dtstring); + } + + public async Task> GetEStatmentDeliveryMethods(DateTime activityDt) + { + try + { + return await _coreApiWrapperDao.GetEStatmentDeliveryMethods(activityDt); + } + catch (Exception ex) + { + var error = $"Failed to get EStatement Delivery Methods from DNA after: {activityDt} Exception Message: {ex.Message}"; + Log.Error(error); + throw new ServiceException(error); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/MicroApiServices/FileOperationsApiService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/MicroApiServices/FileOperationsApiService.cs new file mode 100644 index 0000000..ff88315 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/MicroApiServices/FileOperationsApiService.cs @@ -0,0 +1,43 @@ +using LCU.API.Internal.Operations.IncentiveApi.DataAccess; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.FileOperationsModels.Models; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; + +public class FileOperationsApiService : IFileOperationsApiService +{ + private readonly IFileOperationsApiDao _fileOperationDao; + + public FileOperationsApiService(IFileOperationsApiDao fileOperationDao) + { + _fileOperationDao = fileOperationDao; + } + + public async Task BuildDynamicFileList(DynamicFileListRequest dynamicFileRequest) + { + try + { + return await _fileOperationDao.BuildDynamicFileList(dynamicFileRequest); + } + catch (Exception exception) + { + var message = $"Error to BuildDynamicFileList. FilePath: {dynamicFileRequest.Path}."; + Log.Error(exception, message); + throw new ServiceException($"{message} Exception Message:{exception.Message}"); + } + } + + public async Task GetFileContent(string filePath) + { + try + { + return await _fileOperationDao.GetFileContent(filePath); + } + catch (Exception exception) + { + var message = $"Failure to GetFileContent. FilePath: {filePath}."; + Log.Error(exception, message); + throw new ServiceException($"{message} Exception Message: {exception.Message}"); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/MicroApiServices/ICoreApiWrapperService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/MicroApiServices/ICoreApiWrapperService.cs new file mode 100644 index 0000000..e3cae3e --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/MicroApiServices/ICoreApiWrapperService.cs @@ -0,0 +1,25 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Accounts; +using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Activity; +using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Transaction; +using NewAtmCheckCardActivity = LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities.NewAtmCheckCardActivity; +using NewCheckingAccountActivity = LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities.NewCheckingAccountActivity; +using NewLoanSaleActivity = LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities.NewLoanSaleActivity; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; + +public interface ICoreApiWrapperService +{ + public Task> GetEStatmentDeliveryMethods(DateTime activityDt); + public Task> GetNewCheckingDigitalBankingAccountSalesActivity(DateTime activityDt); + public Task> GetNewCheckingAccountSalesActivity(DateTime activityDt); + public Task> GetNewAtmCheckCardSalesActivity(DateTime activityDt); + public Task> GetNewActivityManagerReferralsActivity(DateTime activityDt); + public Task> GetNewMortgageSaleActivities(DateTime activityDt); + public Task> GetNewVehicleSaleActivities(DateTime activityDt); + public Task> GetNewDpEligibleSaleActivities(DateTime activityDt); + public Task> GetUpdatedAccountInformation(List activityIds); + public Task> GetActivityManagerActivities(ActivityQueryRequest request); + public Task> GetTransactionHistory(string accountNumber, DateTime startDate, DateTime endDate, List transactionCodes); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/MicroApiServices/IFileOperationsApiService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/MicroApiServices/IFileOperationsApiService.cs new file mode 100644 index 0000000..b3fa12e --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/MicroApiServices/IFileOperationsApiService.cs @@ -0,0 +1,9 @@ +using LCU.Library.Internal.FileOperationsModels.Models; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; + +public interface IFileOperationsApiService +{ + Task BuildDynamicFileList(DynamicFileListRequest dynamicFileRequest); + Task GetFileContent(string filePath); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/MicroApiServices/INotificationApiService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/MicroApiServices/INotificationApiService.cs new file mode 100644 index 0000000..7c86ba0 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/MicroApiServices/INotificationApiService.cs @@ -0,0 +1,13 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; +using LCU.API.Internal.Operations.IncentiveApi.Models.GoalDispersal; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; + +public interface INotificationApiService +{ + public Task SendMonthStartGoalDispersalReceipt(Dictionary receipts, List recipients); + public Task SendMissingRoleNotification(IEnumerable usersMissingRole, List recipients); + public Task SendTerminatedEmployeeNotification(IEnumerable terminated, List recipients); + public Task SendTransferRequestInitiatedNotification(ActivityTransferAction transferAction, List recipients); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/MicroApiServices/NotificationApiService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/MicroApiServices/NotificationApiService.cs new file mode 100644 index 0000000..642b5ca --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/MicroApiServices/NotificationApiService.cs @@ -0,0 +1,312 @@ +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.API.Internal.Operations.IncentiveApi.DataAccess; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; +using LCU.API.Internal.Operations.IncentiveApi.Models.GoalDispersal; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.NotificationsApiLib.Models; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; + +public class NotificationApiService : INotificationApiService +{ + private readonly EmailRecipientsConfiguration _emailRecipientsConfig; + private readonly INotificationApiDao _notificationsDao; + private readonly ApiConfiguration _incentiveApiConfig; + + public NotificationApiService(EmailRecipientsConfiguration emailRecipientsConfig, ApiConfiguration incentiveApiConfig, INotificationApiDao notificationsDao, + IUserService userService) + { + _emailRecipientsConfig = emailRecipientsConfig; + _notificationsDao = notificationsDao; + _incentiveApiConfig = incentiveApiConfig; + } + + public async Task SendMonthStartGoalDispersalReceipt(Dictionary receipts, List recipients) + { + try + { + #region Body HTML Boilerplate + + string body = + @" + + + + + + + "; + + #endregion + + foreach (var (departmentName, receipt) in receipts) + { + if (receipt == null) + { + body += $@" + + + + + "; + } + else if (receipt.GoalDispersalErrors.Any()) + { + body += $@" + + + + + "; + } + } + + body += @"

Department

Strategy

Alerts

{departmentName} FAILED No Receipt Data, Failed to Run Dispersal.
{departmentName} {receipt.Strategy} {FormatReceiptItems(receipt.GoalDispersalErrors)}
"; + + var emailRequest = new EmailRequest + { + Recipient = recipients, + Subject = "Month Start Personnel Dispersal Report", + Body = body + }; + + await _notificationsDao.SendMessageAsync(emailRequest); + } + catch (Exception ex) + { + Log.Error(ex, "Failed to Send Month Start Dispersal Report"); + } + } + + public async Task SendMissingRoleNotification(IEnumerable usersMissingRole, List recipients) + { + try + { + #region Body HTML Boilerplate + + string body = + @" + + + + + + + + "; + + #endregion + + foreach (var user in usersMissingRole) + { + body += $@" + + + + + + "; + } + body += @"

Name

Employee ID

Department

Missing Role

{user.FullName}{user.EmployeeId}{user.HomeDepartment}{user.BaseRole}
"; + + var emailRequest = new EmailRequest + { + Recipient = recipients, + Subject = $"Users missing Dayforce role", + Body = body + }; + + await _notificationsDao.SendMessageAsync(emailRequest); + } + catch (Exception ex) + { + Log.Error(ex, "Failed to send missing role notification"); + } + } + + public async Task SendTerminatedEmployeeNotification(IEnumerable terminated, List recipients) + { + try + { + #region Body HTML Boilerplate + + string body = + @" + + + + + + + + + + + "; + + #endregion + + foreach (var receipt in terminated) + { + var manager = receipt.Manager != null ? receipt.Manager.FullName : "Not Found"; + body += $@" + + + + + + + + + "; + } + body += @"

Name

Employee ID

Department

Terminated

Manager

Prev Month Payout

This Month Payout

{receipt.Terminated.FullName}{receipt.Terminated.EmployeeId}{receipt.Terminated.HomeDepartment}{receipt.Terminated.Terminated.Value:MM/dd/yyyy}{manager}${receipt.LastMonthPayout.PayoutTotal}.00${receipt.ThisMonthPayout.PayoutTotal}.00
"; + + var emailRequest = new EmailRequest + { + Recipient = recipients, + Subject = $"Users terminated in Dayforce", + Body = body + }; + + await _notificationsDao.SendMessageAsync(emailRequest); + } + catch (Exception ex) + { + Log.Error(ex, "Failed to send terminated employee notification"); + } + } + + private string FormatReceiptItems(List receiptErrors) + { + string orderedList = @"
    "; + foreach (var error in receiptErrors) + { + orderedList += $@"
  1. {error.EmployeeName} - {error.EmployeeId}: {error.Message}

  2. "; + } + orderedList += @"
"; + return orderedList; + } + + public async Task SendTransferRequestInitiatedNotification(ActivityTransferAction transferAction, + List recipients) + { + try + { + #region Body HTML Boilerplate + + string body = + @""; + + #endregion + + body += @" +

A transfer request has been initiated for the following activity:

+ + + + + + + + + "; + + body += $@" + + + + + + + + +

Product

Activity Type

Requested by

Transfer to Employee

Transfer to Department

{transferAction.RequestedActivity.Product}{transferAction.RequestedActivity.Type}{transferAction.CreatedBy}{transferAction.NewActivityEmployeeId}{transferAction.NewActivityDepartmentId}
"; + var webAppUrl = $@"{_incentiveApiConfig.IncentiveManagerClientUrl}/activity/{transferAction.RequestedActivityId}"; + body += $@" +

+ The request can be approved at {webAppUrl}. +

"; + + if (_emailRecipientsConfig.OverrideRecipients) + { + body += $"

The original recipients have been overriden in this environment: {string.Join(", ", recipients)}

"; + recipients = _emailRecipientsConfig.TransferRequestInitiatedNotification; + } + + var emailRequest = new EmailRequest + { + Recipient = recipients, + Subject = "Activity transfer request initiated", + Body = body + }; + + await _notificationsDao.SendMessageAsync(emailRequest); + } + catch (Exception ex) + { + Log.Error(ex, "Failed to send transfer request initiated notification"); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyDecisionServices/AccountActivityDecisionService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyDecisionServices/AccountActivityDecisionService.cs new file mode 100644 index 0000000..561f6d8 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyDecisionServices/AccountActivityDecisionService.cs @@ -0,0 +1,66 @@ +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Results; +using LCU.Library.Internal.LCUServiceFactory.Configurations; +using Microsoft.Extensions.DependencyInjection; +using LCU.API.Internal.Operations.IncentiveApi.Services.NightlyIncentiveProcesses; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.NightlyDecisionServices; + +public class AccountActivityDecisionService : NightlyIncentiveProcessesService, INightlyIncentiveProcessesService +{ + private readonly IServiceScopeFactory _serviceScopeFactory; + private readonly ProcessResult _processResult; + + public AccountActivityDecisionService(ServicesFactoryConfiguration servicesFactoryConfiguration, + IServiceScopeFactory serviceScopeFactory) + : base(servicesFactoryConfiguration) + { + _serviceScopeFactory = serviceScopeFactory; + _processResult = new ProcessResult(); + } + + public async Task RunProductDecisionProcess(NightlyIncentiveProcessRequest request) + { + try + { + var result = await UpdateAndDecideActivities(); + var message = $"{result.Count()} activities were updated"; + Log.Information(message); + _processResult.MessageStack.Add(message); + } + catch (Exception exception) + { + var message = $"There was an issue with the Account Update process. Exception Message: {exception.Message}."; + _processResult.MessageStack.Add(message); + _processResult.Success = false; + } + return _processResult; + } + + public async Task> UpdateAndDecideActivities() + { + var listChanges = new List(); + using var scope = _serviceScopeFactory.CreateScope(); + + var changes = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().DecisionActivityByState(ActivityState.Created)); + Log.Information($"Auto-decisioned {changes.Count} activities in Created state."); + listChanges.AddRange(changes.ToList()); + + changes = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().DecisionActivityByState(ActivityState.ManualReview)); + Log.Information($"Auto-decisioned {changes.Count} activities in Manual Review state."); + listChanges.AddRange(changes.ToList()); + + changes = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().UpdateAndDecideManualReviewActivities()); + Log.Information($"Updated and Auto-Decisioned {changes.Count} Activities."); + listChanges.AddRange(changes.ToList()); + + changes = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().UpdateAndDecideDpEligibleLoanActivities()); + Log.Information($"Updated and Auto-Decisioned {changes.Count} Debt Protection Eligible Loan Activities."); + listChanges.AddRange(changes.ToList()); + + return listChanges; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyDecisionServices/DirectDepositActivityDecisionService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyDecisionServices/DirectDepositActivityDecisionService.cs new file mode 100644 index 0000000..4a05229 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyDecisionServices/DirectDepositActivityDecisionService.cs @@ -0,0 +1,115 @@ +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Results; +using LCU.Library.Internal.LCUServiceFactory.Configurations; +using Microsoft.Extensions.DependencyInjection; +using LCU.API.Internal.Operations.IncentiveApi.Services.NightlyIncentiveProcesses; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.NightlyDecisionServices; + +public class DirectDepositActivityDecisionService : NightlyIncentiveProcessesService, INightlyIncentiveProcessesService +{ + private readonly IServiceScopeFactory _serviceScopeFactory; + private readonly ProcessResult _processResult; + private const int PageItemLimit = 1000; + private static readonly List TransactionCodes = new List { "XDEP" }; + + public DirectDepositActivityDecisionService(ServicesFactoryConfiguration servicesFactoryConfiguration, + IServiceScopeFactory serviceScopeFactory) + : base(servicesFactoryConfiguration) + { + _serviceScopeFactory = serviceScopeFactory; + _processResult = new ProcessResult(); + } + + public async Task RunProductDecisionProcess(NightlyIncentiveProcessRequest request) + { + try + { + var result = await UpdateAndDecideActivities(); + var message = $"{result.Count()} Direct Deposit activities were updated"; + Log.Information(message); + _processResult.MessageStack.Add(message); + } + catch (Exception exception) + { + var message = $"There was an issue with the Direct Deposit Update Process. Exception Message: {exception.Message}."; + _processResult.MessageStack.Add(message); + _processResult.Success = false; + } + return _processResult; + } + + public async Task> UpdateAndDecideActivities() + { + using var scope = _serviceScopeFactory.CreateScope(); + + var activityPage = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().ListByFilterAsync(new ActivityFilter + { + Limit = PageItemLimit, + Page = 1, + State = new List { ActivityState.PendingReferral }, + Product = new List { Product.DirectDeposit }, + Type = new List { ActivityType.Referral } + })); + + if (activityPage?.Items == null) + { + return new List(); + } + + if (activityPage.Total > PageItemLimit) + { + Log.Warning($"More than {PageItemLimit} items to process in the Update And Decide Direct Deposit Pending Referrals!"); + } + + var activities = activityPage.Items.ToList(); + var currentDateTime = DateTime.Now; + var changes = new List(); + + foreach (var activity in activities) + { + try + { + var activityHistory = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().GetTransactionHistory(activity.Detail.AccountNumber, activity.Created, currentDateTime, TransactionCodes)); + + if (activityHistory == null) + { + var decision = new ActivityStateChange + { + ActivityId = activity.Id, + State = ActivityState.Rejected, + Note = "No Account Was Found With The Given Account Number", + AcceptedDate = activity.Accepted.Date.AddHours(12) + }; + + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().ApplyStateChange(decision)); + changes.Add(decision); + } + + if (activityHistory != null && activityHistory.Any()) + { + var decision = new ActivityStateChange + { + ActivityId = activity.Id, + State = ActivityState.Accepted, + Note = "Account Had At Least One External Deposit Transaction", + AcceptedDate = activity.Accepted.Date.AddHours(12) + }; + + await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().ApplyStateChange(decision)); + changes.Add(decision); + } + } + catch (Exception ex) + { + Log.Error(ex, $"Failed to save direct deposit referral state as accepted for activity:{activity.Id}"); + } + } + + return changes; + } +} + diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyDecisionServices/EStatementReferralActivityDecisionService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyDecisionServices/EStatementReferralActivityDecisionService.cs new file mode 100644 index 0000000..8936851 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyDecisionServices/EStatementReferralActivityDecisionService.cs @@ -0,0 +1,48 @@ +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Results; +using LCU.Library.Internal.LCUServiceFactory.Configurations; +using Microsoft.Extensions.DependencyInjection; +using LCU.API.Internal.Operations.IncentiveApi.Services.NightlyIncentiveProcesses; +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.NightlyDecisionServices; + +public class EStatementReferralActivityDecisionService : NightlyIncentiveProcessesService, INightlyIncentiveProcessesService +{ + private readonly IServiceScopeFactory _serviceScopeFactory; + private readonly ProcessResult _processResult; + + public EStatementReferralActivityDecisionService(ServicesFactoryConfiguration servicesFactoryConfiguration, + IServiceScopeFactory serviceScopeFactory) + : base(servicesFactoryConfiguration) + { + _serviceScopeFactory = serviceScopeFactory; + _processResult = new ProcessResult(); + } + + public async Task RunProductDecisionProcess(NightlyIncentiveProcessRequest request) + { + try + { + var result = await UpdateAndDecideActivities(); + var message = $"{result.Count()} E-Statement Referrals were updated"; + Log.Information(message); + _processResult.MessageStack.Add(message); + } + catch (Exception exception) + { + var message = $"There was an issue with the E-Statement Referral Update process. Exception Message: {exception.Message}."; + _processResult.MessageStack.Add(message); + _processResult.Success = false; + } + return _processResult; + } + + public async Task> UpdateAndDecideActivities() + { + using var scope = _serviceScopeFactory.CreateScope(); + var changes = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().UpdateOrExpireEStatementReferral()); + + return changes; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyDecisionServices/INightlyActivityDecisionService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyDecisionServices/INightlyActivityDecisionService.cs new file mode 100644 index 0000000..4f9fb03 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyDecisionServices/INightlyActivityDecisionService.cs @@ -0,0 +1,8 @@ +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.NightlyDecisionServices; + +public interface INightlyActivityDecisionService +{ + Task HandleProcessRequest(NightlyIncentiveProcessRequest request); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyDecisionServices/LICActivityDecisionService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyDecisionServices/LICActivityDecisionService.cs new file mode 100644 index 0000000..f5fc40f --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyDecisionServices/LICActivityDecisionService.cs @@ -0,0 +1,86 @@ +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Results; +using LCU.Library.Internal.LCUServiceFactory.Configurations; +using Microsoft.Extensions.DependencyInjection; +using LCU.API.Internal.Operations.IncentiveApi.Services.NightlyIncentiveProcesses; +using LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.NightlyDecisionServices; + +public class LICActivityDecisionService : NightlyIncentiveProcessesService, INightlyIncentiveProcessesService +{ + private readonly IServiceScopeFactory _serviceScopeFactory; + private readonly ProcessResult _processResult; + private const int PageItemLimit = 1000; + + public LICActivityDecisionService(ServicesFactoryConfiguration servicesFactoryConfiguration, + IServiceScopeFactory serviceScopeFactory) + : base(servicesFactoryConfiguration) + { + _serviceScopeFactory = serviceScopeFactory; + _processResult = new ProcessResult(); + } + + public async Task RunProductDecisionProcess(NightlyIncentiveProcessRequest request) + { + try + { + var result = await UpdateAndDecideActivities(); + var message = $"{result.Count()} LIC activities were updated"; + Log.Information(message); + _processResult.MessageStack.Add(message); + } + catch (Exception exception) + { + var message = $"There was an issue with the LIC Product Update process. Exception Message: {exception.Message}."; + _processResult.MessageStack.Add(message); + _processResult.Success = false; + } + return _processResult; + } + + public async Task> UpdateAndDecideActivities() + { + using var scope = _serviceScopeFactory.CreateScope(); + var changes = new List(); + var allItemsProcessed = false; + var page = 0; + var processedItemCount = 0; + + while (!allItemsProcessed) + { + try + { + var activityPage = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().ListByFilterAsync(new ActivityFilter + { + Limit = PageItemLimit, + Page = page += 1, + State = new List { ActivityState.ManualReview }, + Product = new List { Product.LIC } + })); + + if (activityPage?.Items == null) + { + return new List(); + } + + var activities = activityPage.Items.ToList(); + var updatedActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().TryUpdateLicReferralStationDetail(activities)); + + changes.AddRange(await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().AutoDecideActivities(updatedActivities))); + processedItemCount += activities.Count; + allItemsProcessed = processedItemCount == activityPage.Total; + } + catch (Exception ex) + { + Log.Error(ex, $"Failed to process all LIC Referral updates"); + allItemsProcessed = true; + } + } + + return changes; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyDecisionServices/NightlyActivityDecisionService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyDecisionServices/NightlyActivityDecisionService.cs new file mode 100644 index 0000000..7b95e86 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyDecisionServices/NightlyActivityDecisionService.cs @@ -0,0 +1,46 @@ +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Results; +using LCU.Library.Internal.LCUServiceFactory.Services; +using LCU.API.Internal.Operations.IncentiveApi.Services.NightlyIncentiveProcesses; +using LCU.Library.Internal.ApiResponse.Services; +using LCU.Library.Internal.Exceptions; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.NightlyDecisionServices; + +public class NightlyActivityDecisionService : INightlyActivityDecisionService +{ + private ProcessResult _processResult; + private INightlyIncentiveProcessesService _nightlyIncentiveProcessesService; + private readonly ILcuServiceFactory _lcuServiceFactory; + + public NightlyActivityDecisionService(ILcuServiceFactory lcuServiceFactory) + { + _nightlyIncentiveProcessesService = null; + _lcuServiceFactory = lcuServiceFactory; + _processResult = new ProcessResult(); + } + + public async Task HandleProcessRequest(NightlyIncentiveProcessRequest request) + { + try + { + ArgumentNullException.ThrowIfNull(request); + _nightlyIncentiveProcessesService = _lcuServiceFactory.GetService(request.NightlyIncentiveProcessType); + ArgumentNullException.ThrowIfNull(_nightlyIncentiveProcessesService); + + var message = $"Queued Request for {request.NightlyIncentiveProcessType}"; + Log.Information(message); + + _processResult = await _nightlyIncentiveProcessesService.RunProductDecisionProcess(request); + await QueueReturnResponse.QueueResponse(request, _processResult.Success, _processResult.MessageStack); + } + catch (Exception exception) + { + var message = $"There was an issue with handling the Daily Decision Process Exception Message: {exception.Message}."; + _processResult.MessageStack.Add(message); + _processResult.Success = false; + await QueueReturnResponse.QueueResponse(request, _processResult.Success, _processResult.MessageStack); + throw new ServiceException(message); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyDecisionServices/ReferralActivityDecisionService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyDecisionServices/ReferralActivityDecisionService.cs new file mode 100644 index 0000000..bdb355d --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyDecisionServices/ReferralActivityDecisionService.cs @@ -0,0 +1,48 @@ +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Results; +using LCU.Library.Internal.LCUServiceFactory.Configurations; +using Microsoft.Extensions.DependencyInjection; +using LCU.API.Internal.Operations.IncentiveApi.Services.NightlyIncentiveProcesses; +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.NightlyDecisionServices; + +public class ReferralActivityDecisionService : NightlyIncentiveProcessesService, INightlyIncentiveProcessesService +{ + private readonly IServiceScopeFactory _serviceScopeFactory; + private readonly ProcessResult _processResult; + + public ReferralActivityDecisionService(ServicesFactoryConfiguration servicesFactoryConfiguration, + IServiceScopeFactory serviceScopeFactory) + : base(servicesFactoryConfiguration) + { + _serviceScopeFactory = serviceScopeFactory; + _processResult = new ProcessResult(); + } + + public async Task RunProductDecisionProcess(NightlyIncentiveProcessRequest request) + { + try + { + var result = await UpdateAndDecideActivities(); + var message = $"{result.Count()} Referrals were updated"; + Log.Information(message); + _processResult.MessageStack.Add(message); + } + catch (Exception exception) + { + var message = $"There was an issue with the Referral Update process. Exception Message: {exception.Message}."; + _processResult.MessageStack.Add(message); + _processResult.Success = false; + } + return _processResult; + } + + public async Task> UpdateAndDecideActivities() + { + using var scope = _serviceScopeFactory.CreateScope(); + var changes = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().MatchOrExpireReferrals()); + + return changes; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyIncentiveProcesses/INightlyIncentiveProcessesService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyIncentiveProcesses/INightlyIncentiveProcessesService.cs new file mode 100644 index 0000000..d7d6277 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyIncentiveProcesses/INightlyIncentiveProcessesService.cs @@ -0,0 +1,13 @@ +using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Results; +using LCU.Library.Internal.LCUServiceFactory.Services; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.NightlyIncentiveProcesses; + +public interface INightlyIncentiveProcessesService : ILcuService +{ + Task RunProductDecisionProcess(NightlyIncentiveProcessRequest request); + Task> UpdateAndDecideActivities(); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyIncentiveProcesses/NightlyIncentiveProcessesService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyIncentiveProcesses/NightlyIncentiveProcessesService.cs new file mode 100644 index 0000000..199f216 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/NightlyIncentiveProcesses/NightlyIncentiveProcessesService.cs @@ -0,0 +1,13 @@ +using LCU.Library.Internal.LCUServiceFactory.Configurations; +using LCU.Library.Internal.LCUServiceFactory.Services; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.NightlyIncentiveProcesses; + +public abstract class NightlyIncentiveProcessesService : LcuAbstractService +{ + public NightlyIncentiveProcessesService(ServicesFactoryConfiguration servicesFactoryConfiguration) + : base(servicesFactoryConfiguration.GetServiceFactoryRegistrationByInterface()) + { + } +} + diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Payout/DefaultPayoutStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Payout/DefaultPayoutStrategy.cs new file mode 100644 index 0000000..4a75532 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Payout/DefaultPayoutStrategy.cs @@ -0,0 +1,122 @@ +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Models.Payout; +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Payout; + +public class DefaultPayoutStrategy : IPayoutStrategy +{ + public bool CanPayoutProduct(Product product) + { + return product != Product.GAPInsurance; + } + + public IReadOnlyCollection CalculateLineItems( + DepartmentPayout payoutPlan, + UserDepartment userDepartment, + List departmentPayoutProfiles, + ProductDetail product + ) + { + var lineItems = new List(); + + var eligiblePayoutProfiles = departmentPayoutProfiles.Where(p => p.Product == product.Product); + + foreach (var eligiblePayoutProfile in eligiblePayoutProfiles.Where(x => x.Type == PayoutType.Personal)) + { + var eligibleGoal = payoutPlan.PersonalGoals.FirstOrDefault(g => g.Product == product.Product); + if (eligibleGoal == null) continue; + + var relatedActivity = payoutPlan.PersonalActivity + .Where(a => a.DepartmentId == userDepartment.DepartmentId && + a.Product == product.Product) + .ToList(); + + if (!relatedActivity.Any()) continue; + + var eligibleActivity = relatedActivity + .Where(a => a.State == ActivityState.Accepted + && !payoutPlan.PersonalRules.Any(x => x.RestrictedByPayoutRule(a, product.Product))); + + var lineItem = new PayoutLineItem(product.Product, eligiblePayoutProfile, eligibleGoal, + userDepartment.SlidingPayout, eligibleActivity, payoutPlan.MonthRange.StartTime) + { + ActivityPeriod = payoutPlan.MonthRange.StartTime, + AppUserId = userDepartment.Employee.Id, + EmployeeId = userDepartment.Employee.EmployeeId, + DepartmentId = userDepartment.DepartmentId, + Role = userDepartment.Role + }; + lineItem.PayoutTotal = CalculateLineItemPayout(eligiblePayoutProfile, lineItem.GoalValue, lineItem.ActivityTotal, + payoutPlan.MonthRange.StartTime, lineItem.SlidingPayout); + + if (lineItem.PayoutTotal > 0) + { + lineItems.Add(lineItem); + } + } + + foreach (var eligiblePayoutProfile in eligiblePayoutProfiles.Where(x => x.Type == PayoutType.Department)) + { + var eligibleGoal = payoutPlan.DepartmentGoals.FirstOrDefault(g => g.Product == product.Product); + if (eligibleGoal == null) continue; + + var relatedActivity = payoutPlan.DepartmentActivity + .Where(a => a.Product == product.Product) + .ToList(); + + var eligibleActivity = relatedActivity + .Where(a => a.State == ActivityState.Accepted + && !payoutPlan.DepartmentRules.Any(x => x.RestrictedByPayoutRule(a, product.Product))); + + var lineItem = new PayoutLineItem(product.Product, eligiblePayoutProfile, eligibleGoal, + userDepartment.SlidingPayout, eligibleActivity, payoutPlan.MonthRange.StartTime) + { + ActivityPeriod = payoutPlan.MonthRange.StartTime, + AppUserId = userDepartment.Employee.Id, + EmployeeId = userDepartment.Employee.EmployeeId, + DepartmentId = userDepartment.DepartmentId, + Role = userDepartment.Role + }; + lineItem.PayoutTotal = CalculateLineItemPayout(eligiblePayoutProfile, lineItem.GoalValue, lineItem.ActivityTotal, + payoutPlan.MonthRange.StartTime, lineItem.SlidingPayout); + + if (lineItem.PayoutTotal > 0) + { + lineItems.Add(lineItem); + } + } + + return lineItems; + } + + public decimal CalculateLineItemPayout(PayoutProfile profile, int goal, decimal actual, DateTime runDate, decimal payoutPercent = 100) + { + if (profile == null) + { + throw new Exception("Attempt to calculate payout with null profile"); + } + + if (goal == 0) return 0; + + var goalPercentage = (double)actual / goal; + var payoutRange = profile.FindPayoutRange(goalPercentage, runDate); + + if (payoutRange == null) + { + return 0; + } + + decimal payoutAmt = payoutRange.Payout; + if (profile.PayEach) + { + payoutAmt *= actual; + } + + double payoutPercentage = (double)payoutPercent / (double)100; + double finalPayout = (double)payoutAmt * payoutPercentage; + payoutAmt = (decimal)Math.Round(finalPayout, 2); + + return payoutAmt; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Payout/GapPayoutStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Payout/GapPayoutStrategy.cs new file mode 100644 index 0000000..2e8ec80 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Payout/GapPayoutStrategy.cs @@ -0,0 +1,103 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Models.Payout; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Payout; + +public class GapPayoutStrategy : IPayoutStrategy +{ + private const bool IsEnabled = false; + + public bool CanPayoutProduct(Product product) + { + return product == Product.GAPInsurance && IsEnabled; + } + + public IReadOnlyCollection CalculateLineItems( + DepartmentPayout payoutPlan, + UserDepartment userDepartment, + List departmentPayouts, + ProductDetail product + ) + { + var lineItems = new List(); + + if (!payoutPlan.Department.IsHome) return lineItems; + + var eligiblePayouts = departmentPayouts.Where(p => p.Product == product.Product); + + if (eligiblePayouts.Any(x => x.Type == PayoutType.Department)) + { + throw new Exception("GAP Payouts configured for personal goals only, if you have enable department payouts you must add payout logic."); + } + + foreach (var eligiblePayoutProfile in eligiblePayouts.Where(x => x.Type == PayoutType.Personal)) + { + var eligibleGoal = payoutPlan.PersonalActivity + .Where(a => a.Detail is { IsDebtProtectionEligible: true } && a.State == ActivityState.Accepted) + .ToList(); + + if (!eligibleGoal.Any()) continue; + + var gapActivity = payoutPlan.PersonalActivity + .Where(a => a.Product == product.Product + && a.State == ActivityState.Accepted + && !payoutPlan.PersonalRules.Any(x => x.RestrictedByPayoutRule(a, product.Product)) + ) + .ToList(); + + var goalCount = eligibleGoal.Count; + var actualCount = eligibleGoal.Count(a => a.Detail.DebtProtectionAddDate != null && a.Detail.DebtProtectionRemoveDate == null); + + gapActivity.AddRange(eligibleGoal); + var lineItem = new PayoutLineItem(product.Product, eligiblePayoutProfile, eligibleGoal.Count, + userDepartment.SlidingPayout, gapActivity) + { + ActivityPeriod = payoutPlan.MonthRange.StartTime, + AppUserId = userDepartment.Employee.Id, + EmployeeId = userDepartment.Employee.EmployeeId, + DepartmentId = userDepartment.DepartmentId, + Role = userDepartment.Role + }; + lineItem.PayoutTotal = CalculateLineItemPayout(eligiblePayoutProfile, goalCount, actualCount, lineItem.ActivityTotal, + payoutPlan.MonthRange.StartTime, lineItem.SlidingPayout); + + if (lineItem.PayoutTotal > 0) + { + lineItems.Add(lineItem); + } + } + + return lineItems; + } + + private decimal CalculateLineItemPayout(PayoutProfile profile, int goal, decimal actual, decimal each, DateTime runDate, decimal payoutPercent = 100) + { + if (profile == null) + { + throw new Exception("Attempt to calculate payout with null profile"); + } + + if (goal == 0) return 0; + + var goalPercentage = (double)actual / goal; + var payoutRange = profile.FindPayoutRange(goalPercentage, runDate); + + if (payoutRange == null) + { + return 0; + } + + decimal payoutAmt = payoutRange.Payout; + if (profile.PayEach) + { + payoutAmt *= each; + } + + var payoutPercentage = (double)payoutPercent / 100; + var finalPayout = (double)payoutAmt * payoutPercentage; + payoutAmt = (decimal)Math.Round(finalPayout, 2); + + return payoutAmt; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Payout/IPayoutCalculateService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Payout/IPayoutCalculateService.cs new file mode 100644 index 0000000..da1e933 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Payout/IPayoutCalculateService.cs @@ -0,0 +1,12 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApi.Models.Payout; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Payout; + +public interface IPayoutCalculateService +{ + Task BuildDepartmentPayoutView(AppUser employee, UserDepartment userDepartment, MonthRange monthRange); + List CalculateGoalLineItems(DepartmentPayout payoutPlan, UserDepartment userDepartment); + List CalculateCommissionLineItems(DepartmentPayout payoutPlan, UserDepartment userDepartment); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Payout/IPayoutService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Payout/IPayoutService.cs new file mode 100644 index 0000000..9700c2d --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Payout/IPayoutService.cs @@ -0,0 +1,22 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; +using LCU.API.Internal.Operations.IncentiveApi.Models.Payout; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Payout +{ + public interface IPayoutService + { + Task> BuildUserDepartmentsPayoutView(AppUser employee, MonthRange range); + Task RunMonthlyIncentivePayouts(PayoutIncentiveRequest request); + Task> CreateMonthlyIncentivePayouts(PayoutIncentiveRequest request); + Task> RetrieveMonthlyIncentivePayouts(DateTime period); + Task RetrieveUserMonthlyIncentivePayout(AppUser appUser, DateTime period); + Task> RetrieveCombinedDepartmentPayouts(AppUser incentiveAppUser, MonthRange range); + Task> CreateCombinedDepartmentPayouts(AppUser incentiveAppUser, MonthRange range); + Task CreateTerminatedEmployeePayouts(AppUser appUser, AppUser manager); + Task CreateMonthlyAccountingPayouts(MonthRange range); + Task RetrieveMonthlyAccountingPayouts(MonthRange range); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Payout/IPayoutStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Payout/IPayoutStrategy.cs new file mode 100644 index 0000000..bf27962 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Payout/IPayoutStrategy.cs @@ -0,0 +1,17 @@ +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Models.Payout; +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Payout; + +public interface IPayoutStrategy +{ + bool CanPayoutProduct(Product product); + + IReadOnlyCollection CalculateLineItems( + DepartmentPayout payoutPlan, + UserDepartment userDepartment, + List departmentPayouts, + ProductDetail product + ); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Payout/PayoutCalculateService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Payout/PayoutCalculateService.cs new file mode 100644 index 0000000..9dc3ccf --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Payout/PayoutCalculateService.cs @@ -0,0 +1,138 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Services.Rules; +using LCU.API.Internal.Operations.IncentiveApi.Models.Payout; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Payout; + +public class PayoutCalculateService : IPayoutCalculateService +{ + private readonly IActivityRepository _activityRepository; + private readonly IGoalRepository _goalRepository; + private readonly ICommissionRepository _commissionRepository; + private readonly IEnumerable _payoutStrategies; + private readonly IBusinessRuleService _businessRuleService; + + public PayoutCalculateService( + IActivityRepository activityRepository, + IGoalRepository goalRepository, + ICommissionRepository commissionRepository, + IEnumerable payoutStrategies, + IBusinessRuleService businessRuleService + ) + { + _activityRepository = activityRepository; + _goalRepository = goalRepository; + _commissionRepository = commissionRepository; + _payoutStrategies = payoutStrategies; + _businessRuleService = businessRuleService; + } + + public async Task BuildDepartmentPayoutView( + AppUser employee, + UserDepartment userDepartment, + MonthRange monthRange + ) + { + var payoutPlan = new DepartmentPayout(employee, userDepartment, monthRange, + _businessRuleService.GetRules(PayoutType.Department), + _businessRuleService.GetRules(PayoutType.Personal)) + { + EligibleCommissions = await _commissionRepository.FindProductCommissions(userDepartment.Role) + }; + + if (!payoutPlan.HasEligibleIncentives()) + { + return payoutPlan; + } + + payoutPlan.PersonalActivity = + await _activityRepository.FindEmployeeActivityByMonthRange(payoutPlan.Employee, payoutPlan.MonthRange); + + payoutPlan.DepartmentActivity = + await _activityRepository.FindDepartmentActivityByMonthRange(userDepartment.DepartmentId, + payoutPlan.MonthRange); + + if (!payoutPlan.HasEligibleActivity()) + { + return payoutPlan; + } + + var users = new List() + { + payoutPlan.Employee + }; + + payoutPlan.PersonalGoals = await _goalRepository.FindPersonnelGoalsByMonthRange(userDepartment.DepartmentId, + users, payoutPlan.MonthRange); + + payoutPlan.DepartmentGoals = await _goalRepository.FindDepartmentGoalsByMonthRange( + userDepartment.DepartmentId, + payoutPlan.MonthRange); + + if (payoutPlan.HasEligibleCommissions()) + { + payoutPlan.CommissionPayouts = CalculateCommissionLineItems(payoutPlan, userDepartment); + } + + payoutPlan.GoalPayouts = CalculateGoalLineItems(payoutPlan, userDepartment); + + return payoutPlan; + } + + public List CalculateCommissionLineItems(DepartmentPayout payoutPlan, UserDepartment userDepartment) + { + var lineItems = new List(); + var departmentCommissions = payoutPlan.EligibleCommissions; + + if (departmentCommissions == null || !departmentCommissions.Any()) return lineItems; + + foreach (var product in departmentCommissions.Select(x => x.Product).Distinct().ToList()) + { + var eligibleCommission = departmentCommissions.FirstOrDefault(c => c.Product == product); + if (eligibleCommission == null) continue; + var eligibleActivity = payoutPlan.PersonalActivity + .Where(a => a.DepartmentId == userDepartment.DepartmentId && + a.Product == product && + a.Type != ActivityType.Other + && a.State == ActivityState.Accepted + && !payoutPlan.PersonalRules.Any(x => x.RestrictedByPayoutRule(a, product))) + .ToList(); + + var lineItem = new PayoutLineItem(product, eligibleCommission, eligibleActivity) + { + ActivityPeriod = payoutPlan.MonthRange.StartTime, + AppUserId = userDepartment.Employee.Id, + EmployeeId = userDepartment.Employee.EmployeeId, + DepartmentId = userDepartment.DepartmentId, + Role = userDepartment.Role + }; + + if (lineItem.PayoutTotal > 0) + { + lineItems.Add(lineItem); + } + } + + return lineItems; + } + + public List CalculateGoalLineItems(DepartmentPayout payoutPlan, UserDepartment userDepartment) + { + var lineItems = new List(); + var departmentPayouts = payoutPlan.EligiblePayouts.ToList(); + if (!departmentPayouts.Any()) return lineItems; + + foreach (var product in ProductDetail.ProductDetails) + { + var strategy = _payoutStrategies.FirstOrDefault(x => x.CanPayoutProduct(product.Product)); + if (strategy == null) continue; + var items = strategy.CalculateLineItems(payoutPlan, userDepartment, departmentPayouts, product); + lineItems.AddRange(items); + } + + return lineItems; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Payout/PayoutService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Payout/PayoutService.cs new file mode 100644 index 0000000..3af8ec9 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Payout/PayoutService.cs @@ -0,0 +1,243 @@ +using LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; +using LCU.API.Internal.Operations.IncentiveApi.Models.Payout; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.ApiResponse.Services; +using LCU.Library.Internal.Exceptions; +using ProcessResult = LCU.Library.Internal.IncentiveApiModels.Results.ProcessResult; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Payout; + +public class PayoutService : IPayoutService +{ + private readonly IUserRepository _userRepository; + private readonly IPayoutRepository _payoutRepository; + private readonly IAccountingRepository _accountingRepository; + private readonly IUnitOfWork _unitOfWork; + private readonly IPayoutCalculateService _payoutCalculateService; + private readonly IActivityService _activityService; + private readonly ProcessResult _payoutIncentiveProcessResult; + private const int MonthsBack = -2; + + public PayoutService(IUserRepository userRepository, IPayoutRepository payoutRepository, IUnitOfWork unitOfWork, IPayoutCalculateService payoutCalculateService, + IAccountingRepository accountingRepository, IActivityService activityService) + { + _userRepository = userRepository; + _payoutRepository = payoutRepository; + _unitOfWork = unitOfWork; + _payoutCalculateService = payoutCalculateService; + _accountingRepository = accountingRepository; + _activityService = activityService; + _payoutIncentiveProcessResult = new ProcessResult(); + } + + public async Task RunMonthlyIncentivePayouts(PayoutIncentiveRequest request) + { + try + { + Log.Information("Running Monthly Incentive Payout Process"); + var result = await CreateMonthlyIncentivePayouts(request); + + Log.Information("Successfully Processed Monthly Incentive Payouts."); + await QueueReturnResponse.QueueResponse(request, _payoutIncentiveProcessResult.Success, _payoutIncentiveProcessResult.MessageStack); + } + catch (Exception exception) + { + Log.Fatal(exception, "There was an Unhandled error with Monthly Incentive Payout Process"); + var message = $"There was an Unhandled error with Monthly Incentive Payout Process from {request.MonthsBack} months back from today's date. Exception Message: {exception.Message}."; + _payoutIncentiveProcessResult.MessageStack.Add(message); + _payoutIncentiveProcessResult.Success = false; + await QueueReturnResponse.QueueResponse(request, _payoutIncentiveProcessResult.Success, _payoutIncentiveProcessResult.MessageStack); + throw new ServiceException(message); + } + } + + public async Task> CreateMonthlyIncentivePayouts(PayoutIncentiveRequest request) + { + var period = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); + period = request.MonthsBack == 0 ? period.AddMonths(MonthsBack) : period.AddMonths(request.MonthsBack); + + var range = new MonthRange(period); + _activityService.CancelPendingTransfersBetween(range).GetAwaiter().GetResult(); + + var users = await _userRepository.GetAllUsersToPayoutAsync(range); + var allNewPayouts = new List(); + IReadOnlyCollection combinedPayouts; + + foreach (var incentiveUser in users) + { + try + { + combinedPayouts = await CreateCombinedDepartmentPayouts(incentiveUser, range); + if (!combinedPayouts.Any()) continue; + await _payoutRepository.SavePayoutLineItemRangeAsync(combinedPayouts); + allNewPayouts.AddRange(combinedPayouts); + } + catch (Exception ex) + { + Log.Fatal(ex, $"Employee {incentiveUser.EmployeeId} - {incentiveUser.FullName} experienced a failed payout calculation!"); + } + } + + await _unitOfWork.CompleteAsync(); + return allNewPayouts; + } + + public async Task CreateMonthlyAccountingPayouts(MonthRange range) + { + var existing = await RetrieveMonthlyAccountingPayouts(range); + if (existing != null) + { + return existing; + } + var users = await _userRepository.GetAllUsersToPayoutAsync(range); + var allNewPayouts = new List(); + var deptNames = new Dictionary(); + + foreach (var incentiveUser in users) + { + try + { + var departments = incentiveUser.ActiveDepartmentsForRange(range); + foreach (var userDepartment in departments) + { + if (!deptNames.ContainsKey(userDepartment.Department.Id.Value)) + { + deptNames.Add(userDepartment.Department.Id.Value, userDepartment.Department.DepartmentName); + } + + var newPlan = + await _payoutCalculateService.BuildDepartmentPayoutView(incentiveUser, userDepartment, + range); + allNewPayouts.AddRange(newPlan.CommissionPayouts); + allNewPayouts.AddRange(newPlan.GoalPayouts); + } + } + catch (Exception ex) + { + Log.Fatal(ex, $"Employee {incentiveUser.EmployeeId} - {incentiveUser.FullName} experienced a failed accounting calculation!"); + } + } + + var summary = new AccountingSummary(range.StartTime.Date); + summary.Apply(allNewPayouts, deptNames); + await _accountingRepository.SaveAccountingSummaryAsync(summary); + await _unitOfWork.CompleteAsync(); + return summary; + } + + public async Task RetrieveMonthlyAccountingPayouts(MonthRange range) + { + return await _accountingRepository.GetAccountingSummaryAsync(range.StartTime); + } + + public async Task> BuildUserDepartmentsPayoutView(AppUser employee, + MonthRange range) + { + var list = new List(); + var departments = employee.ActiveDepartmentsForRange(range); + + foreach (var userDepartment in departments) + { + var newPlan = await _payoutCalculateService.BuildDepartmentPayoutView(employee, userDepartment, range); + list.Add(newPlan); + } + + return list; + } + + public async Task> RetrieveMonthlyIncentivePayouts(DateTime period) + { + var payoutsRaw = await _payoutRepository.GetPayoutLineItemsForPeriodAsync(period); + var payouts = new Dictionary(); + foreach (var lineItem in payoutsRaw) + { + if (payouts.ContainsKey(lineItem.EmployeeId)) + { + payouts[lineItem.EmployeeId].PayoutTotal += lineItem.PayoutTotal; + } + else + { + var employee = await _userRepository.FindUserByEmployeeIdAsync(lineItem.EmployeeId); + payouts[lineItem.EmployeeId] = new PayoutEntry() + { + Code = "Incentive", + EmployeeId = lineItem.EmployeeId, + PayoutTotal = lineItem.PayoutTotal, + EmployeeName = employee.FullName + }; + } + } + + return payouts.Values; + } + + public async Task> CreateCombinedDepartmentPayouts(AppUser incentiveAppUser, MonthRange range) + { + var combinedPayouts = new List(); + var existing = await _payoutRepository.GetEmployeePayoutsForPeriod(incentiveAppUser, range.StartTime); + + if (existing.Any()) return combinedPayouts; + + var payouts = await BuildUserDepartmentsPayoutView(incentiveAppUser, range); + foreach (var departmentPayout in payouts) + { + combinedPayouts.AddRange(departmentPayout.CommissionPayouts); + combinedPayouts.AddRange(departmentPayout.GoalPayouts); + } + + return combinedPayouts; + } + + public async Task> RetrieveCombinedDepartmentPayouts(AppUser incentiveAppUser, MonthRange range) + { + var existing = await _payoutRepository.GetEmployeePayoutsForPeriod(incentiveAppUser, range.StartTime); + if (existing.Any()) return existing; + + return await CreateCombinedDepartmentPayouts(incentiveAppUser, range); + } + + public async Task RetrieveUserMonthlyIncentivePayout(AppUser appUser, DateTime period) + { + var payoutsRaw = await _payoutRepository.GetEmployeePayoutsForPeriod(appUser, period); + var payout = new PayoutEntry() + { + Code = "Incentive", + EmployeeId = appUser.EmployeeId, + PayoutTotal = 0, + EmployeeName = appUser.FullName + }; + + foreach (var lineItem in payoutsRaw) + { + payout.PayoutTotal += lineItem.PayoutTotal; + } + + return payout; + } + + public async Task CreateTerminatedEmployeePayouts(AppUser appUser, AppUser manager) + { + var firstOfThisMonth = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); + var rangeThisMonth = new MonthRange(firstOfThisMonth); + var rangeLastMonth = new MonthRange(firstOfThisMonth.AddMonths(-1)); + var payouts = await CreateCombinedDepartmentPayouts(appUser, rangeThisMonth); + if (payouts.Any()) + { + await _payoutRepository.SavePayoutLineItemRangeAsync(payouts); + } + + payouts = await CreateCombinedDepartmentPayouts(appUser, rangeLastMonth); + if (payouts.Any()) + { + await _payoutRepository.SavePayoutLineItemRangeAsync(payouts); + } + + await _unitOfWork.CompleteAsync(); + var payoutThisMonth = await RetrieveUserMonthlyIncentivePayout(appUser, rangeThisMonth.StartTime); + var payoutLastMonth = await RetrieveUserMonthlyIncentivePayout(appUser, rangeLastMonth.StartTime); + return new TerminatedEmployeeReceipt(appUser, payoutLastMonth, payoutThisMonth, manager); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ProcessorHistoryService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ProcessorHistoryService.cs new file mode 100644 index 0000000..bed6907 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/ProcessorHistoryService.cs @@ -0,0 +1,47 @@ +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public class ProcessorHistoryService : IProcessorHistoryService +{ + private readonly IProcessorHistoryRepository _processorHistoryRepository; + private readonly IUnitOfWork _unitOfWork; + + public ProcessorHistoryService(IProcessorHistoryRepository processorHistoryRepository, IUnitOfWork unitOfWork) + { + _processorHistoryRepository = processorHistoryRepository; + _unitOfWork = unitOfWork; + } + + public async Task GetProcessorHistory(Process process) + { + try + { + return await _processorHistoryRepository.GetProcessorRunHistory(process); + } + catch (Exception) + { + Log.Error( + $"Error in ProcessorHistoryService. GetProcessorRunHistory Failed to retrieve record for process: {process.ToString()}"); + throw; + } + } + + public async Task SaveProcessorHistory(ProcessorHistoryRecord historyRecord) + { + try + { + var process = await _processorHistoryRepository.SaveProcessorRunHistory(historyRecord); + await _unitOfWork.CompleteAsync(); + return process; + } + catch (Exception) + { + Log.Error( + $"Error in ProcessorHistoryService. SaveProcessorHistory Failed to retrieve record for process: {historyRecord.Process.ToString()}"); + throw; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueActionCreditCardServic.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueActionCreditCardServic.cs new file mode 100644 index 0000000..ecd5bab --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueActionCreditCardServic.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.QueueServices +{ + public interface IQueueActionCreditCardService + { + Task QueueActionCreditCardProcess(ActionCreditCardRequest request); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueActionVehicleService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueActionVehicleService.cs new file mode 100644 index 0000000..a6bdd98 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueActionVehicleService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.QueueServices +{ + public interface IQueueActionVehicleService + { + Task QueueActionVehicleProcess(ActionVehicleRequest request); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueAlliedGapMbpService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueAlliedGapMbpService.cs new file mode 100644 index 0000000..5c6ebf4 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueAlliedGapMbpService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.QueueServices +{ + public interface IQueueAlliedGapMbpService + { + Task QueueGapMbpProcess(AlliedGapMbpRequest request); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueCreditCardImportService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueCreditCardImportService.cs new file mode 100644 index 0000000..4d22756 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueCreditCardImportService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.QueueServices +{ + public interface IQueueCreditCardImportService + { + Task QueueCreditCardSalesImport(ImportCreditCardSalesRequest request); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueDayforceSyncService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueDayforceSyncService.cs new file mode 100644 index 0000000..208c517 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueDayforceSyncService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.QueueServices +{ + public interface IQueueDayforceSyncService + { + Task QueueDayforceSyncProcess(DayforceProcessRequest request); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueGoalDispersalService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueGoalDispersalService.cs new file mode 100644 index 0000000..e0e3128 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueGoalDispersalService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.QueueServices +{ + public interface IQueueGoalDispersalService + { + Task QueueGoalDispersalProcess(GoalDispersalRequest request); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueIncentiveProcessesService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueIncentiveProcessesService.cs new file mode 100644 index 0000000..80cc5c4 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueIncentiveProcessesService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.QueueServices +{ + public interface IQueueIncentiveProcessesService + { + Task QueueIncentiveProcess(IncentiveProcessRequest request); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueIndirectVehicleImportService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueIndirectVehicleImportService.cs new file mode 100644 index 0000000..a0ca351 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueIndirectVehicleImportService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.QueueServices +{ + public interface IQueueIndirectVehicleImportService + { + Task QueueIndirectVehicleSalesImport(ImportIndirectVehicleSalesRequest request); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueNightlyIncentiveProcessesService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueNightlyIncentiveProcessesService.cs new file mode 100644 index 0000000..41c9d2f --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueNightlyIncentiveProcessesService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.QueueServices +{ + public interface IQueueNightlyIncentiveProcessesService + { + Task QueueNightlyIncentiveProcess(NightlyIncentiveProcessRequest request); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueuePayoutIncentiveService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueuePayoutIncentiveService.cs new file mode 100644 index 0000000..ecb4733 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueuePayoutIncentiveService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.QueueServices +{ + public interface IQueuePayoutIncentiveService + { + Task QueuePayoutIncentiveProcess(PayoutIncentiveRequest request); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueReportingService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueReportingService.cs new file mode 100644 index 0000000..60943cd --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/IQueueReportingService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.QueueServices +{ + public interface IQueueReportingService + { + Task QueueReportingProcess(ReportingRequest request); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueActionCreditCardService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueActionCreditCardService.cs new file mode 100644 index 0000000..4aef953 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueActionCreditCardService.cs @@ -0,0 +1,36 @@ +using System.Threading; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using MassTransit; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.QueueServices +{ + public class QueueActionCreditCardService : IQueueActionCreditCardService + { + private readonly ISendEndpoint _sendEndpoint; + + public QueueActionCreditCardService(IBusControl busControl, ReceiveEndpointConfigurations receiveEndpointConfigurations) + { + _sendEndpoint = busControl.GetSendEndpoint(receiveEndpointConfigurations.ActionCreditCardProcessEndpoint.ReceiveEndpointUri).Result; + } + + public async Task QueueActionCreditCardProcess(ActionCreditCardRequest request) + { + try + { + var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + await _sendEndpoint.Send(request, cancellationTokenSource.Token); + } + catch (Exception exception) + { + var message = "Failed to Queue Action Credit Card Process."; + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, message); + throw new ServiceException(message); + } + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueActionVehicleService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueActionVehicleService.cs new file mode 100644 index 0000000..c7831e6 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueActionVehicleService.cs @@ -0,0 +1,36 @@ +using System.Threading; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using MassTransit; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.QueueServices +{ + public class QueueActionVehicleService : IQueueActionVehicleService + { + private readonly ISendEndpoint _sendEndpoint; + + public QueueActionVehicleService(IBusControl busControl, ReceiveEndpointConfigurations receiveEndpointConfigurations) + { + _sendEndpoint = busControl.GetSendEndpoint(receiveEndpointConfigurations.ActionVehicleProcessEndpoint.ReceiveEndpointUri).Result; + } + + public async Task QueueActionVehicleProcess(ActionVehicleRequest request) + { + try + { + var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + await _sendEndpoint.Send(request, cancellationTokenSource.Token); + } + catch (Exception exception) + { + var message = "Failed to Queue Action Vehicle Process."; + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, message); + throw new ServiceException(message); + } + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueAlliedGapMbpService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueAlliedGapMbpService.cs new file mode 100644 index 0000000..08a25dc --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueAlliedGapMbpService.cs @@ -0,0 +1,36 @@ +using System.Threading; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using MassTransit; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.QueueServices +{ + public class QueueAlliedGapMbpService : IQueueAlliedGapMbpService + { + private readonly ISendEndpoint _sendEndpoint; + + public QueueAlliedGapMbpService(IBusControl busControl, ReceiveEndpointConfigurations receiveEndpointConfigurations) + { + _sendEndpoint = busControl.GetSendEndpoint(receiveEndpointConfigurations.GapMbpProcessEndpoint.ReceiveEndpointUri).Result; + } + + public async Task QueueGapMbpProcess(AlliedGapMbpRequest request) + { + try + { + var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + await _sendEndpoint.Send(request, cancellationTokenSource.Token); + } + catch (Exception exception) + { + var message = "Failed to Queue Gap Mbp Process."; + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, message); + throw new ServiceException(message); + } + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueCreditCardImportService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueCreditCardImportService.cs new file mode 100644 index 0000000..3c1b585 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueCreditCardImportService.cs @@ -0,0 +1,37 @@ +using System.Threading; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using MassTransit; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.QueueServices +{ + + public class QueueCreditCardImportService : IQueueCreditCardImportService + { + private readonly ISendEndpoint _sendEndpoint; + + public QueueCreditCardImportService(IBusControl busControl, ReceiveEndpointConfigurations receiveEndpointConfigurations) + { + _sendEndpoint = busControl.GetSendEndpoint(receiveEndpointConfigurations.ImportCreditCardSalesEndpoint.ReceiveEndpointUri).Result; + } + + public async Task QueueCreditCardSalesImport(ImportCreditCardSalesRequest request) + { + try + { + var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + await _sendEndpoint.Send(request, cancellationTokenSource.Token); + } + catch (Exception exception) + { + var message = "Failed to Queue Credit Card Sales Import."; + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, message); + throw new ServiceException(message); + } + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueDayforceSyncService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueDayforceSyncService.cs new file mode 100644 index 0000000..98a2feb --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueDayforceSyncService.cs @@ -0,0 +1,36 @@ +using System.Threading; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using MassTransit; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.QueueServices +{ + public class QueueDayforceSyncService : IQueueDayforceSyncService + { + private readonly ISendEndpoint _sendEndpoint; + + public QueueDayforceSyncService(IBusControl busControl, ReceiveEndpointConfigurations receiveEndpointConfigurations) + { + _sendEndpoint = busControl.GetSendEndpoint(receiveEndpointConfigurations.DayforceSyncProcessEndpoint.ReceiveEndpointUri).Result; + } + + public async Task QueueDayforceSyncProcess(DayforceProcessRequest request) + { + try + { + var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + await _sendEndpoint.Send(request, cancellationTokenSource.Token); + } + catch (Exception exception) + { + var message = "Failed to Queue Dayforce Sync Process."; + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, message); + throw new ServiceException(message); + } + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueGoalDispersalService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueGoalDispersalService.cs new file mode 100644 index 0000000..2f4dba1 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueGoalDispersalService.cs @@ -0,0 +1,36 @@ +using System.Threading; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using MassTransit; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.QueueServices +{ + public class QueueGoalDispersalService : IQueueGoalDispersalService + { + private readonly ISendEndpoint _sendEndpoint; + + public QueueGoalDispersalService(IBusControl busControl, ReceiveEndpointConfigurations receiveEndpointConfigurations) + { + _sendEndpoint = busControl.GetSendEndpoint(receiveEndpointConfigurations.GoalDispersalProcessEndpoint.ReceiveEndpointUri).Result; + } + + public async Task QueueGoalDispersalProcess(GoalDispersalRequest request) + { + try + { + var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + await _sendEndpoint.Send(request, cancellationTokenSource.Token); + } + catch (Exception exception) + { + var message = "Failed to Queue Goal Dispersal Process."; + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, message); + throw new ServiceException(message); + } + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueIncentiveProcessesService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueIncentiveProcessesService.cs new file mode 100644 index 0000000..8791798 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueIncentiveProcessesService.cs @@ -0,0 +1,35 @@ +using System.Threading; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using MassTransit; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.QueueServices +{ + public class QueueIncentiveProcessesService : IQueueIncentiveProcessesService + { + private readonly ISendEndpoint _sendEndpoint; + + public QueueIncentiveProcessesService(IBusControl busControl, ReceiveEndpointConfigurations receiveEndpointConfigurations) + { + _sendEndpoint = busControl.GetSendEndpoint(receiveEndpointConfigurations.IncentiveProcessesEndpoint.ReceiveEndpointUri).Result; + } + + public async Task QueueIncentiveProcess(IncentiveProcessRequest request) + { + try + { + var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + await _sendEndpoint.Send(request, cancellationTokenSource.Token); + } + catch (Exception exception) + { + var message = "Failed to Queue Incentive Process."; + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, message); + throw new ServiceException(message); + } + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueIndirectVehicleImportService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueIndirectVehicleImportService.cs new file mode 100644 index 0000000..233f547 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueIndirectVehicleImportService.cs @@ -0,0 +1,37 @@ +using System.Threading; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using MassTransit; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.QueueServices +{ + public class QueueIndirectVehicleImportService : IQueueIndirectVehicleImportService + { + private readonly ISendEndpoint _sendEndpoint; + + public QueueIndirectVehicleImportService(IBusControl busControl, ReceiveEndpointConfigurations receiveEndpointConfigurations) + { + _sendEndpoint = busControl.GetSendEndpoint(receiveEndpointConfigurations.ImportIndirectVehicleSalesEndpoint.ReceiveEndpointUri).Result; + + } + + public async Task QueueIndirectVehicleSalesImport(ImportIndirectVehicleSalesRequest request) + { + try + { + var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + await _sendEndpoint.Send(request, cancellationTokenSource.Token); + } + catch (Exception exception) + { + var message = "Failed to Queue Indirect Vehicle Sales Import."; + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, message); + throw new ServiceException(message); + } + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueNightlyIncentiveProcessesService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueNightlyIncentiveProcessesService.cs new file mode 100644 index 0000000..6f52bd8 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueNightlyIncentiveProcessesService.cs @@ -0,0 +1,35 @@ +using System.Threading; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using MassTransit; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.QueueServices +{ + public class QueueNightlyIncentiveProcessesService : IQueueNightlyIncentiveProcessesService + { + private readonly ISendEndpoint _sendEndpoint; + + public QueueNightlyIncentiveProcessesService(IBusControl busControl, ReceiveEndpointConfigurations receiveEndpointConfigurations) + { + _sendEndpoint = busControl.GetSendEndpoint(receiveEndpointConfigurations.NightlyIncentiveProcessesEndpoint.ReceiveEndpointUri).Result; + } + + public async Task QueueNightlyIncentiveProcess(NightlyIncentiveProcessRequest request) + { + try + { + var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + await _sendEndpoint.Send(request, cancellationTokenSource.Token); + } + catch (Exception exception) + { + var message = "Failed to Queue Nightly Incentive Process."; + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, message); + throw new ServiceException(message); + } + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueuePayoutIncentiveService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueuePayoutIncentiveService.cs new file mode 100644 index 0000000..0dccdc7 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueuePayoutIncentiveService.cs @@ -0,0 +1,36 @@ +using System.Threading; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using MassTransit; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.QueueServices +{ + public class QueuePayoutIncentiveService : IQueuePayoutIncentiveService + { + private readonly ISendEndpoint _sendEndpoint; + + public QueuePayoutIncentiveService(IBusControl busControl, ReceiveEndpointConfigurations receiveEndpointConfigurations) + { + _sendEndpoint = busControl.GetSendEndpoint(receiveEndpointConfigurations.PayoutIncentiveProcessEndpoint.ReceiveEndpointUri).Result; + } + + public async Task QueuePayoutIncentiveProcess(PayoutIncentiveRequest request) + { + try + { + var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + await _sendEndpoint.Send(request, cancellationTokenSource.Token); + } + catch (Exception exception) + { + var message = "Failed to Queue Payout Incentive Process."; + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, message); + throw new ServiceException(message); + } + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueReportingService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueReportingService.cs new file mode 100644 index 0000000..e9a7c1f --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/QueueServices/QueueReportingService.cs @@ -0,0 +1,37 @@ +using System.Threading; +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using MassTransit; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.QueueServices +{ + + public class QueueReportingService : IQueueReportingService + { + private readonly ISendEndpoint _sendEndpoint; + + public QueueReportingService(IBusControl busControl, ReceiveEndpointConfigurations receiveEndpointConfigurations) + { + _sendEndpoint = busControl.GetSendEndpoint(receiveEndpointConfigurations.ReportingProcessEndpoint.ReceiveEndpointUri).Result; + } + + public async Task QueueReportingProcess(ReportingRequest request) + { + try + { + var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + await _sendEndpoint.Send(request, cancellationTokenSource.Token); + } + catch (Exception exception) + { + var message = "Failed to Queue Reporting Process."; + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, message); + throw new ServiceException(message); + } + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Reporting/IReportingService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Reporting/IReportingService.cs new file mode 100644 index 0000000..93f81a9 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Reporting/IReportingService.cs @@ -0,0 +1,8 @@ +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Reporting; + +public interface IReportingService +{ + Task HandleMonthlyReporting(ReportingRequest request); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Reporting/ReportingService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Reporting/ReportingService.cs new file mode 100644 index 0000000..3375444 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Reporting/ReportingService.cs @@ -0,0 +1,46 @@ +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.API.Internal.Operations.IncentiveApi.Services.Payout; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Results; +using LCU.Library.Internal.ApiResponse.Services; +using LCU.Library.Internal.Exceptions; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Reporting; + +public class ReportingService : IReportingService +{ + private readonly IUserRepository _userRepository; + private readonly IPayoutService _payoutService; + private readonly ProcessResult _reportingProcessResult; + + public ReportingService(IPayoutService payoutService, IUserRepository userRepository) + { + _payoutService = payoutService; + _userRepository = userRepository; + _reportingProcessResult = new ProcessResult(); + } + + public async Task HandleMonthlyReporting(ReportingRequest request) + { + try + { + var period = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); + period = request.MonthsBack == 0 ? period.AddMonths(-1) : period.AddMonths(request.MonthsBack); + + var range = new MonthRange(period); + _payoutService.CreateMonthlyAccountingPayouts(range).GetAwaiter().GetResult(); + Log.Information($"Reporting Service processed"); + _reportingProcessResult.MessageStack.Add($"Reporting Service Processed Month Starting {range.ToMonth}."); + await QueueReturnResponse.QueueResponse(request, _reportingProcessResult.Success, _reportingProcessResult.MessageStack); + } + catch (Exception exception) + { + var message = $"There was an issue with handling the Reporting Service Process Exception Message: {exception.Message}."; + _reportingProcessResult.MessageStack.Add(message); + _reportingProcessResult.Success = false; + await QueueReturnResponse.QueueResponse(request, _reportingProcessResult.Success, _reportingProcessResult.MessageStack); + throw new ServiceException(message); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Rules/BusinessRuleService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Rules/BusinessRuleService.cs new file mode 100644 index 0000000..7041f23 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Rules/BusinessRuleService.cs @@ -0,0 +1,32 @@ +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Models; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Rules; + +public class BusinessRuleService : IBusinessRuleService +{ + private readonly IDepartmentService _departmentService; + + public BusinessRuleService(IDepartmentService departmentService) + { + _departmentService = departmentService; + } + + public IReadOnlyCollection GetRules(PayoutType type) + { + var dept = _departmentService + .GetDepartmentsByDayforceGlCodeAsync(NewCheckingReferralRule.MemberServicesGlCode).GetAwaiter() + .GetResult(); + + return type switch + { + PayoutType.Department => new List() + { + new NewCheckingReferralRule(dept.Id.Value) + }, + PayoutType.Personal => new List() + { + } + }; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Rules/IBusinessRuleService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Rules/IBusinessRuleService.cs new file mode 100644 index 0000000..d52086e --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Rules/IBusinessRuleService.cs @@ -0,0 +1,10 @@ +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Models; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Rules +{ + public interface IBusinessRuleService + { + IReadOnlyCollection GetRules(PayoutType type); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Rules/NewCheckingReferralRule.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Rules/NewCheckingReferralRule.cs new file mode 100644 index 0000000..9f7d02a --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Rules/NewCheckingReferralRule.cs @@ -0,0 +1,26 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Models; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Rules; + +public class NewCheckingReferralRule : IPayoutRule +{ + public static readonly int MemberServicesGlCode = 200; + + public NewCheckingReferralRule(int memberServicesDepartmentId) + { + MemberServiceDeptId = memberServicesDepartmentId; + } + + private int MemberServiceDeptId { get; } + + public bool RestrictedByPayoutRule(Activity activity, Product countsForProduct) + { + var result = countsForProduct == activity.Product + && activity.Product == Product.NewChecking + && activity.Type == ActivityType.Referral + && activity.DepartmentId != MemberServiceDeptId; + return result; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/SettingsService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/SettingsService.cs new file mode 100644 index 0000000..31ea412 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/SettingsService.cs @@ -0,0 +1,70 @@ +using LCU.API.Internal.Operations.IncentiveApi.Configuration; +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApi.Models; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public class SettingsService : ISettingsService +{ + private readonly ReportConfiguration _reportConfig; + private readonly IRoleMappingRepository _roleMappingRepository; + private readonly IUnitOfWork _unitOfWork; + + public SettingsService( + ReportConfiguration reportConfig, + IRoleMappingRepository roleMappingRepository, + IUnitOfWork unitOfWork + ) + { + _reportConfig = reportConfig; + _roleMappingRepository = roleMappingRepository; + _unitOfWork = unitOfWork; + } + + public async Task SaveRoleMappingAsync(RoleMappingUpdateRequest roleMappingUpdateRequest) + { + var existingRoleMapping = await _roleMappingRepository.GetRoleMappingByIdAsync(roleMappingUpdateRequest.Id); + existingRoleMapping.ApplicationRole = Role.DetermineRoleOrDefault(roleMappingUpdateRequest.ApplicationRole); + await _roleMappingRepository.UpdateRoleMapping(existingRoleMapping); + await _unitOfWork.CompleteAsync(); + return existingRoleMapping; + } + + public async Task SaveRoleMappingRangeAsync(IEnumerable roleMapping) + { + await _roleMappingRepository.SaveRoleMappingRangeAsync(roleMapping.ToList()); + await _unitOfWork.CompleteAsync(); + } + + public async Task SaveNewUnknownRoleMappingRangeAsync(IEnumerable unknownRoles) + { + var existingBusinessRoles = await _roleMappingRepository.GetAllRoleMappingsAsync(); + var newRoles = unknownRoles.Where(x => existingBusinessRoles.All(e => !string.Equals(e.BusinessRole, x, StringComparison.InvariantCultureIgnoreCase))); + await _roleMappingRepository.SaveNewRoleMappingRangeAsync(newRoles.Select( + role => new RoleMapping { ApplicationRole = Role.MISSING_ROLE, BusinessRole = role }) + .ToList()); + await _unitOfWork.CompleteAsync(); + } + + public async Task> GetRoleMappingsAsync() + { + var roleMappings = await _roleMappingRepository.GetAllRoleMappingsAsync(); + return roleMappings ?? new List(); + } + + public async Task> GetAuthorizedReportSettings(AppUser appUser) + { + var authorizedReports = new List(); + foreach (var reportSettings in _reportConfig.ReportConfigs) + { + if (appUser.UserHasPermission(reportSettings.RequiredPermissionCode)) + { + authorizedReports.Add(reportSettings); + } + } + + return await Task.FromResult(authorizedReports); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Temenos/IImportCreditCardSalesService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Temenos/IImportCreditCardSalesService.cs new file mode 100644 index 0000000..f5cec45 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Temenos/IImportCreditCardSalesService.cs @@ -0,0 +1,8 @@ +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Temenos; + +public interface IImportCreditCardSalesService +{ + Task HandleImportCreditCardSales(ImportCreditCardSalesRequest message); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Temenos/IImportIndirectVehicleSalesService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Temenos/IImportIndirectVehicleSalesService.cs new file mode 100644 index 0000000..74e269b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Temenos/IImportIndirectVehicleSalesService.cs @@ -0,0 +1,8 @@ +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Temenos; + +public interface IImportIndirectVehicleSalesService +{ + Task HandleImportIndirectVehicleSales(ImportIndirectVehicleSalesRequest message); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Temenos/ImportCreditCardSalesService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Temenos/ImportCreditCardSalesService.cs new file mode 100644 index 0000000..f7a2503 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Temenos/ImportCreditCardSalesService.cs @@ -0,0 +1,178 @@ +using CsvHelper; +using CsvHelper.Configuration; +using System.Globalization; +using System.IO; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.API.Internal.Operations.IncentiveApi.Models.Temenos; +using LCU.Library.Internal.ApiResponse.Services; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Results; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Services.Transformations; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Temenos; + +public class ImportCreditCardSalesService : IImportCreditCardSalesService +{ + private readonly IUserRepository _userRepository; + private readonly IActivityService _activityService; + private readonly IFileOperationsApiService _fileOperationsApiService; + private readonly IDeadActivityService _deadActivityService; + private readonly ProcessResult _importCreditCardResult; + + public ImportCreditCardSalesService(IActivityService activityService, IUserRepository userRepository, + IFileOperationsApiService fileOperationsService, IDeadActivityService deadActivityService) + { + _activityService = activityService; + _userRepository = userRepository; + _fileOperationsApiService = fileOperationsService; + _deadActivityService = deadActivityService; + _importCreditCardResult = new ProcessResult(); + } + + public async Task HandleImportCreditCardSales(ImportCreditCardSalesRequest request) + { + try + { + var records = await GetCreditCardSales(request); + List allActivity = await CreateActivities(records); + + var personalCardCount = 0; + var businesCardCount = 0; + + if (allActivity.Any()) + { + await _activityService.SaveActivityRangeAsync(allActivity); + personalCardCount = records.Where(x => x.IsPersonal).Count(); + businesCardCount = records.Where(x => !x.IsPersonal).Count(); + } + + _importCreditCardResult.MessageStack.Add( $"Imported {personalCardCount} personal and {businesCardCount} business credit cards."); + await QueueReturnResponse.QueueResponse(request, _importCreditCardResult.Success, _importCreditCardResult.MessageStack); + } + catch (Exception exception) + { + var message = $"There was an issue with handling Import Credit Card Sales Exception Message: {exception.Message}."; + _importCreditCardResult.MessageStack.Add(message); + _importCreditCardResult.Success = false; + await QueueReturnResponse.QueueResponse(request, _importCreditCardResult.Success, _importCreditCardResult.MessageStack); + throw new ServiceException(message); + } + + } + + private async Task> GetCreditCardSales(ImportCreditCardSalesRequest request) + { + var dynamicFileListRequest = request.ToDynamicCreditCardFileListRequest(); + + var files = await _fileOperationsApiService.BuildDynamicFileList(dynamicFileListRequest); + var records = new List(); + + foreach (var file in files) { + var filePathAndName = request.FinalBasePath + file; + var contents = _fileOperationsApiService.GetFileContent(filePathAndName); + using var stringReader = new StringReader(contents.Result); + var config = new CsvConfiguration(CultureInfo.InvariantCulture) + { + Delimiter = "|", + HasHeaderRecord = false + }; + + using var csv = new CsvReader(stringReader, config); + records.AddRange(csv.GetRecords().ToList()); + } + + return records; + } + + private async Task> CreateActivities(IEnumerable records) + { + var activities = new List(); + var deadActivities = new List(); + + foreach (var record in records) + { + string email = string.IsNullOrEmpty(record.ReferringEmployeeEmail) ? null : record.ReferringEmployeeEmail.Trim(); + var employee = string.IsNullOrEmpty(email) ? null : await FindEmployee(email); + + if (employee == null) + { + email = string.IsNullOrEmpty(record.LOResponsibleEmployeeEmail) ? null : record.LOResponsibleEmployeeEmail.Trim(); + employee = string.IsNullOrEmpty(email) ? null : await FindEmployee(email); + } + + if (employee == null + && (!string.IsNullOrWhiteSpace(record.LODirectLoanOfficer) + && record.LODirectLoanOfficer.Contains(','))) + { + var lastName = record.LODirectLoanOfficer?.Split(',')[0].Trim(); + var firstName = record.LODirectLoanOfficer?.Split(',')[1].Trim(); + email = string.IsNullOrEmpty(firstName) || string.IsNullOrEmpty(lastName) + ? null + : $"{firstName}{lastName}@landmarkcu.com"; + employee = string.IsNullOrEmpty(email) ? null : await FindEmployee(email); + } + + if (employee == null) + { + _importCreditCardResult.MessageStack.Add($"Action Import, No user for {email} email. Saving as Dead Activity."); + deadActivities.Add(record.CreateDeadActivity(DeadActivityReason.MissingRequiredField, $"Action Import, No user for {email} email", email)); + continue; + } + + var homeDept = employee.FindHomeDepartment(); + if (homeDept == null) + { + _importCreditCardResult.MessageStack.Add($"User has no reliably determinable home department, {employee.EmployeeId}. Saving as Dead Activity."); + deadActivities.Add( + record.CreateDeadActivity(DeadActivityReason.MissingRequiredField, + $"User has no reliably determinable home department, {employee.EmployeeId}", email, employee.EmployeeId)); + continue; + } + + var activity = record.CreateActivity(employee); + + if (activities.Any(x => x.SourceId == activity.SourceId)) + { + _importCreditCardResult.MessageStack.Add($"Duplicate activity in Temenos credit card file for Incentive Manager, sourceId is {activity.SourceId}"); + continue; + } + activities.Add(activity); + } + + if (deadActivities.Any()) + { + await SaveDeadActivities(deadActivities); + } + + return activities; + } + + private async Task SaveDeadActivities(List deadActivities) + { + try + { + await _deadActivityService.SaveDeadActivityRange(deadActivities); + } + catch (Exception exception) + { + _importCreditCardResult.MessageStack.Add($"Failed to SaveDeadActivities in ImportCreditCardSalesService. Dead Activities will be lost. Exception message:{exception.Message}"); + } + } + + private async Task FindEmployee(string employeeEmail) + { + try + { + return await _userRepository.FindUserByAdEmailAsync(employeeEmail); + } + catch (Exception exception) + { + _importCreditCardResult.MessageStack.Add($"Error to FindEmployee email in ImportCreditCardSalesService. Exception messge:{exception.Message}"); + return null; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Temenos/ImportIndirectVehicleSalesService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Temenos/ImportIndirectVehicleSalesService.cs new file mode 100644 index 0000000..f83eeba --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Temenos/ImportIndirectVehicleSalesService.cs @@ -0,0 +1,240 @@ +using CsvHelper; +using CsvHelper.Configuration; +using System.Globalization; +using System.IO; +using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; +using LCU.API.Internal.Operations.IncentiveApi.Models.Temenos; +using LCU.Library.Internal.ApiResponse.Services; +using LCU.Library.Internal.Exceptions; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Results; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApi.Services.Transformations; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Temenos; + +public class ImportIndirectVehicleSalesService : IImportIndirectVehicleSalesService +{ + private readonly IUserRepository _userRepository; + private readonly IActivityService _activityService; + private readonly IFileOperationsApiService _fileOperationsApiService; + private readonly IDeadActivityService _deadActivityService; + private readonly ProcessResult _importIndirectVehicleSalesResult; + + public ImportIndirectVehicleSalesService(IActivityService activityService, IUserRepository userRepository, + IFileOperationsApiService fileOperationsApiService, IDeadActivityService deadActivityService) + { + _activityService = activityService; + _userRepository = userRepository; + _fileOperationsApiService = fileOperationsApiService; + _deadActivityService = deadActivityService; + _importIndirectVehicleSalesResult = new ProcessResult(); + } + + + public async Task HandleImportIndirectVehicleSales(ImportIndirectVehicleSalesRequest request) + { + try + { + var records = await GetIndirectVehicleRecordsForImport(request); + await CreateAndSaveActivities(records); + await QueueReturnResponse.QueueResponse(request, _importIndirectVehicleSalesResult.Success, _importIndirectVehicleSalesResult.MessageStack); + } + catch (Exception exception) + { + var message = $"There was an issue with handling Import Indirect Vehicle Sales. Exception Message: {exception.Message}."; + _importIndirectVehicleSalesResult.MessageStack.Add(message); + _importIndirectVehicleSalesResult.Success = false; + await QueueReturnResponse.QueueResponse(request, _importIndirectVehicleSalesResult.Success, _importIndirectVehicleSalesResult.MessageStack); + throw new ServiceException(message); + } + } + + private async Task> GetIndirectVehicleRecordsForImport(ImportIndirectVehicleSalesRequest request) + { + var recordsToImport = new List(); + + var directDynamicFileListRequest = request.ToDirectDynamicVehicleFileListRequest(request.DirectDynamicFileName); + var directFiles = await _fileOperationsApiService.BuildDynamicFileList(directDynamicFileListRequest); + + var directRecords = new List(); + + foreach (var file in directFiles) + { + directRecords.AddRange(await GetRecords(request.FinalBasePath + file)); + } + + if (directRecords.Any()) + { + var indirectDynamicFileListRequest = request.ToIndirectDynamicVehicleFileListRequest(request.IndirectDynamicFileName); + + var indirectFiles = await _fileOperationsApiService.BuildDynamicFileList(indirectDynamicFileListRequest); + var indirectRecords = new List(); + + foreach(var file in indirectFiles) + { + indirectRecords.AddRange(await GetRecords(request.FinalBasePath + file)); + } + + recordsToImport = GetIndirectRecordsToImport(directRecords, indirectRecords); + } + + return recordsToImport; + } + + private async Task CreateAndSaveActivities(List records) + { + var activities = new List(); + var deadActivities = new List(); + + foreach (var record in records) + { + var activity = new Activity() + { + Product = Product.Vehicle, + Type = ActivityType.Sale, + State = ActivityState.Accepted, + SourceSystem = SystemType.Temenos, + SourceCreated = DateTime.Parse(record.DisbursedDate), + Accepted = DateTime.Parse(record.DisbursedDate).Date.AddHours(12), + SourceId = record.ApplicationID, + Detail = new ActivityDetail() + { + AccountNumber = record.AccountNumber, + MemberName = record.ApplicantName, + AccountBalance = !string.IsNullOrEmpty(record.AmountFinanced) + ? ParseMoneyDouble(record.AmountFinanced) + : ParseMoneyDouble("0.0"), + AccountStatus = record.SubProduct + }, + EmployeeEmail = null, + EmployeeId = null, + DepartmentId = -1 + }; + + var employee = await FindEmployee(record.ReferringEmployeeEmail); + if (employee == null) + { + var message = $"Temenos Import, No user for {record.ReferringEmployeeEmail}. Saving as Dead Activity."; + _importIndirectVehicleSalesResult.MessageStack.Add(message); + activity.EmployeeEmail = record.ReferringEmployeeEmail; + deadActivities.Add(new DeadActivity(activity, DeadActivityReason.MissingRequiredField, message)); + continue; + } + + activity.AppUserId = employee.Id; + activity.EmployeeEmail = employee.AdEmail; + activity.EmployeeId = employee.EmployeeId; + + var homeDept = employee.FindHomeDepartment(); + if (homeDept == null) + { + var message = $"User has no reliably determinable home department, {employee.EmployeeId}. Saving as Dead Activity."; + _importIndirectVehicleSalesResult.MessageStack.Add(message); + deadActivities.Add(new DeadActivity(activity, DeadActivityReason.MissingRequiredField, message)); + continue; + } + + activity.DepartmentId = homeDept.DepartmentId; + activities.Add(activity); + } + + if (activities.Any()) + { + await SaveActivities(activities); + } + + if (deadActivities.Any()) + { + await SaveDeadActivities(deadActivities); + } + + _importIndirectVehicleSalesResult.MessageStack.Add($"Imported { activities.Count + deadActivities.Count } indirect vehicle sales."); + } + + private async Task FindEmployee(string employeeEmail) + { + try + { + return await _userRepository.FindUserByAdEmailAsync(employeeEmail); + } + catch (Exception exception) + { + _importIndirectVehicleSalesResult.MessageStack.Add($"Error when finding user by email in ImportIndirectVehicleSalesService. Exception message: { exception.Message }"); + return null; + } + } + + private async Task SaveActivities(List activities) + { + try + { + await _activityService.SaveActivityRangeAsync(activities); + } + catch (Exception exception) + { + var message = $"Failed to save activities from ImportIndirectVehicleSalesService. Activities will be lost. Eception Message: {exception.Message}"; + _importIndirectVehicleSalesResult.MessageStack.Add(message); + throw new ServiceException($"{message}. Exception: {exception.Message}"); + } + } + + private async Task SaveDeadActivities(List deadActivities) + { + try + { + await _deadActivityService.SaveDeadActivityRange(deadActivities); + } + catch (Exception exception) + { + var message = $"Failed to save dead activities from ImportIndirectVehicleSalesService. Dead Activities will be lost. Eception Message: {exception.Message}"; + _importIndirectVehicleSalesResult.MessageStack.Add(message); + throw new ServiceException($"{message}. Exception: {exception.Message}"); + } + } + + private static List GetIndirectRecordsToImport(List directRecords, List indirectRecords) + { + var recordsToImport = new List(); + + foreach (var indirectRecord in indirectRecords) + { + if (!recordsToImport.Any(x => x.ApplicationID == indirectRecord.ApplicationID)) + { + var directSsnMatches = directRecords.Where(x => x.ApplicantSSN == indirectRecord.ApplicantSSN); + var directCreatedBeforeIndirectMatches = directSsnMatches.Where(x => DateTime.Parse(x.CreatedDate) < DateTime.Parse(indirectRecord.CreatedDate)); + + if (directCreatedBeforeIndirectMatches.Any()) + { + var matchedDirectRecord = directCreatedBeforeIndirectMatches.OrderByDescending(x => DateTime.Parse(x.CreatedDate)).FirstOrDefault(); + indirectRecord.ReferringEmployeeEmail = matchedDirectRecord?.ReferringEmployeeEmail; + recordsToImport.Add(indirectRecord); + } + } + } + + return recordsToImport; + } + + private async Task> GetRecords(string filePathAndName) + { + var contents = await _fileOperationsApiService.GetFileContent(filePathAndName); + using var stringReader = new StringReader(contents); + var config = new CsvConfiguration(CultureInfo.InvariantCulture) + { + Delimiter = "|", + HasHeaderRecord = false + }; + + using var csv = new CsvReader(stringReader, config); + return csv.GetRecords().ToList(); + } + + private double? ParseMoneyDouble(string amountFinanced) + { + var parsed = decimal.Parse(amountFinanced, NumberStyles.Currency, new CultureInfo("en-Us")); + return (double)parsed; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Transformations/DynamicFileListRequestTransformer.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Transformations/DynamicFileListRequestTransformer.cs new file mode 100644 index 0000000..539892c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/Transformations/DynamicFileListRequestTransformer.cs @@ -0,0 +1,55 @@ +using LCU.Library.Internal.FileOperationsModels.Models; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services.Transformations; + +public static class DynamicFileListRequestTransformer +{ + public static DynamicFileListRequest ToDirectDynamicVehicleFileListRequest(this ImportIndirectVehicleSalesRequest request, string fileName) => + new() + { + Path = request.FilePath, + Filename = fileName, + DynamicFileSettings = request.DirectDynamicFileSettings, + }; + + public static DynamicFileListRequest ToIndirectDynamicVehicleFileListRequest(this ImportIndirectVehicleSalesRequest request, string fileName) => + new() + { + Path = request.FilePath, + Filename = fileName, + DynamicFileSettings = request.IndirectDynamicFileSettings, + }; + + public static DynamicFileListRequest ToDynamicCreditCardFileListRequest(this ImportCreditCardSalesRequest request) => + new() + { + Path = request.FilePath, + Filename = request.DynamicFileName, + DynamicFileSettings = request.DynamicFileSettings, + }; + + public static DynamicFileListRequest ToDynamicActionCreditCardFileListRequest(this ActionCreditCardRequest request) => + new() + { + Path = request.FilePath, + Filename = request.DynamicFileName, + DynamicFileSettings = request.DynamicFileSettings, + }; + + public static DynamicFileListRequest ToDynamicActionVehicleFileListRequest(this ActionVehicleRequest request) => + new() + { + Path = request.FilePath, + Filename = request.DynamicFileName, + DynamicFileSettings = request.DynamicFileSettings, + }; + + public static DynamicFileListRequest ToDynamicAlliedGapMbpFileListRequest(this AlliedGapMbpRequest request) => + new() + { + Path = request.FilePath, + Filename = request.DynamicFileName, + DynamicFileSettings = request.DynamicFileSettings, + }; +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/UserAssignmentService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/UserAssignmentService.cs new file mode 100644 index 0000000..50de305 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/UserAssignmentService.cs @@ -0,0 +1,68 @@ +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; +using LCU.API.Internal.Operations.IncentiveApiLib.Services; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApi.Services; + +public class UserAssignmentService : IUserAssignmentService +{ + private readonly IUserAssignmentRepository _userAssignmentRepository; + private readonly IUserService _userService; + private readonly IUnitOfWork _unitOfWork; + + public UserAssignmentService(IUserAssignmentRepository userAssignmentRepository, + IUserService userService, IUnitOfWork unitOfWork) + { + _userAssignmentRepository = userAssignmentRepository; + _userService = userService; + _unitOfWork = unitOfWork; + } + + public async Task> GetAllUserAssignmentsAsync() + { + return await _userAssignmentRepository.GetAllUserAssignmentsAsync(); + } + + public async Task GetUserAssignment(AssignmentType assignmentType, string key) { + return await _userAssignmentRepository.FindExistingAssignmentAsync(assignmentType, key); + } + + public async Task SetRegionalManager(UserAssignmentRequest assignmentRequest) + { + var employee = await _userService.GetUserByEmployeeIdAsync(assignmentRequest.EmployeeId); + if (employee == null) + { + throw new NotFoundException("Employee Not Found"); + } + if( !employee.HasRoleAnywhere(Role.RegionalManager)) + { + throw new BadUserAssignmentException("Employee must have the Regional Manager role"); + } + + var assignment = await GetUserAssignment(assignmentRequest.Type.Value, + assignmentRequest.Key); + if (assignment != null) + { + assignment.AppUser = employee; + } + else + { + assignment = new UserAssignment + { + Id = 0, + Type = assignmentRequest.Type.Value, + Key = assignmentRequest.Key, + AppUser = employee + }; + + employee.Assignments.Add(assignment); + + } + await _unitOfWork.CompleteAsync(); + + return assignment; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/UserService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/UserService.cs new file mode 100644 index 0000000..3d3f206 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/UserService.cs @@ -0,0 +1,604 @@ +using LCU.API.Internal.Operations.IncentiveApi.Exceptions; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; +using LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; +using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Results; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Services; + +public class UserService : IUserService +{ + private const string AutoGeneratedHomeAssignment = "Auto Generated Home Assignment"; + + private readonly IApplicationDbIntegrityService _applicationDbIntegrityService; + private readonly IDepartmentRepository _departmentRepository; + private readonly IRoleMappingRepository _roleMappingRepository; + private readonly IUnitOfWork _unitOfWork; + private readonly IUserDepartmentHistoryRepository _userDepartmentHistoryRepository; + private readonly IUserPermissionHistoryRepository _userPermissionHistoryRepository; + private readonly IUserRepository _userRepository; + + public UserService( + IApplicationDbIntegrityService applicationDbIntegrityService, + IDepartmentRepository departmentRepository, + IRoleMappingRepository roleMappingRepository, + IUnitOfWork unitOfWork, + IUserDepartmentHistoryRepository userDepartmentHistoryRepository, + IUserPermissionHistoryRepository userPermissionHistoryRepository, + IUserRepository userRepository + ) + { + _applicationDbIntegrityService = applicationDbIntegrityService; + _departmentRepository = departmentRepository; + _roleMappingRepository = roleMappingRepository; + _unitOfWork = unitOfWork; + _userDepartmentHistoryRepository = userDepartmentHistoryRepository; + _userPermissionHistoryRepository = userPermissionHistoryRepository; + _userRepository = userRepository; + } + + public async Task> GetAllUserSummariesAsync() + { + return await _userRepository.GetAllUserSummariesAsync(); + } + + public IReadOnlyCollection GetTerminatedUsersAsync(MonthRange monthRange) + { + return _userRepository.GetTerminatedUsersAsync(monthRange); + } + + public async Task> GetAllUsersAsync(UserSearchRequest userSearchRequest) + { + if (string.IsNullOrWhiteSpace(userSearchRequest.Query) && + string.IsNullOrWhiteSpace(userSearchRequest.Role) && + string.IsNullOrWhiteSpace(userSearchRequest.FirstName) && + string.IsNullOrWhiteSpace(userSearchRequest.LastName) && + string.IsNullOrEmpty(userSearchRequest.DnaOrgNbr)) + { + return await _userRepository.GetAllUsersAsync(); + } + + return await _userRepository.SearchUsersAsync(userSearchRequest); + } + + public async Task GetUserByEmployeeIdAsync(string employeeId) + { + return await _userRepository.FindUserByEmployeeIdAsync(employeeId); + } + + public async Task GetUserByAdEmailAsync(string adEmail) + { + return await _userRepository.FindUserByAdEmailAsync(adEmail); + } + + public async Task GetActiveUserByEmployeeIdAsync(string employeeId) + { + return await _userRepository.FindActiveUserByEmployeeIdAsync(employeeId); + } + + public async Task GetActiveUserByAdEmailAsync(string adEmail) + { + return await _userRepository.FindUserByAdEmailAsync(adEmail); + } + + public async Task FindUserAsync(string employeeId, string adEmail) + { + return await _userRepository.FindUserAsync(employeeId, adEmail); + } + + public async Task SaveUserAsync(AppUser newAppUser) + { + var user = await _userRepository.SaveUserAsync(newAppUser); + await _unitOfWork.CompleteAsync(); + return user; + } + + public async Task> SaveUserRangeAsync(List newUsers) + { + var users = await _userRepository.SaveUserRangeAsync(newUsers); + await _unitOfWork.CompleteAsync(); + return users; + } + + private async Task SyncDayForceEmployeeExisting(AppUser existing, ActiveEmployeeResult activeEmployee) + { + var homeUserDepartment = existing.Departments.FirstOrDefault(x => x.IsHome && x.EndDate == null); + var salesforceDepartment = await LookupDepartment(activeEmployee); + + if (homeUserDepartment == null && existing.Terminated != null) + { + homeUserDepartment = await GenerateNewEmployeeHomeDepartment(existing, activeEmployee); + existing.Departments.Add(homeUserDepartment); + existing.Terminated = null; + } + + if (homeUserDepartment == null) + { + throw new Exception($"Existing user with no home department, {existing.EmployeeId}"); + } + + var homeDepartmentId = homeUserDepartment.Department == null + ? homeUserDepartment.DepartmentId + : homeUserDepartment.Department.Id; + + if (homeDepartmentId != salesforceDepartment.Id) + { + var monthBegin = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); + var nextMonthBegin = monthBegin.AddMonths(1); + var existingAssignment = existing.Departments.FirstOrDefault(x => + x.SafeGetDepartmentId() == salesforceDepartment.Id + && x.EndDate == null || x.EndDate >= monthBegin); + + if (existingAssignment != null) + { + existingAssignment.EndDate = null; + existingAssignment.IsHome = true; + existingAssignment.Role = _roleMappingRepository.FindMatchingRoleFromBusinessRole(activeEmployee.Role); + } + else + { + var assignment = new UserDepartment() + { + Employee = existing, + Note = AutoGeneratedHomeAssignment, + IsHome = true, + Department = salesforceDepartment, + StartDate = monthBegin, + SlidingPayout = 100, + Role = _roleMappingRepository.FindMatchingRoleFromBusinessRole(activeEmployee.Role) + }; + existing.Departments.Add(assignment); + } + homeUserDepartment.IsHome = false; + homeUserDepartment.EndDate = nextMonthBegin.Subtract(TimeSpan.FromDays(1)); + } + else + { + homeUserDepartment.Role = _roleMappingRepository.FindMatchingRoleFromBusinessRole(activeEmployee.Role); + } + + existing.HomeDepartment = activeEmployee.OrgUnit; + existing.BaseRole = activeEmployee.Role; + existing.FullName = activeEmployee.DisplayName; + existing.ManagerKey = activeEmployee.ManagerKey; + existing.Role = _roleMappingRepository.FindMatchingRoleFromBusinessRole(activeEmployee.Role); + + foreach (var userDepartment in existing.Departments?.Where(department => + !department.IsHome && string.Equals(department.Note, AutoGeneratedHomeAssignment, StringComparison.InvariantCultureIgnoreCase))) + { + userDepartment.Note = ""; + } + + return existing; + } + + private async Task SyncDayForceEmployeeNew(ActiveEmployeeResult activeEmployee) + { + var newEmployee = new AppUser + { + Role = _roleMappingRepository.FindMatchingRoleFromBusinessRole(activeEmployee.Role), + AdEmail = activeEmployee.LoginName, + EmployeeId = activeEmployee.EmployeeNumber, + HomeDepartment = activeEmployee.OrgUnit, + BaseRole = activeEmployee.Role, + FullName = activeEmployee.DisplayName, + ManagerKey = activeEmployee.ManagerKey, + Departments = new List() + }; + newEmployee.Departments.Add(await GenerateNewEmployeeHomeDepartment(newEmployee, activeEmployee)); + await SaveUserAsync(newEmployee); + return newEmployee; + } + + private async Task GenerateNewEmployeeHomeDepartment(AppUser appUser, ActiveEmployeeResult activeEmployee) + { + var hireDate = activeEmployee.DetermineHireDate(); + var assignment = new UserDepartment() + { + Employee = appUser, + Note = AutoGeneratedHomeAssignment, + IsHome = true, + Department = await LookupDepartment(activeEmployee), + StartDate = new DateTime(hireDate.Year, hireDate.Month, 1), + SlidingPayout = 100, + Role = _roleMappingRepository.FindMatchingRoleFromBusinessRole(activeEmployee.Role) + }; + + return assignment; + } + + public async Task SyncDayForceEmployee(ActiveEmployeeResult activeEmployee) + { + var existing = await GetUserByEmployeeIdAsync(activeEmployee.EmployeeNumber); + if (existing != null) + { + existing = await SyncDayForceEmployeeExisting(existing, activeEmployee); + await _unitOfWork.CompleteAsync(); + } + else + { + existing = await SyncDayForceEmployeeNew(activeEmployee); + } + + return existing; + } + + public async Task TerminateDayForceEmployee(TerminatedEmployeeResult terminatedEmployee) + { + var existingAppUser = await GetUserByEmployeeIdAsync(terminatedEmployee.EmployeeNumber); + + if (!(existingAppUser is { Terminated: null })) return null; + + var dataSyncSucceeded = await SyncUserIds(existingAppUser); + if (!dataSyncSucceeded) + { + throw new Exception($"Failed to sync AppUser data for AppUserId: {existingAppUser.Id}"); + } + + var monthBegin = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); + var nextMonthBegin = monthBegin.AddMonths(1); + var termDeptDate = nextMonthBegin.Subtract(TimeSpan.FromDays(1)); + existingAppUser.AdditionalPermissions.Clear(); + existingAppUser.Terminated = terminatedEmployee.TerminationDate; + foreach (var department in existingAppUser.Departments) + { + if (department.EndDate == null || department.EndDate.Value.Date > termDeptDate.Date) + { + department.EndDate = termDeptDate; + } + } + + await _unitOfWork.CompleteAsync(); + return existingAppUser; + } + + private async Task SyncUserIds(AppUser appUser) + { + return await _applicationDbIntegrityService.SyncIdsWithDb(appUser); + } + + public async Task> AssignDepartments( + string employeeId, + DepartmentAssignmentRequest assignmentRequest + ) + { + var employee = await GetUserByEmployeeIdAsync(employeeId); + if (employee == null) + { + throw new NotFoundException("Employee Not Found"); + } + + var newAssignments = new List(); + + foreach (var departmentId in assignmentRequest.DepartmentId) + { + var department = await _departmentRepository.FindDepartmentByIdAsync(departmentId); + if (department == null) + { + throw new NotFoundException("Department Not Found"); + } + + var role = Role.GetRolesList() + .FirstOrDefault(role => role.Name.Equals(assignmentRequest.Role)); + + if (role == null) + { + throw new BadDepartmentAssignmentException("Invalid Or Missing Role"); + } + + if (assignmentRequest.SlidingPayout < 0 || assignmentRequest.SlidingPayout > UserDepartment.PayoutMax) + { + throw new BadDepartmentAssignmentException( + $"Sliding Payout must be between 0 and {UserDepartment.PayoutMax}"); + } + + var newAssignment = new UserDepartment() + { + Employee = employee, + Department = department, + Note = assignmentRequest.Note, + StartDate = assignmentRequest.StartDate, + EndDate = assignmentRequest.EndDate, + Role = role, + SlidingPayout = assignmentRequest.SlidingPayout + }; + + foreach (var userDepartment in employee.Departments) + { + if (userDepartment.IsConflictingAssignment(newAssignment, out string message)) + { + throw new BadDepartmentAssignmentException(message); + } + } + + employee.Departments.Add(newAssignment); + newAssignments.Add(newAssignment); + } + + await _unitOfWork.CompleteAsync(); + + return newAssignments; + } + + public async Task> UpdatePermissions(string employeeId, AdditionalPermissionRequest request) + { + await AssignPermissions(employeeId, request.AddPermCodes); + var additional = await RemovePermissions(employeeId, request.RemovePermCodes); + return additional; + } + + public async Task> AssignPermissions(string employeeId, List permCodes) + { + var employee = await GetUserByEmployeeIdAsync(employeeId); + if (employee == null) + { + throw new NotFoundException("Employee Not Found"); + } + + var newPermissions = Permission.GetAllPermissions() + .Where(permission => permCodes.Contains(permission.Code) && + !employee.UserHasPermission(permission)) + .ToList(); + + var newUserPermissions = newPermissions.Select(perm => new UserPermission() + { + Permission = perm, + AppUser = employee + }) + .ToList(); + + employee.AdditionalPermissions ??= new List(); + employee.AdditionalPermissions.AddRange(newUserPermissions); + + await _userPermissionHistoryRepository.SaveUserPermissionsInsertion(newUserPermissions); + await _unitOfWork.CompleteAsync(); + return employee.AdditionalPermissions.Select(x => x.Permission).ToList(); + } + + public async Task> RemovePermissions(string employeeId, List permCodes) + { + var employee = await GetUserByEmployeeIdAsync(employeeId); + if (employee == null) + { + throw new NotFoundException("Employee Not Found"); + } + + var permissionsToRemove = permCodes.Select(requestCode => + { + return Permission.GetAllPermissions().SingleOrDefault(p => p.Code.Equals(requestCode)); + }); + + var permissionsRemoved = employee.AdditionalPermissions.Where(existingPerm => permissionsToRemove.Contains(existingPerm.Permission)).ToList(); + + await _userPermissionHistoryRepository.SaveUserPermissionsDeletion(permissionsRemoved); + + employee.AdditionalPermissions = + employee.AdditionalPermissions.Where(existingPerm => !permissionsToRemove.Contains(existingPerm.Permission)).ToList(); + + await _unitOfWork.CompleteAsync(); + return employee.AdditionalPermissions.Select(x => x.Permission).ToList(); + } + + public async Task UpdateDepartmentAssignment(DepartmentUpdateRequest request) + { + var existing = await GetUserByEmployeeIdAsync(request.EmployeeId); + if (existing == null) + { + throw new NotFoundException("Employee not found."); + } + + var assignment = existing.Departments.FirstOrDefault(x => x.Id == request.AssignmentId); + if (assignment == null) + { + throw new NotFoundException("Assignment not found."); + } + + var role = Role.GetRolesList().FirstOrDefault(role => role.Name.Equals(request.Role)); + + if (role == null) + { + throw new BadDepartmentAssignmentException("Invalid Or Missing Role"); + } + + if (request.SlidingPayout is < 0 or > UserDepartment.PayoutMax) + { + throw new BadDepartmentAssignmentException($"Sliding Payout must be between 0 and {UserDepartment.PayoutMax}"); + } + + await _userDepartmentHistoryRepository.SaveUserDepartmentUpdate(assignment, request); + + assignment.EndDate = request.EndDate; + assignment.Role = role; + assignment.SlidingPayout = request.SlidingPayout; + await _unitOfWork.CompleteAsync(); + return assignment; + } + + public async Task> FindDepartmentAssignments(DepartmentPersonnelSearchRequest searchRequest) + { + return await _departmentRepository.FindDepartmentAssignments(searchRequest); + } + + private async Task LookupDepartment(ActiveEmployeeResult employee) + { + var department = await _departmentRepository.FindOrCreateDepartment(employee); + + if (department == null) + { + throw new Exception("Couldn't get or create a department"); + } + + return department; + } + + public async Task TryFindSingleUserByName(UserNameSearchRequest searchRequest) + { + var userSearchRequest = new UserSearchRequest() + { + FirstName = searchRequest.FirstName, + LastName = searchRequest.LastName, + MiddleInit = searchRequest.MiddleInit, + DnaOrgNbr = searchRequest.DnaOrgNbr + }; + + var foundUsers = await _userRepository.SearchUsersAsync(userSearchRequest); + + if (foundUsers.Count < 1 && !string.IsNullOrWhiteSpace(userSearchRequest.MiddleInit)) + { + foundUsers = await _userRepository.SearchUsersAsync(new UserSearchRequest() + { + FirstName = userSearchRequest.FirstName, + LastName = userSearchRequest.LastName, + DnaOrgNbr = userSearchRequest.DnaOrgNbr + }); + } + + if (foundUsers.Count < 1) + { + foundUsers = await _userRepository.SearchUsersAsync(new UserSearchRequest() + { + FirstName = userSearchRequest.FirstName, + LastName = userSearchRequest.LastName, + }); + } + + return foundUsers.Single(); + } + + public async Task FindManager(AppUser appUser) + { + return await GetUserByEmployeeIdAsync(appUser.ManagerKey); + } + + public async Task> FindDepartmentManagers(int departmentId) + { + var searchRequest = new DepartmentPersonnelSearchRequest + { + DepartmentId = departmentId, + IncludeAllPersonnel = true, + Month = DateTime.Today.Month, + Year = DateTime.Today.Year + }; + var usersInDepartment = await _departmentRepository.FindDepartmentAssignments(searchRequest); + var departmentManagers = usersInDepartment + .Where(user => user.Departments + .Any(deptAssignment => Role.DepartmentManagerRoles.Contains(deptAssignment.Role) + && deptAssignment.DepartmentId == departmentId)) + .ToList(); + + return departmentManagers; + } + + public async Task UserHasAnyRequiredPermission(string appUserEmail, List allowedPermissions) + { + var user = await GetUserByAdEmailAsync(appUserEmail); + if (user == null) + { + return false; + } + + var hasPermission = false; + foreach (var userDepartment in user.Departments) + { + var userDeptPermissions = userDepartment.Role.Permissions.Select(x => x.Code).ToList(); + if (allowedPermissions.Any(p => userDeptPermissions.Contains(p))) + { + hasPermission = true; + } + } + + if (!hasPermission) + { + var userAdditionalPermissions = (user.AdditionalPermissions ?? new List()) + .Select(x => x.Permission.Code) + .ToList(); + if (allowedPermissions.Any(p => userAdditionalPermissions.Contains(p))) + { + hasPermission = true; + } + } + + return hasPermission; + } + + public async Task SyncActiveUserIds() + { + var allUsersSucceeded = true; + try + { + var activeUsers = await _userRepository.GetAllUsersAsync(); + foreach (var appUser in activeUsers.Where(x => x.Terminated == null)) + { + var userSyncSucceeded = await _applicationDbIntegrityService.SyncIdsWithDb(appUser); + if (userSyncSucceeded) + { + continue; + } + + allUsersSucceeded = false; + Log.Error($"Failed to sync Ids for AppUser: {appUser.Id}"); + } + } + catch (Exception ex) + { + Log.Error(ex, ex.Message); + return false; + } + + return allUsersSucceeded; + } + + public static bool ValidateChangePermission(string currentEmployeeId, string targetEmployeeId) => + currentEmployeeId == targetEmployeeId; + + public async Task UpdateEmployeeDetails(EmployeeDetailsUpdate employeeDetailsUpdate) + { + try + { + var existingAppUser = await GetUserByEmployeeIdAsync(employeeDetailsUpdate.OldEmployeeId); + if (existingAppUser != null) + { + existingAppUser = await SyncEmployeeDetails(existingAppUser, employeeDetailsUpdate); + await _unitOfWork.CompleteAsync(); + return new EmployeeDetailsUpdateResult + { + AppUser = existingAppUser, + UpdateMessage = $"Employee Details Update Succeeded", + UpdatedSuccessfully = true + }; + } + + return new EmployeeDetailsUpdateResult + { + UpdateMessage = $"Employee Details Update Failed, No Existing Application User Found To Update" + }; + } + catch (Exception ex) + { + Log.Error(ex, ex.Message); + return new EmployeeDetailsUpdateResult + { + UpdateMessage = $"Employee Details Update Failed, {ex.Message}" + }; + } + } + + public async Task SyncEmployeeDetails(AppUser appUser, EmployeeDetailsUpdate employeeDetailsUpdate) + { + appUser.AdEmail = employeeDetailsUpdate.NewEmail; + appUser.EmployeeId = employeeDetailsUpdate.NewEmployeeId; + appUser.FullName = employeeDetailsUpdate.NewFullName; + + var updateSyncSucceeded = await _applicationDbIntegrityService.SyncAppUserUpdate(appUser); + if (!updateSyncSucceeded) + { + throw new Exception($"Failed to update AppUser details for AppUserId: {appUser.Id}"); + } + + return appUser; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Utilities/CronHelpers.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Utilities/CronHelpers.cs new file mode 100644 index 0000000..fea4cbc --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Utilities/CronHelpers.cs @@ -0,0 +1,20 @@ +using NCrontab; + +namespace LCU.API.Internal.Operations.IncentiveApi.Utilities; + +public static class CronHelpers +{ + public static CrontabSchedule SetCronSchedule(string cron, string cronDefault) + { + try + { + return CrontabSchedule.Parse(cron); + } + catch (Exception exception) + { + Log.Error(exception, "Failure parsing cron-tab in appsettings."); + return CrontabSchedule.Parse(cronDefault); + } + } +} + diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/appsettings.json b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/appsettings.json new file mode 100644 index 0000000..2b24b7b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/appsettings.json @@ -0,0 +1,245 @@ +{ + "AzureActiveDirectory": { + "Instance": "https://login.microsoftonline.com/", + "Domain": "landmarkcu.com", + "TenantId": "63cd1554-2783-4a86-ae50-b9e6c21c9499", + "ClientId": "40cd32ed-fb23-475e-b0cb-d8af1f39dd80", + "ValidAudience": "e9cb601e-e98d-4881-90fc-d5758dac5ec8" + }, + "Primary": { + "AppName": "Incentive API", + "SeqUrl": "http://localhost:5080", + "SeqKey": "9MQu0vLDwQ1LCFzGixZM", + "ApplicationDbIntegrityCronSchedule": "0 0 * * *", + "CorsAllowedOrigins": [ + "http://localhost:3000", + "https://localhost:3001", + "http://lcu-internal.com", + "https://lcu-internal.com", + "https://qa-api.lcu-internal.com", + "https://uat-api.lcu-internal.com", + "https://prod-api.lcu-internal.com", + "https://qa-incentive-manager.apps.azure.lcu-internal.com", + "https://uat-incentive-manager.apps.azure.lcu-internal.com", + "https://prd-incentive-manager.apps.azure.lcu-internal.com" + ], + "IncentiveManagerClientUrl": "https://qa-api.lcu-internal.com/incentive-manager", + "FeatureGates": { + "EnableControllers": true + }, + "FeatureFlags": { + "RunApplicationDbIntegrityHostedService": true, + "RunImportCreditCardSalesConsumer": true, + "RunImportIndirectVehicleSalesConsumer": true, + "RunActionCreditCardProcessConsumer": true, + "RunActionVehicleProcessConsumer": true, + "RunIncentiveProcessesConsumer": true, + "RunNightlyIncentiveProcessesConsumer": true, + "RunReportingProcessConsumer": true, + "RunGoalDispersalProcessConsumer": true, + "RunPayoutIncentiveProcessConsumer": true, + "RunDayforceSyncProcessConsumer": true, + "RunGapMbpProcessConsumer": true + } + }, + "ServicesFactoryConfiguration": { + "ServiceFactoryRegistrations": [ + { + "ServiceInterfaceName": "INightlyIncentiveProcessesService", + "ServiceRegistrations": [ + { + "ServiceName": "DirectDepositActivityDecisionService", + "ServiceId": "0" + }, + { + "ServiceName": "ReferralActivityDecisionService", + "ServiceId": "1" + }, + { + "ServiceName": "AccountActivityDecisionService", + "ServiceId": "2" + }, + { + "ServiceName": "LICActivityDecisionService", + "ServiceId": "3" + }, + { + "ServiceName": "EStatementReferralActivityDecisionService", + "ServiceId": "4" + } + ] + }, + { + "ServiceInterfaceName": "IIncentiveProcessesService", + "ServiceRegistrations": [ + { + "ServiceName": "NewCheckingAccountActivityService", + "ServiceId": "0" + }, + { + "ServiceName": "NewDigitalCheckingAccountActivityService", + "ServiceId": "1" + }, + { + "ServiceName": "AtmCheckCardActivityService", + "ServiceId": "2" + }, + { + "ServiceName": "DpEligibleLoanSalesActivityService", + "ServiceId": "3" + }, + { + "ServiceName": "MortgageSaleActivityService", + "ServiceId": "4" + }, + { + "ServiceName": "ReferralActivityService", + "ServiceId": "5" + }, + { + "ServiceName": "VehicleSalesActivityService", + "ServiceId": "6" + } + ] + } + ] + }, + "DayForceConfiguration": { + "Url": "https://www.dayforcehcm.com/api", + "DayForceClientName": "landmarkcu", + "DayForceApiVersion": "V1", + "DayForceUsername": "DayforceAPI", + "DayForcePassword": "Azure-Key-Vault", + "DayForceActiveUserReport": "INCENTIVE_MANAGER_ACTIVE", + "DayForceTerminatedUserReport": "INCENTIVE_MANAGER_TERMINATED", + "DayForceManualReportRecipients": [ + "WendySchenk@landmarkcu.com", + "DebMcClarran@landmarkcu.com" + ] + }, + "SqlConnectionConfiguration": { + "ConnectionString": "data source=localhost;initial catalog=IncentiveManager;Integrated Security=SSPI;MultipleActiveResultSets=True;App=EntityFramework;Encrypt=False", + "MaxRetryCount": 5, + "MaxRetryDelay": "0.00:00:30" + }, + "EmailRecipientsConfiguration": { + "TransferRequestInitiatedNotification": [ + "GeorgeWarner@landmarkcu.com", + "DebMcClarran@landmarkcu.com" + ], + "OverrideRecipients": true + }, + "ReportConfiguration": { + "ReportConfigs": [ + { + "Name": "Activity Report", + "RequiredPermissionCode": "VIEW_ACTIVITY_REPORT", + "Url": "https://d2dtbetl01q.lcu-internal.com/reports/report/IncentiveManager/IncentiveManagerActivities" + }, + { + "Name": "Associate Record Report", + "RequiredPermissionCode": "VIEW_ASSOCIATE_RECORD_REPORT", + "Url": "https://d2dtbetl01q.lcu-internal.com/reports/report/IncentiveManager/Admin/IncentiveManagerAssociateRecord" + }, + { + "Name": "Audit Report", + "RequiredPermissionCode": "VIEW_AUDIT_REPORT", + "Url": "https://d2dtbetl01q.lcu-internal.com/reports/report/IncentiveManager/Admin/IncentiveManagerAudit" + }, + { + "Name": "Goal Progress Report", + "RequiredPermissionCode": "VIEW_GOAL_PROGRESS_REPORT", + "Url": "https://d2dtbetl01q.lcu-internal.com/reports/report/IncentiveManager/IncentiveManagerGoalProgress" + }, + { + "Name": "Payout Report", + "RequiredPermissionCode": "VIEW_PAYOUT_REPORT", + "Url": "https://d2dtbetl01q.lcu-internal.com/reports/report/IncentiveManager/Admin/IncentiveManagerPayout" + } + ] + }, + "ConnectionConfiguration": { + "HostUrl": "localhost", + "UserName": "guest", + "Password": "guest" + }, + "CoreApiWrapperConfiguration": { + "Url": "https://localhost:44355/api/core/", + "GetNewCheckingAccountActivityEndpoint": "Activity/Checking/", + "GetNewDigitalBankingCheckingAccountActivityEndpoint": "Activity/DigitalBankingChecking/", + "GetNewATMCheckCardActivityEndpoint": "Activity/AtmCheckCard/", + "GetNewActivityManagerReferralsActivityEndpoint": "Activity/ActivtyManagerIncentiveReferral", + "GetNewMortgageAccountActivityEndpoint": "Activity/MortgageAccount", + "GetNewVehicleSaleActivityEndpoint": "Activity/VehicleAccount", + "GetNewDPEligibleAccountsActivityEndpoint": "Activity/DPEligibleAccounts", + "QueryActivityManagerActivityEndpoint": "Activity/QueryActivityManagerActivity", + "GetAccountActivityAccountDetailsEndpoint": "Account/QueryActivityAccountDetails", + "GetTransactionHistoryEndpoint": "Transaction/GetTransactionHistory", + "GetEStatementDeliveryMethodsEndpoint": "Activity/GetEStatementDeliveryActivity" + }, + "IncentiveProcessesEndpoint": { + "ReceiveEndpoint": "LCU.IncentiveApi.IncentiveProcesses", + "ReceiveEndpointType": "exchange", + "PrefetchLimit": 1 + }, + "NightlyIncentiveProcessesEndpoint": { + "ReceiveEndpoint": "LCU.IncentiveApi.NightlyIncentiveProcesses", + "ReceiveEndpointType": "exchange", + "PrefetchLimit": 1 + }, + "ImportCreditCardSalesEndpoint": { + "ReceiveEndpoint": "LCU.IncentiveApi.ImportCreditCardSales", + "ReceiveEndpointType": "exchange", + "PrefetchLimit": 1 + }, + "ImportIndirectVehicleSalesEndpoint": { + "ReceiveEndpoint": "LCU.IncentiveApi.ImportIndirectVehicleSales", + "ReceiveEndpointType": "exchange", + "PrefetchLimit": 1 + }, + "ReportingProcessEndpoint": { + "ReceiveEndpoint": "LCU.IncentiveApi.ReportingProcess", + "ReceiveEndpointType": "exchange", + "PrefetchLimit": 1 + }, + "GoalDispersalProcessEndpoint": { + "ReceiveEndpoint": "LCU.IncentiveApi.GoalDispersalProcess", + "ReceiveEndpointType": "exchange", + "PrefetchLimit": 1 + }, + "PayoutIncentiveProcessEndpoint": { + "ReceiveEndpoint": "LCU.IncentiveApi.PayoutIncentiveProcess", + "ReceiveEndpointType": "exchange", + "PrefetchLimit": 1 + }, + "DayforceSyncProcessEndpoint": { + "ReceiveEndpoint": "LCU.IncentiveApi.DayforceSyncProcess", + "ReceiveEndpointType": "exchange", + "PrefetchLimit": 1 + }, + "GapMbpProcessEndpoint": { + "ReceiveEndpoint": "LCU.IncentiveApi.GapMbpProcess", + "ReceiveEndpointType": "exchange", + "PrefetchLimit": 1 + }, + "ActionCreditCardProcessEndpoint": { + "ReceiveEndpoint": "LCU.IncentiveApi.ActionCreditCardProcess", + "ReceiveEndpointType": "exchange", + "PrefetchLimit": 1 + }, + "ActionVehicleProcessEndpoint": { + "ReceiveEndpoint": "LCU.IncentiveApi.ActionVehicleProcess", + "ReceiveEndpointType": "exchange", + "PrefetchLimit": 1 + }, + "FileOperationsApiConfiguration": { + "Url": "https://qa-fileoperationsapi.lcu-internal.com", + "GetFileContentEndpoint": "/api/FileOperations", + "BuildDynamicFileListEndpoint": "/DynamicFile/BuildDynamicFileList", + "RetryCount": 1 + }, + "NotificationApiConfiguration": { + "Url": "https://localhost:53144/api/", + "Email": "Email" + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/using.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/using.cs new file mode 100644 index 0000000..a4eb77a --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/using.cs @@ -0,0 +1,5 @@ +global using Serilog; +global using System; +global using System.Linq; +global using System.Collections.Generic; +global using System.Threading.Tasks; diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Extensions/ActivityExtensions.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Extensions/ActivityExtensions.cs new file mode 100644 index 0000000..28ba655 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Extensions/ActivityExtensions.cs @@ -0,0 +1,58 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Extensions; + +public static class ActivityExtensions +{ + public static StateChangeRequirement RequiredToChangeState(this Activity activity, ActivityState targetState) + { + return activity.State switch + { + ActivityState.Accepted => targetState switch + { + ActivityState.Accepted => StateChangeRequirement.AdminOrPermission, + ActivityState.ManualReview => StateChangeRequirement.AdminOrPermission, + ActivityState.PendingReferral when activity.Type == ActivityType.Referral => StateChangeRequirement.AdminOrPermission, + ActivityState.PendingTransfer => StateChangeRequirement.AdminOrPermission, + ActivityState.Rejected => StateChangeRequirement.AdminOrPermission, + _ => StateChangeRequirement.NotAllowed + }, + ActivityState.Created => targetState switch + { + ActivityState.Accepted => StateChangeRequirement.SystemOnly, + ActivityState.ManualReview => StateChangeRequirement.SystemOnly, + ActivityState.PendingReferral => StateChangeRequirement.SystemOnly, + ActivityState.Rejected => StateChangeRequirement.SystemOnly, + _ => StateChangeRequirement.NotAllowed + }, + ActivityState.ManualReview => targetState switch + { + ActivityState.Accepted => StateChangeRequirement.AdminOrPermission, + ActivityState.Rejected => StateChangeRequirement.Hierarchical, + _ => StateChangeRequirement.SystemOnly + }, + ActivityState.PendingReferral => targetState switch + { + ActivityState.Accepted => StateChangeRequirement.AdminOrPermission, + ActivityState.Transferred => StateChangeRequirement.Hierarchical, + _ => StateChangeRequirement.NotAllowed + }, + ActivityState.PendingTransfer => targetState switch + { + ActivityState.Accepted => StateChangeRequirement.AdminOrPermission, + ActivityState.Transferred => StateChangeRequirement.Hierarchical, + _ => StateChangeRequirement.NotAllowed + }, + ActivityState.Rejected => targetState switch + { + ActivityState.Accepted => StateChangeRequirement.AdminOrPermission, + ActivityState.ManualReview => StateChangeRequirement.AdminOrPermission, + ActivityState.PendingReferral when activity.Type == ActivityType.Referral => StateChangeRequirement.AdminOrPermission, + _ => StateChangeRequirement.NotAllowed + }, + ActivityState.Transferred => StateChangeRequirement.NotAllowed, + _ => StateChangeRequirement.NotAllowed + }; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Extensions/EntityFrameworkOrderBy.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Extensions/EntityFrameworkOrderBy.cs new file mode 100644 index 0000000..1246a7c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Extensions/EntityFrameworkOrderBy.cs @@ -0,0 +1,56 @@ +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Extensions; + +public static class EntityFrameworkExtensions +{ + public static IOrderedQueryable OrderBy(this IQueryable query, string propertyName) + { + var entityType = typeof(TSource); + + var propertyInfo = entityType.GetProperty(propertyName); + ParameterExpression arg = Expression.Parameter(entityType, "x"); + MemberExpression property = Expression.Property(arg, propertyName); + var selector = Expression.Lambda(property, new ParameterExpression[] { arg }); + + var enumarableType = typeof(System.Linq.Queryable); + var method = enumarableType.GetMethods() + .Where(m => m.Name == "OrderBy" && m.IsGenericMethodDefinition) + .Where(m => + { + var parameters = m.GetParameters().ToList(); + return parameters.Count == 2; + }).Single(); + + MethodInfo genericMethod = method.MakeGenericMethod(entityType, propertyInfo.PropertyType); + + var newQuery = (IOrderedQueryable)genericMethod.Invoke(genericMethod, new object[] { query, selector }); + return newQuery; + } + + public static IOrderedQueryable OrderByDescending(this IQueryable query, string propertyName) + { + var entityType = typeof(TSource); + + var propertyInfo = entityType.GetProperty(propertyName); + ParameterExpression arg = Expression.Parameter(entityType, "x"); + MemberExpression property = Expression.Property(arg, propertyName); + var selector = Expression.Lambda(property, new ParameterExpression[] { arg }); + + var enumarableType = typeof(System.Linq.Queryable); + var method = enumarableType.GetMethods() + .Where(m => m.Name == "OrderByDescending" && m.IsGenericMethodDefinition) + .Where(m => + { + var parameters = m.GetParameters().ToList(); + return parameters.Count == 2; + }).Single(); + + MethodInfo genericMethod = method.MakeGenericMethod(entityType, propertyInfo.PropertyType); + + var newQuery = (IOrderedQueryable)genericMethod.Invoke(genericMethod, new object[] { query, selector }); + return newQuery; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Extensions/ObjectExtensions.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Extensions/ObjectExtensions.cs new file mode 100644 index 0000000..628a44c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Extensions/ObjectExtensions.cs @@ -0,0 +1,37 @@ +using LCU.Library.Internal.IncentiveApiModels.Common; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Extensions; + +public static class ObjectExtensions +{ + public static IEnumerable GetPropertyValueVariances(this T objectInstanceOne, T objectInstanceTwo) + { + var propertyValueVariances = new List(); + var propertyInfoCollection = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(property => property.GetCustomAttributes().Any(attribute => attribute is UpdateAllowed)); + + foreach (var propertyInfo in propertyInfoCollection) + { + if (!Equals(propertyInfo.GetValue(objectInstanceOne), propertyInfo.GetValue(objectInstanceTwo))) + { + propertyValueVariances.Add(new PropertyValueVariance + { + ComparedProperty = propertyInfo.Name, + PropertyValueOne = GetPropertyValueOrDefaultString(objectInstanceOne, propertyInfo), + PropertyValueTwo = GetPropertyValueOrDefaultString(objectInstanceTwo, propertyInfo), + }); + } + } + + return propertyValueVariances; + } + + private static string GetPropertyValueOrDefaultString(this T queryObject, PropertyInfo propertyInfo) + { + var propertyValue = propertyInfo.GetValue(queryObject); + return propertyValue != null ? propertyValue.ToString() : "NULL"; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/LCU.API.Internal.Operations.IncentiveApiLib.csproj b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/LCU.API.Internal.Operations.IncentiveApiLib.csproj new file mode 100644 index 0000000..fb11d07 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/LCU.API.Internal.Operations.IncentiveApiLib.csproj @@ -0,0 +1,41 @@ + + + + net8.0 + LCU.API.Internal.Operations.IncentiveApiLib + 1.2.3 + 1.2.3 + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Persistence/ApplicationDbContext.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Persistence/ApplicationDbContext.cs new file mode 100644 index 0000000..66c0c9b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Persistence/ApplicationDbContext.cs @@ -0,0 +1,760 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Microsoft.EntityFrameworkCore; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Persistence; + +public class ApplicationDbContext : DbContext, IApplicationDbContext +{ + private readonly ICurrentUserService _currentUserService; + private readonly IDateTime _dateTime; + + public ApplicationDbContext( + DbContextOptions options, + ICurrentUserService currentUserService, + IDateTime dateTime + ) : base(options) + { + _currentUserService = currentUserService; + _dateTime = dateTime; + } + + public DbSet Goals { get; set; } + public DbSet GoalChanges { get; set; } + public DbSet Activities { get; set; } + public DbSet ActivityDetails { get; set; } + public DbSet ActivityStateChanges { get; set; } + public DbSet ActivityDetailUpdates { get; set; } + public DbSet Departments { get; set; } + public DbSet AppUsers { get; set; } + public DbSet DepartmentAssignments { get; set; } + public DbSet Commissions { get; set; } + public DbSet ProcessorRunHistory { get; set; } + public DbSet UserPermissions { get; set; } + public DbSet PayoutLineItems { get; set; } + public DbSet UserAssignments { get; set; } + public DbSet RoleMappings { get; set; } + public DbSet ActivityTransferActions { get; set; } + public DbSet DeadActivities { get; set; } + public DbSet DepartmentLineItems { get; set; } + public DbSet UserDepartmentHistories { get; set; } + public DbSet UserPermissionHistories { get; set; } + + public override Task SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken()) + { + var currentUser = _currentUserService.GetCurrentUser(); + var currentAppUser = ChangeTracker.Context.Set().FirstOrDefault(x => x.AdEmail == currentUser.Email); + var currentUserGuid = currentAppUser?.Id ?? Guid.Empty; + + foreach (var entry in ChangeTracker.Entries()) + { + if (entry.State == EntityState.Added) + { + entry.Entity.CreatedByType = currentUser.UserType(); + entry.Entity.CreatedBy = currentUser.UserName(); + entry.Entity.CreatedByAppUserId = currentUserGuid; + entry.Entity.Created = _dateTime.Now; + } + } + + foreach (var entry in ChangeTracker.Entries()) + { + if (entry.State == EntityState.Modified || entry.State == EntityState.Added) + { + entry.Entity.LastUpdatedByType = currentUser.UserType(); + entry.Entity.LastUpdatedBy = currentUser.UserName(); + entry.Entity.LastUpdatedByAppUserId = currentUserGuid; + entry.Entity.LastUpdated = _dateTime.Now; + } + } + + return base.SaveChangesAsync(cancellationToken); + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + builder.Entity() + .ToTable("ActivityPayouts") + .HasKey(apli => new { apli.ActivityId, apli.PayoutLineItemId }); + + builder.Entity() + .HasOne(bg => bg.Activity) + .WithMany(b => b.ActivityPayouts) + .HasForeignKey(bg => bg.ActivityId); + + builder.Entity() + .HasOne(bg => bg.PayoutLineItem) + .WithMany(g => g.ActivityPayouts) + .HasForeignKey(bg => bg.PayoutLineItemId); + + builder.Entity().ToTable("Activity"); + builder.Entity().HasKey(p => p.Id); + builder.Entity() + .Property(e => e.Product) + .IsRequired() + .HasConversion(x => x.ToString(), + x => ConvertEnumOrDefault(x, Product.Unknown)); + builder.Entity() + .Property(e => e.Type) + .IsRequired() + .HasConversion(x => x.ToString(), + x => ConvertEnumOrDefault(x, ActivityType.Unknown)); + builder.Entity() + .Property(e => e.SourceSystem) + .IsRequired() + .HasConversion(x => x.ToString(), + x => ConvertEnumOrDefault(x, SystemType.Unknown)); + builder.Entity().Property(e => e.SourceId).IsRequired(); + builder.Entity().Property(e => e.SourceCreated).IsRequired(); + builder.Entity() + .HasOne(e => e.Detail) + .WithOne(b => b.Activity) + .HasForeignKey(b => b.ActivityRef) + .IsRequired(); + builder.Entity() + .HasMany(e => e.MetaData) + .WithOne(b => b.Activity) + .HasForeignKey(b => b.ActivityRef); + builder.Entity().Property(e => e.DepartmentId).IsRequired(); + builder.Entity().Property(e => e.EmployeeId).IsRequired(); + builder.Entity().Property(e => e.EmployeeEmail); + builder.Entity().Property(e => e.MatchingActivityId).HasDefaultValue(0); + builder.Entity().Property(e => e.MatchingDpAccessoryActivityId).HasDefaultValue(0); + builder.Entity().Property(e => e.MatchingGapInsuranceActivityId).HasDefaultValue(0); + builder.Entity().Property(e => e.Accepted).IsRequired(); + builder.Entity() + .Property(e => e.State) + .IsRequired() + .HasConversion(x => x.ToString(), + x => ConvertEnumOrDefault(x, ActivityState.Unknown)); + builder.Entity().HasIndex(e => e.Accepted); + builder.Entity().HasIndex(e => new { e.Accepted, e.Id }); + + builder.Entity().ToTable("ActivityDetail"); + builder.Entity().HasKey(p => p.Id); + builder.Entity().HasIndex(p => new { p.ActivityRef, p.Id }); + builder.Entity().Property(p => p.SubAccountNumber).HasDefaultValue(""); + + builder.Entity().ToTable("ActivityMetaData"); + builder.Entity().HasKey(p => p.Id); + builder.Entity().Property(p => p.MetaData); + builder.Entity() + .Property(p => p.MetaDataType) + .IsRequired() + .HasConversion(x => x.ToString(), + x => ConvertEnumOrDefault(x, MetaDataType.Unknown)); + builder.Entity().HasIndex(p => new { p.ActivityRef, p.Id }); + + builder.Entity().ToTable("Goals"); + builder.Entity().HasKey(p => p.Id); + builder.Entity() + .Property(e => e.Product) + .IsRequired() + .HasConversion(x => x.ToString(), + x => ConvertEnumOrDefault(x, Product.Unknown)); + builder.Entity().Property(e => e.StartDate).IsRequired(); + builder.Entity().Property(e => e.EndDate).IsRequired(); + builder.Entity().Property(e => e.EmployeeId); + builder.Entity().Property(e => e.DepartmentId); + builder.Entity().Property(e => e.Value).IsRequired(); + builder.Entity().HasIndex(e => new { e.DepartmentId, e.StartDate, e.EndDate }); + builder.Entity().HasIndex(e => new { e.DepartmentId, e.StartDate, e.EndDate, e.Id }); + + builder.Entity().ToTable("GoalChanges"); + builder.Entity().HasKey(p => p.Id); + builder.Entity() + .Property(e => e.Product) + .IsRequired() + .HasConversion(x => x.ToString(), + x => ConvertEnumOrDefault(x, Product.Unknown)); + builder.Entity().Property(e => e.StartDate).IsRequired(); + builder.Entity().Property(e => e.EndDate).IsRequired(); + builder.Entity().Property(e => e.Value).IsRequired(); + + builder.Entity().ToTable("Departments"); + builder.Entity().HasKey(p => p.Id); + builder.Entity().HasIndex(e => e.DepartmentName).IsUnique(); + builder.Entity().Property(e => e.BusinessLineCode).IsRequired(); + builder.Entity().Property(e => e.RegionCode); + builder.Entity().Property(e => e.DayforceDepartmentName).IsRequired(); + builder.Entity().Property(e => e.DayForceGlCode).IsRequired(); + builder.Entity().Property(e => e.DnaOrgNbr).IsRequired(); + builder.Entity() + .Property(e => e.State) + .IsRequired() + .HasConversion(x => x.ToString(), + x => ConvertEnumOrDefault(x, DepartmentState.Unknown)); + + builder.Entity().ToTable("AppUser"); + builder.Entity().HasKey(e => e.Id); + builder.Entity().Property(e => e.HomeDepartment).IsRequired(); + builder.Entity().Property(e => e.FullName).IsRequired(); + builder.Entity().Property(e => e.ManagerKey); + builder.Entity().Property(e => e.Terminated); + builder.Entity() + .Property(e => e.Role) + .HasConversion( + x => x.Name, + x => Role.DetermineRoleOrDefault(x) + ) + .IsRequired(); + builder.Entity().Property(e => e.BaseRole).IsRequired(); + builder.Entity().HasMany(e => e.AdditionalPermissions).WithOne(e => e.AppUser); + + builder.Entity().ToTable("UserPermissions"); + builder.Entity().HasKey(e => e.Id); + builder.Entity() + .HasOne(e => e.AppUser) + .WithMany(e => e.AdditionalPermissions) + .IsRequired(); + builder.Entity() + .Property(e => e.Permission) + .IsRequired() + .HasConversion( + x => x.Code, + x => Permission.GetAllPermissions(false).FirstOrDefault(perm => perm.Code.Equals(x)) ?? Permission.Unknown); + + builder.Entity().ToTable("DeptAssignments"); + builder.Entity().HasKey(p => p.Id); + builder.Entity().Property(e => e.StartDate).IsRequired(); + builder.Entity().Property(e => e.EndDate); + builder.Entity().Property(e => e.Note); + builder.Entity().Property(e => e.SlidingPayout).HasColumnType("DECIMAL(5,2)").IsRequired().HasDefaultValue(0); + builder.Entity().Property(e => e.IsHome).IsRequired(); + builder.Entity() + .HasOne(p => p.Employee) + .WithMany(b => b.Departments) + .IsRequired(); + builder.Entity() + .HasOne(p => p.Department) + .WithMany(b => b.Assignments) + .IsRequired(); + builder.Entity() + .Property(e => e.Role) + .HasConversion( + x => x.Name, + x => Role.DetermineRoleOrDefault(x) + ) + .IsRequired(); + + builder.Entity().ToTable("ActivityStateChanges"); + builder.Entity().HasKey(p => p.Id); + builder.Entity() + .HasOne() + .WithMany() + .HasForeignKey(p => p.ActivityId) + .IsRequired(); + builder.Entity() + .Property(e => e.State) + .IsRequired() + .HasConversion( + x => x.ToString(), + x => ConvertEnumOrDefault(x, ActivityState.Unknown)) + .IsRequired(); + builder.Entity().Property(e => e.AcceptedDate); + builder.Entity().Property(e => e.Note); + + builder.Entity().ToTable("ActivityDetailUpdates"); + builder.Entity().HasKey(p => p.Id); + builder.Entity().HasOne().WithMany().HasForeignKey(p => p.ActivityDetailId).IsRequired(); + builder.Entity().Property(e => e.Note); + + builder.Entity().ToTable("Commissions"); + builder.Entity().HasKey(p => p.Id); + builder.Entity() + .Property(p => p.Product) + .IsRequired() + .HasConversion( + x => x.ToString(), + x => ConvertEnumOrDefault(x, Product.Unknown) + ) + .IsRequired(); + builder.Entity().Property(p => p.RatePerProduct).IsRequired(); + builder.Entity() + .Property(p => p.Role) + .IsRequired() + .HasConversion( + x => x.Name, + x => Role.DetermineRoleOrDefault(x) + ) + .IsRequired(); + builder.Entity().HasIndex(p => p.Role); + + builder.Entity().ToTable("ProcessorRunHistory"); + builder.Entity().HasKey(p => p.Process); + builder.Entity() + .Property(e => e.Process) + .HasConversion( + x => x.ToString(), + x => ConvertEnumOrDefault(x, Process.Unknown)) + .IsRequired(); + builder.Entity().Property(e => e.LastSuccessfulRunTime); + builder.Entity().Property(e => e.LastProcessedRecordId); + builder.Entity().Property(e => e.LastProcessedRecordTimestamp); + + builder.Entity().ToTable("PayoutLineItems"); + builder.Entity().HasKey(p => p.Id); + builder.Entity() + .Property(p => p.Product) + .IsRequired() + .HasConversion( + x => x.ToString(), + x => ConvertEnumOrDefault(x, Product.Unknown)); + builder.Entity() + .Property(p => p.Role) + .IsRequired() + .HasConversion( + x => x.Name, + x => Role.DetermineRoleOrDefault(x) + ); + builder.Entity().Property(p => p.EmployeeId).IsRequired(); + builder.Entity().Property(p => p.DepartmentId).IsRequired(); + builder.Entity().Property(p => p.ActivityPeriod).IsRequired(); + builder.Entity().Property(p => p.ActivityTotal).HasColumnType("DECIMAL(12,2)").IsRequired(); + builder.Entity().Property(p => p.PayoutTotal).HasColumnType("DECIMAL(12,2)").IsRequired(); + builder.Entity().Property(p => p.SlidingPayout).HasColumnType("DECIMAL(5,2)").IsRequired(); + + builder.Entity().ToTable("UserAssignments").HasKey(p => p.Id); + builder.Entity() + .Property(p => p.Type) + .IsRequired() + .HasConversion( + x => x.ToString(), + x => ConvertEnumOrDefault(x, AssignmentType.Unknown)); + builder.Entity().Property(p => p.Key).IsRequired(); + builder.Entity().HasOne(p => p.AppUser).WithMany(p => p.Assignments).IsRequired(); + + builder.Entity().ToTable("ActivityTransferActions").HasKey(p => p.Id); + builder.Entity() + .Property(p => p.ActivityStateBeforeTransfer) + .IsRequired() + .HasConversion( + x => x.ToString(), + x => ConvertEnumOrDefault(x, ActivityState.Unknown) + ); + builder.Entity().Property(p => p.RequestedActivityId).IsRequired(); + builder.Entity() + .Property(p => p.State) + .IsRequired() + .HasConversion( + x => x.ToString(), + x => ConvertEnumOrDefault(x, ActivityTransferState.Unknown) + ); + builder.Entity().Property(p => p.NewActivityId); + builder.Entity().Property(p => p.NewActivityDepartmentId).IsRequired(); + builder.Entity().Property(p => p.NewActivityEmployeeId).IsRequired(); + builder.Entity().Property(p => p.RequestedDepartmentApproved); + builder.Entity().Property(p => p.RequestedDepartmentApprover); + builder.Entity().Property(p => p.NewDepartmentApproved); + builder.Entity().Property(p => p.NewDepartmentApprover); + + builder.Entity().ToTable("DeadActivity"); + builder.Entity().HasKey(p => p.Id); + builder.Entity() + .Property(e => e.Product) + .HasConversion(x => x.ToString(), + x => ConvertEnumOrDefault(x, Product.Unknown)); + builder.Entity() + .Property(e => e.Reason) + .HasConversion(x => x.ToString(), + x => ConvertEnumOrDefault(x, DeadActivityReason.Unknown)) + .IsRequired(); + builder.Entity().Property(e => e.ReasonNote); + builder.Entity() + .Property(e => e.Type) + .HasConversion(x => x.ToString(), + x => ConvertEnumOrDefault(x, ActivityType.Unknown)); + builder.Entity() + .Property(e => e.SourceSystem) + .HasConversion(x => x.ToString(), + x => ConvertEnumOrDefault(x, SystemType.Unknown)); + builder.Entity().Property(e => e.SourceId); + builder.Entity().Property(e => e.SourceCreated); + builder.Entity() + .HasOne(e => e.DeadDetail) + .WithOne(b => b.DeadActivity) + .HasForeignKey(b => b.DeadActivityRef); + builder.Entity().Property(e => e.DepartmentId); + builder.Entity().Property(e => e.EmployeeId); + builder.Entity().Property(e => e.EmployeeEmail); + builder.Entity().Property(e => e.Accepted); + builder.Entity() + .Property(e => e.State) + .HasConversion(x => x.ToString(), + x => ConvertEnumOrDefault(x, ActivityState.Unknown)); + + builder.Entity().ToTable("DeadActivityDetail"); + builder.Entity().HasKey(p => p.Id); + + builder.Entity().ToTable("DepartmentLineItems"); + builder.Entity().HasKey(p => p.Id); + builder.Entity().Property(p => p.PayoutTotal).HasColumnType("DECIMAL(12,2)").IsRequired(); + builder.Entity().Property(p => p.PayoutPercentage).HasColumnType("DECIMAL(5,2)").IsRequired(); + + builder.Entity().ToTable("UserDepartmentHistory"); + builder.Entity().HasKey(p => p.Id); + + builder.Entity().ToTable("UserPermissionHistory"); + builder.Entity().HasKey(p => p.Id); + + builder.Entity().ToTable("RoleMappings"); + builder.Entity().HasKey(r => r.Id); + builder.Entity().Property(r => r.Id); + builder.Entity() + .Property(r => r.ApplicationRole) + .HasConversion( + x => x.Name, + x => Role.DetermineRoleOrDefault(x) + ) + .IsRequired(); + builder.Entity().Property(r => r.BusinessRole); + } + + private static T ConvertEnumOrDefault(string value, T defaultValue) + { + try + { + return (T)Enum.Parse(typeof(T), value); + } + catch (Exception) + { + return defaultValue; + } + } + + public async Task SeedRoleMappingData() + { + try + { + if (RoleMappings.Any()) + { + return; + } + + RoleMappings.AddRange(new List + { + new() { ApplicationRole = Role.Teller, BusinessRole = "ACH REPRESENTATIVE" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "ACH SUPERVISOR" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "ADMIN ASSISTANT" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "ADMIN ASST BUS LEND" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "APPLICATION SUP SPEC" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "APPLICATIONS SUPPORT MANAGER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "ASSOCIATE COUNSEL - COMPLIANCE" }, + new() { ApplicationRole = Role.ABM, BusinessRole = "ASST BRANCH MANAGER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "ASST MGR IND LENDING" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "AVP / MANAGER, CARD OPERATIONS" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "AVP LOAN SERVICING" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "AVP FACILITIES & PURCHASING" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "AVP, BUSINESS LENDING OPERATIONS" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "AVP, CARD PRODUCT MGMT" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "AVP, COLLECTIONS" }, + new() { ApplicationRole = Role.Manager, BusinessRole = "AVP, DIGITAL & ONLINE SERVICING" }, + new() { ApplicationRole = Role.Manager, BusinessRole = "AVP, MEMBER SERVICE" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "AVP/ACCOUNTING" }, + new() { ApplicationRole = Role.BM, BusinessRole = "AVP/BRANCH MANAGER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "AVP/BUSINESS LENDING" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "AVP/FACILITIES" }, + new() { ApplicationRole = Role.Manager, BusinessRole = "AVP/MANAGER OPS" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "AVP/MANAGER, CARD OPERATIONS" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "AVP/MARKETING" }, + new() { ApplicationRole = Role.Manager, BusinessRole = "AVP/MGR CONS LEN MSC" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "AVP/MTG OPERATIONS" }, + new() { ApplicationRole = Role.Manager, BusinessRole = "AVP/MTG SALES" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "BENEFITS ANALYST" }, + new() { ApplicationRole = Role.BM, BusinessRole = "BRANCH MANAGER" }, + new() { ApplicationRole = Role.BM, BusinessRole = "BRANCH MANAGER - CORE PROJECT" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "BRANCH ONBOARDING COORDINATOR" }, + new() { ApplicationRole = Role.BOM, BusinessRole = "BRANCH OP MANAGER" }, + new() { ApplicationRole = Role.BusinessLineManager, BusinessRole = "BRANCH SUPPORT SPECIALIST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "BSA ASSOCIATE" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "BSA LEAD" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "BSA MANAGER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "BUS CREDIT ANALYST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "BUS LN DOC & CLOSING SPEC" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "BUS LN SERVICING SPEC" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "BUSINESS ANALYST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "BUSINESS INTELLIGENCE ENGINEER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "CARD PROJECT ANALYST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "CASH MANAGEMENT SPECIALIST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "CFO" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "CHIEF INFORMATION & RISK OFFICER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "CHIEF PEOPLE OFFICER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "CIO" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "CL PROC MANAGER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "CL PROC SUPERVISOR" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "CLERK" }, + new() { ApplicationRole = Role.LIC, BusinessRole = "CLIENT SRVC ASST-LIC" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "CLO" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "CLV SUPERVISOR" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "COLL SPEC-BANKRUPTCY" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "COLL SPEC-CRED BUREA" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "COLL SPEC-FRAUD" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "COLL SPEC-LOSS MIT" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "COLL SPEC-PROBATE" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "COLL SPEC-RECOVERY" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "COLL SPEC-REO FORE" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "COLL SPEC-REPOS" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "COLL SPEC-SKIP TRACE" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "COLLECT BUS ANALYST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "COLLECTION CLERK" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "COLLECTIONS MANAGEMENT SYSTEM ADMINISTRATOR" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "COLLECTOR" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "COMMS SPECIALIST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "COMPENSATION MANAGER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "COMPLIANCE ANALYST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "COMPLIANCE MANAGER - FAIR LENDING" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "COMPLIANCE MGR-ADVISORY" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "COMPLIANCE OFFICER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "COMPLIANCE SPECIALIST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "COMPLIANCE TRAINER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "CONS LN PROC AUD CLE" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "CONS LN PROCESSOR" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "CONS LN COORDINATOR" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "CONS LN VAULT CLERK" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "CREDIT CARD MEMBER SERVICE REP" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "CREDIT OPERATIONS COORDINATOR" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "CXO" }, + new() { ApplicationRole = Role.Developer, BusinessRole = "DATA ARCHITECT" }, + new() { ApplicationRole = Role.Developer, BusinessRole = "DATA ANALYST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "DEALER RELATION MGR" }, + new() { ApplicationRole = Role.Developer, BusinessRole = "DEV OPS ENGINEER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "DESKTOP & FIELD SUPPORT SPECIALIST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "DIGITAL PROGRAM MANAGER" }, + new() { ApplicationRole = Role.MSR, BusinessRole = "DIGITAL REPRESENTATIVE" }, + new() { ApplicationRole = Role.MSR, BusinessRole = "DIGITAL REPRESENTATIVE (BILINGUAL)" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "DIR FIN PLAN & ANALY" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "DIR MGMNT REPORTING" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "DIR SECURITY/FRAUD" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "DIRECTOR COMMUNITY RELATIONS & EVENTS" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "DIRECTOR INV & TREAS" }, + new() { ApplicationRole = Role.Manager, BusinessRole = "DIRECTOR MEMBER RELATIONSHIPS" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "ENHANCED DUE DILIGEN" }, + new() { ApplicationRole = Role.Developer, BusinessRole = "ENTERPRISE ARCHITECT" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "ENTERPRISE RISK MGMT ANALYST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "ENTERPRISE RISK MGR" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "EVP" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "EXECUTIVE ASSISTANT" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "FACILITIES CLERK" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "FACILITIES SPECIALIST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "FINANCIAL ANALYST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "FINANCIAL OPERATIONS ANALYST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "FRAUD PREV ASSOCIATE - DEBIT CARDS" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "FRAUD PREVENTION SPECIALIST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "GRAPHIC DESIGNER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "HR ADVISOR" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "HR BUSINESS PARTNER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "HR CONSULTANT" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "HR COORDINATOR" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "IND LN/LS CLERK" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "IND LN/LS OFFICER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "IND LN/LS OFFICER N TRNG" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "IND LN/LS OFFICER PM" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "IND LN/LS PROC SUPV" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "IND LN/LS OFFICER N PM" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "IND LOAN PROC" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "IND LOAN UNDERWRITER PM" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "INF SECURITY ANALYST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "INF SECURITY OFFICER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "INFRAST & OPER MGR" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "INFRASTRUCTURE SPECIALIST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "INFRASTRUCTURE SUPPORT SPECIALIST - SHAREPOINT & O365" }, + new() { ApplicationRole = Role.Mortgage, BusinessRole = "INTERNET MLO" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "INTERNAL COMM & ENGMNT SPECIALIST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "INV/SECURITY" }, + new() { ApplicationRole = Role.OpsHelpDesk, BusinessRole = "IRA ADMINISTRATOR" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "IT DELIVERY MANAGER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "IT HELPDESK OPS SUP" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "IT HELPDESK/OPS I" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "IT HELPDESK/OPS II" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "ITEM PROCESSING CLK" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "LEAD COLLECT CLERK" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "LEAD IT DESK FIELD" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "LEAD PROCESS CLERK" }, + new() { ApplicationRole = Role.Teller, BusinessRole = "LEAD TELLER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "LEGAL ANALYST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MAIL PROCESSING CLERK" }, + new() { ApplicationRole = Role.ManagementTrainee, BusinessRole = "MANAGEMENT TRAINEE" }, + new() { ApplicationRole = Role.Manager, BusinessRole = "MANAGER MSC" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MANAGER, HUMAN RESOURCES" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MANAGER, INTERNAL AUDIT" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MANAGER MTG SERVICING" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MANAGER, TALENT ACQUISITION" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MARKETING ASSISTANT" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MARKETING COORDINATOR, CONTENT" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MARKETING COORDINATOR, CREATIVE" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MARKETING TRAFFIC & PROD MGR" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MEMBER EXPERIENCE ANALYST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MEMBER EXP ANALYST" }, + new() { ApplicationRole = Role.MSCTeamLead, BusinessRole = "MEMBER SERVICE ONBOARDING COORDINATOR" }, + new() { ApplicationRole = Role.MSR, BusinessRole = "MEMBER SERVICE REP" }, + new() { ApplicationRole = Role.MSR, BusinessRole = "MEMBER SERVICE REP (BILINGUAL)" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MGR BUS LEND CR OPS" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MGR CONSUMER LOAN UNDERWRITING" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MGR INDIRECT LOAN UNDERWRITING" }, + new() { ApplicationRole = Role.Manager, BusinessRole = "MGR MEMBER SVS CTR" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MORTGAGE CLOSING SUPERVISOR" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MORTGAGE DISCLOSURE DESK REPRESENTATIVE" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MORTGAGE QUALITY ASSURANCE SPECIALST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MRKTNG BUS ANALYST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MTG CLOSING CLERK" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MTG CLOSING COORDTR" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MTG LN SERV SUPV" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MTG LN SV SPECIALIST" }, + new() { ApplicationRole = Role.Mortgage, BusinessRole = "MTG LOAN ORIGINATOR" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MTG LOAN PROCESSOR" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MTG LOAN UNDERWRITER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MTG PROCESSING CLERK" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MTG PROCESSING SUP" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MTG SERVICING REP" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "MTG UNDERWRITING SUP" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "NET SUPP SPECIALIST" }, + new() { ApplicationRole = Role.MSR, BusinessRole = "ONLINE EXPERIENCE SPECIALIST" }, + new() { ApplicationRole = Role.MSR, BusinessRole = "ONLINE EXPERIENCE SPECIALIST (BILINGUAL)" }, + new() { ApplicationRole = Role.OpsHelpDesk, BusinessRole = "OP HELP DESK REP" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "OPERATIONS MANAGER" }, + new() { ApplicationRole = Role.Admin, BusinessRole = "OPERATIONS QUALITY CONTROL ANALYST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "OPERATIONS SUPP MGR" }, + new() { ApplicationRole = Role.OpsHelpDesk, BusinessRole = "OPERATIONS SUPP SPEC" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "ORG CHANGE/DEV LD" }, + new() { ApplicationRole = Role.MSR, BusinessRole = "OUTBOUND MEMBER RELATION SPCL" }, + new() { ApplicationRole = Role.MSR, BusinessRole = "OUTBOUND MEMBER RELATION SPCL (BILINGUAL)" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "PAYROLL & HRIS ADMINISTRATOR" }, + new() { ApplicationRole = Role.PFO, BusinessRole = "PERS FIN OFFICER" }, + new() { ApplicationRole = Role.PFO, BusinessRole = "PERS FIN OFFICER - MSC" }, + new() { ApplicationRole = Role.PFO, BusinessRole = "PERS FIN OFFICER - MSC (BILINGUAL)" }, + new() { ApplicationRole = Role.PFO, BusinessRole = "PFO IN TRAINING" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "PHYSICAL SECURITY MANAGER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "PORT MGR BUS LENDING" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "PRESIDENT/CEO" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "PROCESS ENGINEER MANAGER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "PRODUCT MANAGER, DIGITAL BANKING" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "PRODUCT MANAGER, RETAIL DEPOSITS" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "PROJECT MANAGER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "PURCH SPECIALIST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "PURCHASING ASSISTANT" }, + new() { ApplicationRole = Role.MSCTeamLead, BusinessRole = "QUALITY ASSUR SPCLST" }, + new() { ApplicationRole = Role.MSCTeamLead, BusinessRole = "QUALITY ASSURANCE SPECIALIST - MSC" }, + new() { ApplicationRole = Role.Developer, BusinessRole = "QUALITY ASSURANCE ANALYST" }, + new() { ApplicationRole = Role.Teller, BusinessRole = "RECEPTIONIST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "RECRUITER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SECURITY SERVICES" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SOC MEDIA SPECIALIST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SOFT ENG/DATA" }, + new() { ApplicationRole = Role.Developer, BusinessRole = "SOFTWARE ENG" }, + new() { ApplicationRole = Role.Developer, BusinessRole = "SOFTWARE ENGINEERING MANAGER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR ADMIN ASSISTANT" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR BSA ASSOC" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR BUS CRED ANALYST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR. BUSINESS ANALYST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR BUS LN DOC & CLOSING SPEC" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR COMPENSATION ANALYST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR COMPLIANCE ANALYST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR CONS LN PROCESSOR" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR CONS LN UNDERWRITER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR DATABASE ADMINISTRATOR" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR ENHANCED DUE DILIGENCE ANALYST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR FIN ANALYST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR IND LN PROC" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR IND LN/LS OFFICER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR IND LN/LS OFFICER PM" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR IND LOAN UNDERWRITER PM" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR IND LOAN UNDERWRITER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR INFRAS SPECIALIST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR INTERNAL AUDITOR" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR INV/SECURITY" }, + new() { ApplicationRole = Role.LIC, BusinessRole = "SR INVESTMENT REP" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR LEADERSHIP DEV ADVISOR" }, + new() { ApplicationRole = Role.MSR, BusinessRole = "SR MEMBER SERV REP" }, + new() { ApplicationRole = Role.MSR, BusinessRole = "SR MEMBER SERV REP (BILINGUAL)" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR MTG LOAN PROCESSOR" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR MORTGAGE CLOSING COORDINATOR" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR MORTGAGE SERVICES SYSTEMS ADMINISTRATOR" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR MTG LOAN UNDERWRITER" }, + new() { ApplicationRole = Role.PFO, BusinessRole = "SR ORG CHANGE & COMMUNICATIONS ADVISOR" }, + new() { ApplicationRole = Role.PFO, BusinessRole = "SR PERS FIN OFFICER" }, + new() { ApplicationRole = Role.PFO, BusinessRole = "SR PERS FIN OFFICER - MSC" }, + new() { ApplicationRole = Role.Developer, BusinessRole = "SR SOFTWARE ENG" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR STAFF ACCOUNTANT" }, + new() { ApplicationRole = Role.Teller, BusinessRole = "SR TELLER II (FLOATER)" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR BUSINESS ANALYST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SR THIRD PARTY MANAGEMENT ANALYST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "STAFF ACCOUNTANT" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "STAFF COUNSEL" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "STAFF INT AUDITOR" }, + new() { ApplicationRole = Role.Manager, BusinessRole = "SUPERVISOR, MORTGAGE LENDING" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SUPVR COLLECTIONS" }, + new() { ApplicationRole = Role.BusinessLineManager, BusinessRole = "SVP BR STRATEGY & DE" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SVP BUSINESS LENDING" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SVP DIGITAL STRATEGY" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SVP HR TRNG COMMUNIC" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "SVP LEGAL, RISK & COMPLIANCE" }, + new() { ApplicationRole = Role.Manager, BusinessRole = "SVP MORTGAGE SERVICES" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "TALENT ACQUISITION SPECIALIST" }, + new() { ApplicationRole = Role.MSCTeamLead, BusinessRole = "TEAM LDR - MSC LOANS" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "TEAM LDR-CLP" }, + new() { ApplicationRole = Role.MSCTeamLead, BusinessRole = "TEAM LDR-CONS LN-MSC" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "TEAM LDR-IND LNDG" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "TEAM LDR-IND LNDG PM" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "TEAM LDR-INDIRECT LOAN PM" }, + new() { ApplicationRole = Role.MSCTeamLead, BusinessRole = "TEAM LDR-MSC" }, + new() { ApplicationRole = Role.MSCTeamLead, BusinessRole = "TEAM LDR-MSC (BILINGUAL)" }, + new() { ApplicationRole = Role.LIC, BusinessRole = "TEAM LEAD - CLIENT SERVICES ASSISTANTS" }, + new() { ApplicationRole = Role.Mortgage, BusinessRole = "TEAM LEAD - MTG SERVICING" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "TEAM LEAD-COLLECTION" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "TEAM LEADER - CARD SERVICES" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "TECHNICAL WRITER" }, + new() { ApplicationRole = Role.Teller, BusinessRole = "TELLER I" }, + new() { ApplicationRole = Role.Teller, BusinessRole = "TELLER I (CO-OP)" }, + new() { ApplicationRole = Role.Teller, BusinessRole = "TELLER I (FLOATER)" }, + new() { ApplicationRole = Role.Teller, BusinessRole = "TELLER II" }, + new() { ApplicationRole = Role.Teller, BusinessRole = "TELLER II (FLOATER)" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "THIRD PARTY MANAGEMENT ANALYST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "TITLE CLERK" }, + new() { ApplicationRole = Role.MSCTeamLead, BusinessRole = "TM LDR DIGITAL BANK" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "TRAINER-MSC" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "TRAINER-PROF DEV/CHNG" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "TRNG ONBD SPECIALIST" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "TRNG OPER SPECIALIST" }, + new() { ApplicationRole = Role.UniversalSpecialistPFO, BusinessRole = "UNIVERSAL SPECIALIST" }, + new() { ApplicationRole = Role.UniversalSpecialistTeller, BusinessRole = "UNIVERSAL SPECIALIST (FLOATER)" }, + new() { ApplicationRole = Role.RegionalManager, BusinessRole = "VP BR SALES & SRVC" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "VP BUSINESS LENDING TEAM LEAD" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "VP CARD SERVICES" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "VP COLLECTIONS" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "VP COMM DEVELOPMENT" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "VP CONSUMER LENDING" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "VP CONTROLLER" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "VP HUMAN RESOURCES" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "VP IND LENDING" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "VP INTERNAL AUDIT" }, + new() { ApplicationRole = Role.LIC, BusinessRole = "VP LANDMARK INV CTR" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "VP MARKETING" }, + new() { ApplicationRole = Role.BusinessLineManager, BusinessRole = "VP MEMBER SERVICE CENTER" }, + new() { ApplicationRole = Role.Mortgage, BusinessRole = "VP MORTGAGE" }, + new() { ApplicationRole = Role.Manager, BusinessRole = "VP MORTGAGE SALES" }, + new() { ApplicationRole = Role.Admin, BusinessRole = "VP OPERATIONS" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "VP ORGANIZATIONAL CHANGE MANAGEMENT & COMMUNICATIONS" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "VP PROJ MGMNT" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "VP SOFTWARE ENG & DA" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "VP TRAINING & DEV" }, + new() { ApplicationRole = Role.UNKNOWN_ROLE, BusinessRole = "WEBSITE CONTENT SPECIALIST" } + }); + + await SaveChangesAsync(); + + } + catch + { + return; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Persistence/ApplicationDbIntegrityService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Persistence/ApplicationDbIntegrityService.cs new file mode 100644 index 0000000..ee30dc3 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Persistence/ApplicationDbIntegrityService.cs @@ -0,0 +1,772 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Results; +using Microsoft.EntityFrameworkCore; +using Serilog; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Persistence; + +public class ApplicationDbIntegrityService : IApplicationDbIntegrityService +{ + private readonly ApplicationDbContext _applicationDbContext; + private const string DataIntegrityFailureMessage = "AppUserId maintenance was required, the system was unable to carry out all of this maintenance in an automated fashion and this must be looked into immediately"; + private const string DataIntegrityPartialSuccessMessage = "AppUserId maintenance was required, it was carried out in an automated fashion but this should not have been required and needs to be looked into"; + private const string DataIntegritySuccessMessage = "Data integrity audit finished successfully, no maintenance was required"; + + public ApplicationDbIntegrityService(ApplicationDbContext applicationDbContext) + { + _applicationDbContext = applicationDbContext; + } + + public async Task AppUserIdIntegrityMaintenanceSucceeded(CancellationToken cancellationToken) + { + try + { + EnsureAuditCreateAndUpdateAppUserIds(); + + Log.Information("ApplicationDbIntegrity: RunDataIntegrityAudit method Started"); + + var allMaintenanceResults = new List + { + await DetermineActivityMaintenanceAuditResult(), + await DetermineActivityTransferActionMaintenanceAuditResult(), + await DetermineDeadActivityMaintenanceAuditResult(), + await DetermineGoalMaintenanceAuditResult(), + await DetermineGoalChangeMaintenanceAuditResult() + }; + + Log.Information("ApplicationDbIntegrity: RunDataIntegrityAudit method Ended"); + + var overallMaintenanceResult = await DetermineOverallDataIntegrityAuditResult(allMaintenanceResults); + + if (!overallMaintenanceResult.MaintenanceAutomationFailed && !overallMaintenanceResult.MaintenanceWasRequired) + { + Log.Information(DataIntegritySuccessMessage); + return true; + } + + if (!overallMaintenanceResult.MaintenanceAutomationFailed && overallMaintenanceResult.MaintenanceWasRequired) + { + Log.Information(DataIntegrityPartialSuccessMessage); + throw new Exception(DataIntegrityPartialSuccessMessage); + } + + if (!overallMaintenanceResult.MaintenanceAutomationFailed && overallMaintenanceResult.MaintenanceWasRequired) + { + Log.Information(DataIntegrityFailureMessage); + throw new Exception(DataIntegrityFailureMessage); + } + + return false; + } + catch (Exception ex) + { + Log.Error(ex, $"Failed to execute AppUserId integrity checks, Error: {ex.Message}"); + return false; + } + } + + private async Task DetermineOverallDataIntegrityAuditResult(List dataIntegrityAuditResults) + { + return await Task.FromResult( + new DataIntegrityAuditResult + { + MaintenanceAutomationFailed = dataIntegrityAuditResults.Any(x => x.MaintenanceAutomationFailed), + MaintenanceWasRequired = dataIntegrityAuditResults.Any(x => x.MaintenanceWasRequired) + }); + } + + private async Task DetermineActivityMaintenanceAuditResult() + { + var dataIntegrityAuditResult = new DataIntegrityAuditResult(); + var targetEmployeeIds = _applicationDbContext.Activities.Where(x => x.EmployeeId != null && x.AppUserId == Guid.Empty) + .Select(x => x.EmployeeId) + .Distinct(); + + if (!targetEmployeeIds.Any()) + { + return dataIntegrityAuditResult; + } + + dataIntegrityAuditResult.MaintenanceWasRequired = true; + + Log.Warning("Found Activity records with empty AppUserIds, this should not happen and needs to be investigated"); + + foreach (var targetEmployeeId in targetEmployeeIds) + { + try + { + var appUsers = _applicationDbContext.AppUsers.Where(x => x.EmployeeId == targetEmployeeId); + + if (appUsers.Count() != 1) + { + Log.Information($"{appUsers.Count()} returned from AppUser lookup on {targetEmployeeId} during DetermineActivityMaintenanceAuditResult, " + + $"unable to perform automatic maintenance this needs to be addressed manually"); + dataIntegrityAuditResult.MaintenanceAutomationFailed = true; + continue; + } + + var appUser = await appUsers.FirstAsync(); + foreach (var item in _applicationDbContext.Activities.Where(x => x.EmployeeId == appUser.EmployeeId + && x.AppUserId == Guid.Empty)) + { + item.AppUserId = appUser.Id; + } + } + catch (Exception ex) + { + dataIntegrityAuditResult.MaintenanceAutomationFailed = true; + Log.Error(ex, ex.Message); + } + } + + return dataIntegrityAuditResult; + } + + private async Task DetermineActivityTransferActionMaintenanceAuditResult() + { + var dataIntegrityAuditResult = new DataIntegrityAuditResult(); + var targetEmployeeIds = _applicationDbContext.ActivityTransferActions.Where(x => x.NewActivityEmployeeId != null && x.NewAppUserId == Guid.Empty) + .Select(x => x.NewActivityEmployeeId) + .Distinct(); + + if (!targetEmployeeIds.Any()) + { + return dataIntegrityAuditResult; + } + + dataIntegrityAuditResult.MaintenanceWasRequired = true; + + Log.Information("Found ActivityTransferAction records with empty AppUserIds, this should not happen and needs to be investigated"); + + foreach (var targetEmployeeId in targetEmployeeIds) + { + try + { + var appUsers = _applicationDbContext.AppUsers.Where(x => x.EmployeeId == targetEmployeeId); + + if (appUsers.Count() != 1) + { + Log.Information($"{appUsers.Count()} returned from AppUser lookup on {targetEmployeeId} during DetermineActivityTransferActionMaintenanceAuditResult, " + + $"unable to perform automatic maintenance this needs to be addressed manually"); + dataIntegrityAuditResult.MaintenanceAutomationFailed = true; + continue; + } + + var appUser = await appUsers.FirstAsync(); + foreach (var item in _applicationDbContext.ActivityTransferActions.Where(x => x.NewActivityEmployeeId == appUser.EmployeeId + && x.NewAppUserId == Guid.Empty)) + { + item.NewAppUserId = appUser.Id; + } + } + catch (Exception ex) + { + dataIntegrityAuditResult.MaintenanceAutomationFailed = true; + Log.Error(ex, ex.Message); + } + } + + return dataIntegrityAuditResult; + } + + private void EnsureAuditCreateAndUpdateAppUserIds() + { + Log.Information("ApplicationDbIntegrity: EnsureAuditCreateAndUpdateAppUserIds method Started"); + + foreach (var appUser in _applicationDbContext.AppUsers) + { + try + { + SyncIdsWithDb(appUser, true).GetAwaiter().GetResult(); + } + catch (Exception ex) + { + Log.Error(ex, ex.Message); + } + } + + Log.Information("ApplicationDbIntegrity: EnsureAuditCreateAndUpdateAppUserIds method Ended"); + } + + private async Task DetermineDeadActivityMaintenanceAuditResult() + { + var dataIntegrityAuditResult = new DataIntegrityAuditResult(); + var targetEmployeeIds = _applicationDbContext.DeadActivities.Where(x => x.EmployeeId != null && x.AppUserId == Guid.Empty) + .Select(x => x.EmployeeId) + .Distinct(); + + if (!targetEmployeeIds.Any()) + { + return dataIntegrityAuditResult; + } + + dataIntegrityAuditResult.MaintenanceWasRequired = true; + + Log.Information("Found DeadActivity records with empty AppUserIds, this should not happen and needs to be investigated"); + + foreach (var targetEmployeeId in targetEmployeeIds) + { + try + { + var appUsers = _applicationDbContext.AppUsers.Where(x => x.EmployeeId == targetEmployeeId); + + if (appUsers.Count() != 1) + { + Log.Information($"{appUsers.Count()} returned from AppUser lookup on {targetEmployeeId} during DetermineDeadActivityMaintenanceAuditResult, " + + $"unable to perform automatic maintenance this needs to be addressed manually"); + dataIntegrityAuditResult.MaintenanceAutomationFailed = true; + continue; + } + + var appUser = await appUsers.FirstAsync(); + foreach (var item in _applicationDbContext.DeadActivities.Where(x => x.EmployeeId == appUser.EmployeeId + && x.AppUserId == Guid.Empty)) + { + item.AppUserId = appUser.Id; + } + } + catch (Exception ex) + { + dataIntegrityAuditResult.MaintenanceAutomationFailed = true; + Log.Error(ex, ex.Message); + } + } + + return dataIntegrityAuditResult; + } + + private async Task DetermineGoalMaintenanceAuditResult() + { + var dataIntegrityAuditResult = new DataIntegrityAuditResult(); + var targetEmployeeIds = _applicationDbContext.Goals.Where(x => x.EmployeeId != null && x.AppUserId == Guid.Empty).Select(x => x.EmployeeId).Distinct(); + + if (!targetEmployeeIds.Any()) + { + return dataIntegrityAuditResult; + } + + dataIntegrityAuditResult.MaintenanceWasRequired = true; + + Log.Information("Found Goal records with empty AppUserIds, this should not happen and needs to be investigated"); + + foreach (var targetEmployeeId in targetEmployeeIds) + { + try + { + var appUsers = _applicationDbContext.AppUsers.Where(x => x.EmployeeId == targetEmployeeId); + + if (appUsers.Count() != 1) + { + Log.Information($"{appUsers.Count()} returned from AppUser lookup on {targetEmployeeId} during DetermineGoalMaintenanceAuditResult, " + + $"unable to perform automatic maintenance this needs to be addressed manually"); + dataIntegrityAuditResult.MaintenanceAutomationFailed = true; + continue; + } + + var appUser = await appUsers.FirstAsync(); + foreach (var item in _applicationDbContext.Goals.Where(x => x.EmployeeId == appUser.EmployeeId + && x.AppUserId == Guid.Empty)) + { + item.AppUserId = appUser.Id; + } + } + catch (Exception ex) + { + dataIntegrityAuditResult.MaintenanceAutomationFailed = true; + Log.Error(ex, ex.Message); + } + } + + return dataIntegrityAuditResult; + } + + private async Task DetermineGoalChangeMaintenanceAuditResult() + { + var dataIntegrityAuditResult = new DataIntegrityAuditResult(); + var targetEmployeeIds = _applicationDbContext.GoalChanges.Where(x => x.PersonId != null && x.AppUserId == Guid.Empty) + .Select(x => x.PersonId) + .Distinct(); + + if (!targetEmployeeIds.Any()) + { + return dataIntegrityAuditResult; + } + + dataIntegrityAuditResult.MaintenanceWasRequired = true; + + Log.Information("Found GoalChange records with empty AppUserIds, this should not happen and needs to be investigated"); + + foreach (var targetEmployeeId in targetEmployeeIds) + { + try + { + var appUsers = _applicationDbContext.AppUsers.Where(x => x.EmployeeId == targetEmployeeId); + + if (appUsers.Count() != 1) + { + Log.Information($"{appUsers.Count()} returned from AppUser lookup on {targetEmployeeId} during DetermineGoalChangeMaintenanceAuditResult, " + + $"unable to perform automatic maintenance this needs to be addressed manually"); + dataIntegrityAuditResult.MaintenanceAutomationFailed = true; + continue; + } + + var appUser = await appUsers.FirstAsync(); + foreach (var item in _applicationDbContext.GoalChanges.Where(x => x.PersonId == appUser.EmployeeId + && x.AppUserId == Guid.Empty)) + { + item.AppUserId = appUser.Id; + } + } + catch (Exception ex) + { + dataIntegrityAuditResult.MaintenanceAutomationFailed = true; + Log.Error(ex, ex.Message); + } + } + + return dataIntegrityAuditResult; + } + + public async Task SyncAppUserUpdate(AppUser appUser) + { + try + { + foreach (var item in _applicationDbContext.Activities.Where(x => x.CreatedByAppUserId == appUser.Id)) + { + item.CreatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + foreach (var item in _applicationDbContext.Activities.Where(x => x.LastUpdatedByAppUserId == appUser.Id)) + { + item.LastUpdatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + foreach (var item in _applicationDbContext.Activities.Where(x => x.AppUserId == appUser.Id)) + { + item.EmployeeEmail = appUser.AdEmail; + item.EmployeeId = appUser.EmployeeId; + } + + foreach (var item in _applicationDbContext.ActivityDetailUpdates.Where(x => x.CreatedByAppUserId == appUser.Id)) + { + item.CreatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + foreach (var item in _applicationDbContext.ActivityStateChanges.Where(x => x.CreatedByAppUserId == appUser.Id)) + { + item.CreatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + foreach (var item in _applicationDbContext.ActivityTransferActions.Where(x => x.CreatedByAppUserId == appUser.Id)) + { + item.CreatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + foreach (var item in _applicationDbContext.ActivityTransferActions.Where(x => x.LastUpdatedByAppUserId == appUser.Id)) + { + item.LastUpdatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + foreach (var item in _applicationDbContext.AppUsers.Where(x => x.CreatedByAppUserId == appUser.Id)) + { + item.CreatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + foreach (var item in _applicationDbContext.AppUsers.Where(x => x.LastUpdatedByAppUserId == appUser.Id)) + { + item.LastUpdatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + foreach (var item in _applicationDbContext.Commissions.Where(x => x.CreatedByAppUserId == appUser.Id)) + { + item.CreatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + foreach (var item in _applicationDbContext.Commissions.Where(x => x.LastUpdatedByAppUserId == appUser.Id)) + { + item.LastUpdatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + foreach (var item in _applicationDbContext.DeadActivities.Where(x => x.CreatedByAppUserId == appUser.Id)) + { + item.CreatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + foreach (var item in _applicationDbContext.DeadActivities.Where(x => x.LastUpdatedByAppUserId == appUser.Id)) + { + item.LastUpdatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + foreach (var item in _applicationDbContext.DeadActivities.Where(x => x.AppUserId == appUser.Id)) + { + item.EmployeeEmail = appUser.AdEmail; + item.EmployeeId = appUser.EmployeeId; + } + + foreach (var item in _applicationDbContext.DepartmentAssignments.Where(x => x.CreatedByAppUserId == appUser.Id)) + { + item.CreatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + foreach (var item in _applicationDbContext.DepartmentAssignments.Where(x => x.LastUpdatedByAppUserId == appUser.Id)) + { + item.LastUpdatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + foreach (var item in _applicationDbContext.DepartmentLineItems.Where(x => x.CreatedByAppUserId == appUser.Id)) + { + item.CreatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + foreach (var item in _applicationDbContext.DepartmentLineItems.Where(x => x.LastUpdatedByAppUserId == appUser.Id)) + { + item.LastUpdatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + foreach (var item in _applicationDbContext.Departments.Where(x => x.CreatedByAppUserId == appUser.Id)) + { + item.CreatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + foreach (var item in _applicationDbContext.Departments.Where(x => x.LastUpdatedByAppUserId == appUser.Id)) + { + item.LastUpdatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + foreach (var item in _applicationDbContext.GoalChanges.Where(x => x.CreatedByAppUserId == appUser.Id)) + { + item.CreatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + foreach (var item in _applicationDbContext.GoalChanges.Where(x => x.AppUserId == appUser.Id)) + { + item.PersonId = appUser.EmployeeId; + } + + foreach (var item in _applicationDbContext.Goals.Where(x => x.CreatedByAppUserId == appUser.Id)) + { + item.CreatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + foreach (var item in _applicationDbContext.Goals.Where(x => x.LastUpdatedByAppUserId == appUser.Id)) + { + item.LastUpdatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + foreach (var item in _applicationDbContext.Goals.Where(x => x.AppUserId == appUser.Id)) + { + item.EmployeeId = appUser.EmployeeId; + } + + foreach (var item in _applicationDbContext.PayoutLineItems.Where(x => x.CreatedByAppUserId == appUser.Id)) + { + item.CreatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + foreach (var item in _applicationDbContext.PayoutLineItems.Where(x => x.AppUserId == appUser.Id)) + { + item.EmployeeId = appUser.EmployeeId; + } + + foreach (var item in _applicationDbContext.RoleMappings.Where(x => x.CreatedByAppUserId == appUser.Id)) + { + item.CreatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + foreach (var item in _applicationDbContext.RoleMappings.Where(x => x.LastUpdatedByAppUserId == appUser.Id)) + { + item.LastUpdatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + foreach (var item in _applicationDbContext.UserDepartmentHistories.Where(x => x.CreatedByAppUserId == appUser.Id) + ) + { + item.CreatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + foreach (var item in _applicationDbContext.UserPermissionHistories.Where(x => x.CreatedByAppUserId == appUser.Id)) + { + item.CreatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + foreach (var item in _applicationDbContext.UserPermissionHistories.Where(x => x.AppUserId == appUser.Id)) + { + item.EmployeeId = int.Parse(appUser.EmployeeId); + } + + foreach (var item in _applicationDbContext.UserPermissions.Where(x => x.CreatedByAppUserId == appUser.Id)) + { + item.CreatedBy = appUser.AppUserFullNameInFromContextFormat(); + } + + await _applicationDbContext.SaveChangesAsync(); + + return true; + } + catch (Exception ex) + { + Log.Error(ex, ex.Message); + } + + return false; + } + + public async Task SyncIdsWithDb(AppUser appUser, bool limitScopeToAuditCreateAndUpdate = false) + { + try + { + Log.Information($"ApplicationDbIntegrity: SyncIdsWithDb for User {appUser.FullName} Started"); + foreach (var item in _applicationDbContext.Activities.Where(x => x.CreatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.LastUpdatedByAppUserId == Guid.Empty)) + { + item.CreatedByAppUserId = appUser.Id; + } + + foreach (var item in _applicationDbContext.Activities.Where(x => x.LastUpdatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.LastUpdatedByAppUserId == Guid.Empty)) + { + item.LastUpdatedByAppUserId = appUser.Id; + } + + if (!limitScopeToAuditCreateAndUpdate) + { + foreach (var item in _applicationDbContext.Activities.Where(x => x.EmployeeEmail == appUser.AdEmail + && x.EmployeeId == appUser.EmployeeId + && x.AppUserId == Guid.Empty)) + { + item.AppUserId = appUser.Id; + } + } + + foreach (var item in _applicationDbContext.ActivityDetailUpdates.Where(x => x.CreatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.CreatedByAppUserId == Guid.Empty)) + { + item.CreatedByAppUserId = appUser.Id; + } + + foreach (var item in _applicationDbContext.ActivityStateChanges.Where(x => x.CreatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.CreatedByAppUserId == Guid.Empty)) + { + item.CreatedByAppUserId = appUser.Id; + } + + foreach (var item in _applicationDbContext.ActivityTransferActions.Where(x => x.CreatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.CreatedByAppUserId == Guid.Empty)) + { + item.CreatedByAppUserId = appUser.Id; + } + + foreach (var item in _applicationDbContext.ActivityTransferActions.Where(x => x.LastUpdatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.LastUpdatedByAppUserId == Guid.Empty)) + { + item.LastUpdatedByAppUserId = appUser.Id; + } + + foreach (var item in _applicationDbContext.AppUsers.Where(x => x.CreatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.CreatedByAppUserId == Guid.Empty)) + { + item.CreatedByAppUserId = appUser.Id; + } + + foreach (var item in _applicationDbContext.AppUsers.Where(x => x.LastUpdatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.LastUpdatedByAppUserId == Guid.Empty)) + { + item.LastUpdatedByAppUserId = appUser.Id; + } + + foreach (var item in _applicationDbContext.Commissions.Where(x => x.CreatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.CreatedByAppUserId == Guid.Empty)) + { + item.CreatedByAppUserId = appUser.Id; + } + + foreach (var item in _applicationDbContext.Commissions.Where(x => x.LastUpdatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.LastUpdatedByAppUserId == Guid.Empty)) + { + item.LastUpdatedByAppUserId = appUser.Id; + } + + foreach (var item in _applicationDbContext.DeadActivities.Where(x => x.CreatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.CreatedByAppUserId == Guid.Empty)) + { + item.CreatedByAppUserId = appUser.Id; + } + + foreach (var item in _applicationDbContext.DeadActivities.Where(x => x.LastUpdatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.LastUpdatedByAppUserId == Guid.Empty)) + { + item.LastUpdatedByAppUserId = appUser.Id; + } + + if (!limitScopeToAuditCreateAndUpdate) + { + foreach (var item in _applicationDbContext.DeadActivities.Where(x => x.EmployeeEmail == appUser.AdEmail + && x.EmployeeId == appUser.EmployeeId + && x.AppUserId == Guid.Empty)) + { + item.AppUserId = appUser.Id; + } + } + + foreach (var item in _applicationDbContext.DepartmentAssignments.Where(x => x.CreatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.CreatedByAppUserId == Guid.Empty)) + { + item.CreatedByAppUserId = appUser.Id; + } + + foreach (var item in _applicationDbContext.DepartmentAssignments.Where(x => x.LastUpdatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.LastUpdatedByAppUserId == Guid.Empty)) + { + item.LastUpdatedByAppUserId = appUser.Id; + } + + if (!limitScopeToAuditCreateAndUpdate) + { + foreach (var item in _applicationDbContext.DepartmentAssignments.Where(x => x.Employee.AdEmail == appUser.AdEmail + && x.Employee.EmployeeId == appUser.EmployeeId + && x.AppUserId == Guid.Empty)) + { + item.AppUserId = appUser.Id; + } + } + + foreach (var item in _applicationDbContext.DepartmentLineItems.Where(x => x.CreatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.CreatedByAppUserId == Guid.Empty)) + { + item.CreatedByAppUserId = appUser.Id; + } + + foreach (var item in _applicationDbContext.DepartmentLineItems.Where(x => x.LastUpdatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.LastUpdatedByAppUserId == Guid.Empty)) + { + item.LastUpdatedByAppUserId = appUser.Id; + } + + foreach (var item in _applicationDbContext.Departments.Where(x => x.CreatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.CreatedByAppUserId == Guid.Empty)) + { + item.CreatedByAppUserId = appUser.Id; + } + + foreach (var item in _applicationDbContext.Departments.Where(x => x.LastUpdatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.LastUpdatedByAppUserId == Guid.Empty)) + { + item.LastUpdatedByAppUserId = appUser.Id; + } + + foreach (var item in _applicationDbContext.GoalChanges.Where(x => x.CreatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.CreatedByAppUserId == Guid.Empty)) + { + item.CreatedByAppUserId = appUser.Id; + } + + if (!limitScopeToAuditCreateAndUpdate) + { + foreach (var item in _applicationDbContext.GoalChanges.Where(x => x.PersonId == appUser.EmployeeId + && x.AppUserId == Guid.Empty)) + { + item.AppUserId = appUser.Id; + } + } + + foreach (var item in _applicationDbContext.Goals.Where(x => x.CreatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.CreatedByAppUserId == Guid.Empty)) + { + item.CreatedByAppUserId = appUser.Id; + } + + foreach (var item in _applicationDbContext.Goals.Where(x => x.LastUpdatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.LastUpdatedByAppUserId == Guid.Empty)) + { + item.LastUpdatedByAppUserId = appUser.Id; + } + + if (!limitScopeToAuditCreateAndUpdate) + { + foreach (var item in _applicationDbContext.Goals.Where(x => x.EmployeeId == appUser.EmployeeId + && x.AppUserId == Guid.Empty)) + { + item.AppUserId = appUser.Id; + } + } + + foreach (var item in _applicationDbContext.PayoutLineItems.Where(x => x.CreatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.CreatedByAppUserId == Guid.Empty)) + { + item.CreatedByAppUserId = appUser.Id; + } + + if (!limitScopeToAuditCreateAndUpdate) + { + foreach (var item in _applicationDbContext.PayoutLineItems.Where(x => x.EmployeeId == appUser.EmployeeId + && x.AppUserId == Guid.Empty)) + { + item.AppUserId = appUser.Id; + } + } + + foreach (var item in _applicationDbContext.RoleMappings.Where(x => x.CreatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.CreatedByAppUserId == Guid.Empty)) + { + item.CreatedByAppUserId = appUser.Id; + } + + foreach (var item in _applicationDbContext.RoleMappings.Where(x => x.LastUpdatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.LastUpdatedByAppUserId == Guid.Empty)) + { + item.LastUpdatedByAppUserId = appUser.Id; + } + + foreach (var item in _applicationDbContext.UserDepartmentHistories.Where(x => x.CreatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.CreatedByAppUserId == Guid.Empty)) + { + item.CreatedByAppUserId = appUser.Id; + } + + foreach (var item in _applicationDbContext.UserPermissionHistories.Where(x => x.CreatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.CreatedByAppUserId == Guid.Empty)) + { + item.CreatedByAppUserId = appUser.Id; + } + + if (!limitScopeToAuditCreateAndUpdate) + { + foreach (var item in _applicationDbContext.UserPermissionHistories.Where(x => x.EmployeeId.ToString() == appUser.EmployeeId + && x.AppUserId == Guid.Empty)) + { + item.AppUserId = appUser.Id; + } + } + + foreach (var item in _applicationDbContext.UserPermissions.Where(x => x.CreatedBy == appUser.AppUserFullNameInFromContextFormat() + && x.CreatedByAppUserId == Guid.Empty)) + { + item.CreatedByAppUserId = appUser.Id; + } + + if (!limitScopeToAuditCreateAndUpdate) + { + foreach (var item in _applicationDbContext.UserPermissions.Where(x => x.AppUser.AdEmail == appUser.AdEmail + && x.AppUser.EmployeeId == appUser.EmployeeId + && x.AppUserId == Guid.Empty)) + { + item.AppUserId = appUser.Id; + } + } + + await _applicationDbContext.SaveChangesAsync(); + return true; + } + catch (Exception ex) + { + Log.Error(ex, ex.Message); + } + + return false; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Persistence/CurrentUserService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Persistence/CurrentUserService.cs new file mode 100644 index 0000000..0e2def9 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Persistence/CurrentUserService.cs @@ -0,0 +1,59 @@ +using System; +using System.Linq; +using System.Security.Claims; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using Microsoft.AspNetCore.Http; +using Serilog; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Persistence; + +public class CurrentUserService : ICurrentUserService +{ + protected readonly IHttpContextAccessor HttpContextAccessor; + + public CurrentUserService(IHttpContextAccessor httpContextAccessor) + { + HttpContextAccessor = httpContextAccessor; + } + + public ApplicationUser GetCurrentUser() + { + try + { + if (HttpContextAccessor != null && HttpContextAccessor.HttpContext != null) + { + var userClaimsPrincipal = HttpContextAccessor.HttpContext.User; + var parsedApplicationUser = new ApplicationUser + { + FirstName = userClaimsPrincipal.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.GivenName)?.Value, + LastName = userClaimsPrincipal.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.Surname)?.Value, + Email = userClaimsPrincipal.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.Upn)?.Value, + Roles = userClaimsPrincipal.Claims.Where((claim) => claim.Type == ClaimTypes.Role).Select((claim) => claim?.Value).ToArray(), + IsSystem = false, + }; + Log.Debug($"ApplicationUser: {parsedApplicationUser.Email} successfully parsed from HttpContext"); + return parsedApplicationUser; + } + + Log.Debug("HttpContextAccessor or HttpContext are null, returning default System ApplicationUser"); + return DefaultSystemApplicationUser(); + } + catch (Exception ex) + { + Log.Error(ex, ex.Message); + return DefaultSystemApplicationUser(); + } + } + + private ApplicationUser DefaultSystemApplicationUser() + { + return new ApplicationUser() + { + FirstName = "Incentive", + LastName = "Manager", + Email = "", + Roles = new[] { Role.UNKNOWN_ROLE.Name }, + IsSystem = true + }; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Persistence/DateTimeService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Persistence/DateTimeService.cs new file mode 100644 index 0000000..e0c5f41 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Persistence/DateTimeService.cs @@ -0,0 +1,83 @@ +using System; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Persistence; + +public class DateTimeService : IDateTime +{ + public DateTime Now => DateTime.Now; + public DateTime UTCNow => DateTime.UtcNow; + public DateTime ConvertDnaUnspecifiedTimeToUtc(DateTime dateTime) { + var dnaAssumedTimeZone = TimeZoneConverter.TZConvert.GetTimeZoneInfo("America/Chicago"); + return TimeZoneInfo.ConvertTime(dateTime, dnaAssumedTimeZone, TimeZoneInfo.Utc); + } + + public DateTime? CSTWindowsNow + { + get + { + try + { + var centralTimeZone = TimeZoneConverter.TZConvert.GetTimeZoneInfo("Central Standard Time"); + return TimeZoneInfo.ConvertTimeFromUtc(UTCNow, centralTimeZone); + } + catch (Exception) + { + return null; + } + } + } + + public DateTime? CSTLinuxNow + { + get + { + try + { + var centralTimeZone = TimeZoneConverter.TZConvert.GetTimeZoneInfo("America/Chicago"); + return TimeZoneInfo.ConvertTimeFromUtc(UTCNow, centralTimeZone); + } + catch (Exception) + { + return null; + } + } + } + + public DateTime ConvertDateTimeFromCSTToUTCLinux(string dateTimeString) + { + var date = DateTime.Parse(dateTimeString); + return TimeZoneInfo.ConvertTime(date, TimeZoneConverter.TZConvert.GetTimeZoneInfo("America/Chicago"), TimeZoneInfo.Utc); + } + public DateTime ConvertDateTimeFromCSTToUTCWindows(string dateTimeString) + { + var date = DateTime.Parse(dateTimeString); + return TimeZoneInfo.ConvertTime(date, TimeZoneConverter.TZConvert.GetTimeZoneInfo("Central Standard Time"), TimeZoneInfo.Utc); + } + + public DateTime getNextBonusPeriod(DateTime today) + { + var seedDate = new DateTime(2021, 10, 01); + while (seedDate.Date <= today.Date || PayPeriodInMonth(seedDate) != 2) + { + seedDate = seedDate.AddDays(14); + } + return seedDate; + } + + private int PayPeriodInMonth(DateTime input) + { + var period = 1; + var previousPayDay = input.AddDays(-14); + if (previousPayDay.Month == input.Month) + { + period = 2; + } + previousPayDay = previousPayDay.AddDays(-14); + if (previousPayDay.Month == input.Month) + { + period = 3; + } + + return period; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Persistence/IApplicationDbContext.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Persistence/IApplicationDbContext.cs new file mode 100644 index 0000000..1d554af --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Persistence/IApplicationDbContext.cs @@ -0,0 +1,32 @@ +using System.Threading; +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using Microsoft.EntityFrameworkCore; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Persistence; + +public interface IApplicationDbContext +{ + DbSet Goals { get; set; } + DbSet GoalChanges { get; set; } + DbSet Activities { get; set; } + DbSet ActivityDetails { get; set; } + DbSet ActivityStateChanges { get; set; } + DbSet ActivityDetailUpdates { get; set; } + public DbSet Departments { get; set; } + public DbSet AppUsers { get; set; } + public DbSet DepartmentAssignments { get; set; } + public DbSet RoleMappings { get; set; } + DbSet Commissions { get; set; } + public DbSet ProcessorRunHistory { get; set; } + public DbSet UserPermissions { get; set; } + public DbSet PayoutLineItems { get; set; } + public DbSet UserAssignments { get; set; } + public DbSet ActivityTransferActions { get; set; } + public DbSet DeadActivities { get; set; } + public DbSet DepartmentLineItems { get; set; } + public DbSet UserDepartmentHistories { get; set; } + public DbSet UserPermissionHistories { get; set; } + Task SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken()); + public Task SeedRoleMappingData(); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Persistence/IApplicationDbIntegrityService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Persistence/IApplicationDbIntegrityService.cs new file mode 100644 index 0000000..0739d4c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Persistence/IApplicationDbIntegrityService.cs @@ -0,0 +1,12 @@ +using System.Threading; +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Persistence; + +public interface IApplicationDbIntegrityService +{ + Task AppUserIdIntegrityMaintenanceSucceeded(CancellationToken cancellationToken); + Task SyncAppUserUpdate(AppUser appUser); + Task SyncIdsWithDb(AppUser appUser, bool limitScopeToAuditCreateAndUpdate = false); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Persistence/ICurrentUserService.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Persistence/ICurrentUserService.cs new file mode 100644 index 0000000..5b7fd4b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Persistence/ICurrentUserService.cs @@ -0,0 +1,8 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Persistence; + +public interface ICurrentUserService +{ + ApplicationUser GetCurrentUser(); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Persistence/IDateTime.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Persistence/IDateTime.cs new file mode 100644 index 0000000..e40f272 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Persistence/IDateTime.cs @@ -0,0 +1,14 @@ +using System; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Persistence; + +public interface IDateTime +{ + DateTime Now { get; } + DateTime UTCNow { get; } + DateTime? CSTLinuxNow { get; } + DateTime? CSTWindowsNow { get; } + DateTime ConvertDateTimeFromCSTToUTCWindows(string dateTimeString); + DateTime ConvertDateTimeFromCSTToUTCLinux(string dateTimeString); + DateTime getNextBonusPeriod(DateTime today); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/AccountingRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/AccountingRepository.cs new file mode 100644 index 0000000..5fc57ca --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/AccountingRepository.cs @@ -0,0 +1,36 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using Microsoft.EntityFrameworkCore; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public class AccountingRepository : BaseRepository, IAccountingRepository +{ + public AccountingRepository(IApplicationDbContext context) : base(context) + { + } + + public async Task SaveAccountingSummaryAsync(AccountingSummary summary) + { + await _context.DepartmentLineItems.AddRangeAsync(summary.DepartmentPayouts); + return summary; + } + + public async Task GetAccountingSummaryAsync(DateTime payoutPeriod) + { + var rows = await _context.DepartmentLineItems.Where(x => x.PayoutMonth.Date == payoutPeriod.Date).ToListAsync(); + + if (rows != null && rows.Any()) + { + var summary = new AccountingSummary(payoutPeriod); + summary.DepartmentPayouts = rows; + summary.Total = summary.DepartmentPayouts.Sum(x => x.PayoutTotal); + return summary; + } + + return null; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/ActivityChangeRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/ActivityChangeRepository.cs new file mode 100644 index 0000000..dfccd59 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/ActivityChangeRepository.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using Microsoft.EntityFrameworkCore; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public class ActivityChangeRepository : BaseRepository, IActivityChangeRepository +{ + public ActivityChangeRepository(ApplicationDbContext context) : base(context) + { + } + + public async Task SaveActivityStateChange(ActivityStateChange stateChange) + { + await _context.ActivityStateChanges.AddAsync(stateChange); + return stateChange; + } + + public async Task> GetActivityChangesByActivityId(int activityId) + { + return await _context.ActivityStateChanges.Where(x => x.ActivityId == activityId).ToListAsync(); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/ActivityDetailUpdateRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/ActivityDetailUpdateRepository.cs new file mode 100644 index 0000000..1981341 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/ActivityDetailUpdateRepository.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using Microsoft.EntityFrameworkCore; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public class ActivityDetailUpdateRepository : BaseRepository, IActivityDetailUpdateRepository +{ + public ActivityDetailUpdateRepository(ApplicationDbContext context) : base(context) + { + } + + public async Task GetActivityDetailById(int activityDetailId) + { + return await _context.ActivityDetails.FindAsync(activityDetailId); + } + + public async Task SaveActivityDetailUpdate(ActivityDetailUpdate activityDetailUpdate) + { + await _context.ActivityDetailUpdates.AddAsync(activityDetailUpdate); + return activityDetailUpdate; + } + + public async Task> GetActivityDetailUpdatesByActivityDetailId(int activityDetailId) + { + return await _context.ActivityDetailUpdates.Where(x => x.ActivityDetailId == activityDetailId).ToListAsync(); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/ActivityRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/ActivityRepository.cs new file mode 100644 index 0000000..dc69d0c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/ActivityRepository.cs @@ -0,0 +1,566 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Extensions; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Microsoft.EntityFrameworkCore; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public class ActivityRepository : BaseRepository, IActivityRepository +{ + public ActivityRepository(IApplicationDbContext context) : base(context) + { + } + + public async Task SaveActivityAsync(Activity activity) + { + await _context.Activities.AddAsync(activity); + return activity.Id; + } + + public async Task SaveActivityRangeAsync(List activities) + { + await _context.Activities.AddRangeAsync(activities); + } + + public void UpdateActivityRangeAsync(List activities) + { + var existing = activities.Where(x => _context.Activities.Contains(x)); + _context.Activities.UpdateRange(activities); + } + + public async Task> GetActivitiesAsync() + { + return await _context.Activities + .Include(activity => activity.Detail) + .Include(activity => activity.MetaData) + .OrderBy(t => t.Id) + .ToListAsync(); + } + + public async Task> GetActivitiesAsync(int page, int limit) + { + var results = new PagedResult(); + var skip = (page - 1) * limit; + var activities = _context.Activities + .Include(activity => activity.Detail) + .Include(activity => activity.MetaData) + .OrderByDescending(t => t.Created); + + + results.Total = activities.Count(); + results.Items = await activities.Skip(skip).Take(limit).ToListAsync(); + return results; + } + + public async Task> FindUnmatchedReferrals() + { + var activities = _context.Activities.Include(activity => activity.Detail).Include(activity => activity.MetaData); + IQueryable querySet = activities + .Where(x => x.Type == ActivityType.Referral + && x.State == ActivityState.PendingReferral) + .OrderByDescending(t => t.LastUpdated); + + return await querySet.ToListAsync(); + } + + public async Task> FindPendingEStatementsReferrals() + { + var activities = _context.Activities.Include(activity => activity.Detail).Include(activity => activity.MetaData); + IQueryable querySet = activities + .Where(x => x.Type == ActivityType.Referral + && x.State == ActivityState.PendingReferral + && x.Product == Product.eStatement + && string.IsNullOrEmpty(x.Detail.AccountNumber)) + .OrderByDescending(t => t.LastUpdated); + + return await querySet.ToListAsync(); + } + + public async Task FindUnmatchedSaleForReferral(Activity referral) + { + var activities = _context.Activities + .Include(activity => activity.Detail) + .Include(activity => activity.MetaData); + + var matchOnMetaData = string.IsNullOrEmpty(referral.Detail.SubjectOrgNbr) && string.IsNullOrEmpty(referral.Detail.SubjectPersonNbr); + + var matchedActivities = matchOnMetaData + ? await activities.Where(MetaDataMatchPredicate(referral)).ToListAsync() + : await activities.Where(PersonDataMatchPredicate(referral)).ToListAsync(); + + if (matchOnMetaData && matchedActivities.Any()) + { + matchedActivities = matchedActivities.Where(x => + ListsContainMatchingItem( + x.MetaData.Where(e => e.MetaDataType == MetaDataType.Email).Select(e => e.MetaData).ToList(), + referral.MetaData.Where(r => r.MetaDataType == MetaDataType.Email).Select(e => e.MetaData).ToList() + ) + || + ListsContainMatchingItem( + x.MetaData.Where(e => e.MetaDataType == MetaDataType.Phone).Select(e => e.MetaData).ToList(), + referral.MetaData.Where(r => r.MetaDataType == MetaDataType.Phone).Select(e => e.MetaData).ToList() + ) + ) + .ToList(); + } + + var matchingSaleActivityIds = matchedActivities.Select(x => x.Id); + var activityIDsAssociatedToStateChange = _context.ActivityStateChanges + .Where(change => matchingSaleActivityIds.Contains(change.ActivityMatchId)) + .Select(x => x.ActivityMatchId); + var activityIDsNotAssociatedToStateChange = matchingSaleActivityIds.Except(activityIDsAssociatedToStateChange); + return matchedActivities.Where(x => activityIDsNotAssociatedToStateChange.Contains(x.Id)) + .OrderByDescending(t => t.LastUpdated) + .FirstOrDefault(); + } + + public EStatementDeliveryMethod FindDeliveryMethodForEStatement(Activity referral, List deliveryMethods) + { + var matchedMethods = new List(); + var nullReferralOrgNumber = "0"; + + var matchableDeliveryMethods = deliveryMethods.Where(x => x.ActivityDateTime.Date >= referral.SourceCreated.Date).ToList(); + + matchedMethods.AddRange(matchableDeliveryMethods.Where(x => x.PersonNumber == referral.Detail.SubjectPersonNbr + || x.OrganizationNumber == (referral.Detail.SubjectOrgNbr ?? nullReferralOrgNumber) + && x.OrganizationNumber != nullReferralOrgNumber + && referral.Detail.SubjectOrgNbr != nullReferralOrgNumber)); + + matchedMethods.AddRange(matchableDeliveryMethods.Where(x => + ListsContainMatchingItem( + x.MetaData.Where(e => e.MetaDataType == MetaDataType.Email).Select(e => e.MetaData).ToList(), + referral.MetaData.Where(r => r.MetaDataType == MetaDataType.Email).Select(e => e.MetaData).ToList() + ) + || + ListsContainMatchingItem( + x.MetaData.Where(e => e.MetaDataType == MetaDataType.Phone).Select(e => e.MetaData).ToList(), + referral.MetaData.Where(r => r.MetaDataType == MetaDataType.Phone).Select(e => e.MetaData).ToList() + ) + ) + .ToList()); + + var matchedMethod = matchedMethods.OrderByDescending(a => a.ActivityDateTime).FirstOrDefault(); + + if (matchedMethod != null) + { + deliveryMethods.Remove(matchedMethod); + } + + return matchedMethod; + } + + private static Expression> MetaDataMatchPredicate(Activity referral) + { + return x => (x.Type == ActivityType.Sale || x.Type == ActivityType.Other) + && x.Product == referral.Product + && x.MetaData != null && referral.MetaData != null + && x.State == ActivityState.Accepted + && x.SourceCreated.Date >= referral.SourceCreated.Date; + } + + private static bool ListsContainMatchingItem(IReadOnlyList list1, IReadOnlyList list2) + { + if ((list1?.Any() ?? false) && (list2?.Any() ?? false)) + { + return list1.Any(x => list2.Any(t => string.Equals(t, x, StringComparison.CurrentCultureIgnoreCase))); + } + + return false; + } + + private static Expression> PersonDataMatchPredicate(Activity referral) + { + return x => (x.Type == ActivityType.Sale || x.Type == ActivityType.Other) + && x.Product == referral.Product + && x.Detail.SubjectPersonNbr == referral.Detail.SubjectPersonNbr + && x.Detail.SubjectOrgNbr == referral.Detail.SubjectOrgNbr + && x.State == ActivityState.Accepted + && x.SourceCreated.Date >= referral.SourceCreated.Date; + } + + public async Task FindDuplicateSaleAsync(Activity queryActivity) + { + if (queryActivity.Detail == null || queryActivity.Type != ActivityType.Sale) + { + return null; + } + + var activities = _context.Activities + .Include(activity => activity.Detail) + .Include(activity => activity.MetaData); + + IQueryable querySet = activities + .Where(x => x.Id != queryActivity.Id + && x.Product == queryActivity.Product + && x.Type == queryActivity.Type + && x.Detail.AccountNumber == queryActivity.Detail.AccountNumber + && x.Detail.SubAccountNumber == queryActivity.Detail.SubAccountNumber + && x.Detail.SubjectOrgNbr == queryActivity.Detail.SubjectOrgNbr + && x.Detail.SubjectPersonNbr == queryActivity.Detail.SubjectPersonNbr + && ( + queryActivity.SourceId == null + || x.SourceId == queryActivity.SourceId) + ) + .OrderByDescending(t => t.LastUpdated); + + return await querySet.FirstOrDefaultAsync(); + } + + public async Task FindDuplicateReferralAsync(Activity queryActivity) + { + if (queryActivity.Detail == null || queryActivity.Type != ActivityType.Referral) + { + return null; + } + + var activities = _context.Activities + .Include(activity => activity.Detail); + + IQueryable querySet = activities + .Where(x => x.Id != queryActivity.Id + && x.Type == queryActivity.Type && x.Product == queryActivity.Product + && x.Detail.SubjectPersonNbr != null && queryActivity.Detail.SubjectPersonNbr != null + && x.Detail.SubjectOrgNbr != null && queryActivity.Detail.SubjectOrgNbr != null + && x.Detail.SubjectPersonNbr == queryActivity.Detail.SubjectPersonNbr + && x.Detail.SubjectOrgNbr == queryActivity.Detail.SubjectOrgNbr + ) + .OrderByDescending(t => t.LastUpdated); + + return await querySet.FirstOrDefaultAsync(); + } + + public async Task FindActivityForStateUpdate(Activity queryActivity) + { + if (queryActivity.Detail == null) + { + return null; + } + + var activities = _context.Activities + .Include(activity => activity.Detail) + .Include(activity => activity.MetaData); + + IQueryable querySet = activities + .Where(x => x.EmployeeEmail == queryActivity.EmployeeEmail + && x.EmployeeId == queryActivity.EmployeeId + && x.DepartmentId == queryActivity.DepartmentId + && x.Product == queryActivity.Product + && x.SourceCreated.Date == queryActivity.SourceCreated.Date + && x.SourceSystem == queryActivity.SourceSystem + && x.Type == queryActivity.Type + && x.Detail.AccountNumber == queryActivity.Detail.AccountNumber + && x.Detail.SubAccountNumber == queryActivity.Detail.SubAccountNumber + ) + .OrderByDescending(t => t.LastUpdated); + + var returnedItems = await querySet.ToListAsync(); + Func activityStateCondition = x => x.State != queryActivity.State; + var returnItem = returnedItems.Count(activityStateCondition) > 1 + ? returnedItems.FirstOrDefault(activityStateCondition) + : returnedItems.FirstOrDefault(); + + return returnItem; + } + + public async Task FindActivityForDetailUpdate(Activity queryActivity) + { + if (queryActivity.Detail == null) + { + return null; + } + + var returnedItems = await GetActivityForDetailUpdate(queryActivity); + + if (returnedItems.Count == 0) + { + returnedItems = await GetActivityForDetailUpdate(queryActivity, false); + } + + if (returnedItems.Count == 0) + { + return null; + } + + Func activityStatePredicate = x => x.State == ActivityState.Accepted; + var returnItem = returnedItems.Count(activityStatePredicate) >= 1 + ? returnedItems.FirstOrDefault(activityStatePredicate) + : returnedItems.FirstOrDefault(); + + return returnItem; + } + + private async Task> GetActivityForDetailUpdate(Activity queryActivity, bool filterOnSubAccountNumber = true) + { + var activities = _context.Activities + .Include(activity => activity.Detail) + .Include(activity => activity.MetaData); + + IQueryable querySet = activities + .Where(x => x.EmployeeEmail == queryActivity.EmployeeEmail + && x.EmployeeId == queryActivity.EmployeeId + && x.DepartmentId == queryActivity.DepartmentId + && x.Product == queryActivity.Product + && x.SourceSystem == queryActivity.SourceSystem + && x.Type == queryActivity.Type + && x.Detail.AccountNumber == queryActivity.Detail.AccountNumber + ) + .OrderByDescending(t => t.LastUpdated); + + if (filterOnSubAccountNumber) + { + return await querySet.Where(x => x.Detail.SubAccountNumber == queryActivity.Detail.SubAccountNumber).ToListAsync(); + } + + return await querySet.Where(x => x.State == ActivityState.Accepted).ToListAsync(); + } + + public async Task> GetPagedFilteredActivities(ActivityFilter filter) + { + var results = new PagedResult(); + var skip = (filter.Page - 1) * filter.Limit; + var activities = _context.Activities.Include(activity => activity.Detail).Include(activity => activity.MetaData); + IQueryable querySet = activities; + + if (filter.Product != null && filter.Product.Any()) + { + var products = filter.Product; + querySet = querySet.Where(x => products.Contains(x.Product)); + } + + if (filter.Type != null && filter.Type.Any()) + { + var types = filter.Type; + querySet = querySet.Where(x => types.Contains(x.Type)); + } + + if (filter.State != null && filter.State.Any()) + { + var states = filter.State; + querySet = querySet.Where(x => states.Contains(x.State)); + } + + if (filter.DateLow != null) + { + querySet = querySet.Where(x => x.Accepted.Date >= filter.DateLow); + } + + if (filter.DateHigh != null) + { + querySet = querySet.Where(x => x.Accepted.Date <= filter.DateHigh); + } + + if (filter.DepartmentId != 0) + { + querySet = querySet.Where(x => x.DepartmentId == filter.DepartmentId); + } + + if (!string.IsNullOrWhiteSpace(filter.EmployeeId)) + { + querySet = querySet.Where(x => x.EmployeeId == filter.EmployeeId); + } + + if (!string.IsNullOrWhiteSpace(filter.Query)) + { + var query = filter.Query.ToLower().Trim(); + querySet = querySet.Where(x => + x.Detail.MemberName.ToLower().Contains(query) + || x.Detail.AccountNumber.ToLower().Contains(query) + || x.Detail.SubjectPersonNbr.ToLower().Contains(query) + || x.Detail.SubjectOrgNbr.ToLower().Contains(query) + || x.SourceId.ToLower() == query + || query == x.Id.ToString()); + } + + querySet = SortQuerySet(querySet, filter); + results.Total = querySet.Count(); + results.Items = await querySet.Skip(skip).Take(filter.Limit).ToListAsync(); + return results; + } + + public async Task> GetAllActivitiesWithPayoutsForEmployee(string employeeId) + { + return await _context.Activities.Include(activity => activity.ActivityPayouts).Where(x => x.EmployeeId == employeeId).ToListAsync(); + } + + private IQueryable SortQuerySet(IQueryable querySet, ActivityFilter filter) + { + if (string.IsNullOrWhiteSpace(filter.SortBy)) + { + return querySet + .OrderByDescending(t => t.Accepted); + } + + if (filter.SortDescending.HasValue && filter.SortDescending.Value) + { + return (IQueryable)querySet.OrderByDescending(filter.SortBy); + } + + return (IQueryable)querySet.OrderBy(filter.SortBy); + } + + public async Task GetActivityById(int activityId) + { + return await _context.Activities.FindAsync(activityId); + } + + public async Task> FindEmployeeActivityByMonthRange(AppUser appUser, MonthRange monthRange) + { + return await _context.Activities + .Include(activity => activity.Detail) + .Include(activity => activity.MetaData) + .Where(x => x.Accepted.Date >= monthRange.StartTime.Date && + x.Accepted.Date <= monthRange.EndTime.Date && + appUser.EmployeeId == x.EmployeeId) + .ToListAsync(); + } + + public async Task> FindDepartmentActivityByMonthRange(int departmentId, MonthRange monthRange) + { + return await _context.Activities + .Include(activity => activity.Detail) + .Include(activity => activity.MetaData) + .Where(x => x.Accepted.Date >= monthRange.StartTime.Date && + x.Accepted.Date <= monthRange.EndTime.Date && + x.DepartmentId == departmentId) + .ToListAsync(); + } + + public async Task GetActivityIncludingPayouts(int activityId) + { + return await _context.Activities + .Include(activity => activity.Detail) + .Include(activity => activity.MetaData) + .Include(activity => activity.ActivityPayouts) + .ThenInclude(payouts => payouts.PayoutLineItem) + .FirstOrDefaultAsync(x => x.Id == activityId); + } + + public async Task> GetActivitiesBySourceId(List sourceIds) + { + return await _context.Activities.Where(x => sourceIds.Contains(x.SourceId)).ToListAsync(); + } + + public async Task FindDuplicateReferralBySourceIdAsync(Activity queryActivity) + { + if (queryActivity.Detail == null || queryActivity.Type != ActivityType.Referral) + { + return null; + } + + var activities = _context.Activities + .Include(activity => activity.Detail) + .Include(activity => activity.MetaData); + + IQueryable querySet = activities + .Where(x => x.Id != queryActivity.Id + && x.Type == queryActivity.Type + && x.Product == queryActivity.Product + && x.SourceId == queryActivity.SourceId) + .OrderByDescending(t => t.LastUpdated); + + return await querySet.FirstOrDefaultAsync(); + } + + public async Task> FindById( + List ids, + bool withDetail = true, + bool withTracking = true + ) + { + if (!ids.Any()) + { + return new List(); + } + + var query = withTracking + ? _context.Activities + : _context.Activities.AsNoTracking(); + + if (withDetail) + { + query = query.Include(activity => activity.Detail); + } + + query = query.Where(activity => ids.Contains(activity.Id)); + + return await query.ToListAsync(); + } + + public async Task> FindLatestBySourceId( + List sourceIds, + bool withDetail = true, + bool withTracking = true + ) + { + if (!sourceIds.Any()) + { + return new List(); + } + + var query = withTracking + ? _context.Activities + : _context.Activities.AsNoTracking(); + + if (withDetail) + { + query = query.Include(activity => activity.Detail); + } + + return (await query + .Where(activity => sourceIds.Contains(activity.SourceId)) + .GroupBy(activity => activity.SourceId) + .Select(group => group.OrderBy(activity => activity.Created).FirstOrDefault()) + .ToListAsync()) + .Where(activity => activity != null) + .ToList(); + } + + public async Task> FindGapInsuranceEligible( + MonthRange monthRange, + bool withTracking = true + ) + { + var query = withTracking + ? _context.Activities + : _context.Activities.AsNoTracking(); + + return await query + .Include(activity => activity.Detail) + .Where(activity => + activity.Detail != null && + activity.Detail.IsGapInsuranceEligible && + activity.Accepted.Date >= monthRange.StartTime.Date && + activity.Accepted.Date <= monthRange.EndTime.Date) + .ToListAsync(); + } + + public async Task> GetFilteredActivities( + MonthRange monthRange, + Product product, + ActivityState activityState, + bool withTracking = true + ) + { + var querySet = withTracking + ? _context.Activities + : _context.Activities.AsNoTracking(); + + return await querySet + .Include(activity => activity.Detail) + .Where( + activity => + activity.Accepted.Date >= monthRange.StartTime.Date && + activity.Accepted.Date <= monthRange.EndTime.Date && + activity.Product == product && + activity.State == activityState + ) + .ToListAsync(); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/ActivityTransferActionRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/ActivityTransferActionRepository.cs new file mode 100644 index 0000000..4dc8c53 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/ActivityTransferActionRepository.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Microsoft.EntityFrameworkCore; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public class ActivityTransferActionRepository : BaseRepository, IActivityTransferActionRepository +{ + public ActivityTransferActionRepository(IApplicationDbContext context) : base(context) + { + } + + public async Task SaveTransferAsync(ActivityTransferAction activityTransferAction) + { + await _context.ActivityTransferActions.AddAsync(activityTransferAction); + return activityTransferAction.Id; + } + + public async Task GetTransferActionById(int id) + { + return await _context.ActivityTransferActions + .Include(x => x.RequestedActivity) + .FirstOrDefaultAsync(x => x.Id == id); + } + + public async Task GetRequestedTransferActionByActivityId(int requestedActivityId) + { + return await _context.ActivityTransferActions + .FirstOrDefaultAsync(x => x.RequestedActivityId == requestedActivityId + && x.State == ActivityTransferState.Requested); + } + + public async Task> GetActiveTransfersForStateAsync( + ActivityState state) + { + return await _context.ActivityTransferActions + .Include(x => x.RequestedActivity) + .Where(x => x.ActivityStateBeforeTransfer == state + && x.State == ActivityTransferState.Requested) + .ToListAsync(); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/BaseRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/BaseRepository.cs new file mode 100644 index 0000000..32c6ec6 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/BaseRepository.cs @@ -0,0 +1,13 @@ +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public abstract class BaseRepository +{ + protected readonly IApplicationDbContext _context; + + protected BaseRepository(IApplicationDbContext context) + { + _context = context; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/CommissionRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/CommissionRepository.cs new file mode 100644 index 0000000..9e17d1a --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/CommissionRepository.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using Microsoft.EntityFrameworkCore; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public class CommissionRepository : BaseRepository, ICommissionRepository +{ + public CommissionRepository(IApplicationDbContext context) : base(context) + { + } + + public async Task FindExistingCommission(int id) + { + return await _context.Commissions.FindAsync(id); + } + + public async Task FindExistingCommission(Role role, Product product) + { + return await _context.Commissions.FirstOrDefaultAsync(x => x.Role == role && + x.Product == product); + } + + public async Task> GetCommissionsAsync() + { + return await _context.Commissions.ToListAsync(); + } + + public async Task SaveCommission(Commission commission) + { + await _context.Commissions.AddAsync(commission); + } + + public async Task> FindProductCommissions(Role employeeRole) + { + return await _context.Commissions.AsNoTracking().Where(x => x.Role == employeeRole).ToListAsync(); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/DeadActivityRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/DeadActivityRepository.cs new file mode 100644 index 0000000..a5eab51 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/DeadActivityRepository.cs @@ -0,0 +1,142 @@ +using LCU.API.Internal.Operations.IncentiveApiLib.Extensions; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public class DeadActivityRepository : BaseRepository, IDeadActivityRepository +{ + public DeadActivityRepository(IApplicationDbContext context) : base(context) + { + } + + public async Task SaveDeadActivity(DeadActivity newDeadActivity) + { + await _context.DeadActivities.AddAsync(newDeadActivity); + return newDeadActivity; + } + + public async Task> SaveDeadActivityRange(List newActivities) + { + await _context.DeadActivities.AddRangeAsync(newActivities); + return newActivities; + } + + public async Task> GetDeadActivitiesBySourceIds(List sourceIds) + { + return await _context.DeadActivities.Include(x => x.DeadDetail).Where(x => sourceIds.Contains(x.SourceId)).ToListAsync(); + } + + public async Task> GetAllDeadActivities() + { + return await _context.DeadActivities.Include(x => x.DeadDetail).ToListAsync(); + } + + public async Task GetDeadActivityById(int activityId) + { + return await _context.DeadActivities + .Include(x => x.DeadDetail) + .FirstOrDefaultAsync(x => x.Id == activityId); + } + + public async Task> GetPagedDeadActivities(int page, int limit) + { + var results = new PagedResult(); + var skip = (page - 1) * limit; + var deadActivities = _context.DeadActivities + .Include(activity => activity.DeadDetail) + .OrderByDescending(t => t.Created); + + results.Total = deadActivities.Count(); + results.Items = await deadActivities.Skip(skip).Take(limit).ToListAsync(); + return results; + } + + public async Task> GetFilteredDeadActivities(DeadActivityFilter filter) + { + var results = new PagedResult(); + var skip = (filter.Page - 1) * filter.Limit; + var activities = _context + .DeadActivities.Include(activity => activity.DeadDetail) + .Where(activity => activity.LivingActivityId == null); + IQueryable querySet = activities; + + if (filter.Product != null && filter.Product.Any()) + { + var products = filter.Product; + querySet = querySet.Where(x => products.Contains((Product)x.Product)); + } + + if (filter.Type != null && filter.Type.Any()) + { + var types = filter.Type; + querySet = querySet.Where(x => types.Contains((ActivityType)x.Type)); + } + + if (filter.State != null && filter.State.Any()) + { + var states = filter.State; + querySet = querySet.Where(x => states.Contains((ActivityState)x.State)); + } + + if (filter.DateLow != null) + { + querySet = querySet.Where(x => x.Accepted >= filter.DateLow); + } + + if (filter.DateHigh != null) + { + querySet = querySet.Where(x => x.Accepted <= filter.DateHigh); + } + + if (filter.DepartmentId != 0) + { + querySet = querySet.Where(x => x.DepartmentId == filter.DepartmentId); + } + + if (!string.IsNullOrWhiteSpace(filter.EmployeeId)) + { + querySet = querySet.Where(x => x.EmployeeId == filter.EmployeeId); + } + + if (!string.IsNullOrWhiteSpace(filter.Query)) + { + var query = filter.Query.ToLower().Trim(); + querySet = querySet.Where(x => + (x.DeadDetail.MemberName ?? "").ToLower().Contains(query) + || (x.DeadDetail.AccountNumber ?? "").ToLower().Contains(query) + || (x.DeadDetail.SubAccountNumber ?? "").ToLower().Contains(query) + || (x.DeadDetail.SubjectPersonNbr ?? "").ToLower().Contains(query) + || (x.DeadDetail.SubjectOrgNbr ?? "").ToLower().Contains(query) + || (x.SourceId ?? "").ToLower() == query + || query == x.Id.ToString()); + } + + querySet = SortQuerySet(querySet, filter); + results.Total = querySet.Count(); + results.Items = await querySet.Skip(skip).Take(filter.Limit).ToListAsync(); + return results; + } + + private IQueryable SortQuerySet(IQueryable querySet, ActivityFilter filter) + { + if (string.IsNullOrWhiteSpace(filter.SortBy)) + { + return querySet + .OrderByDescending(t => t.Accepted); + } + + if (filter.SortDescending.HasValue && filter.SortDescending.Value) + { + return (IQueryable)querySet.OrderByDescending(filter.SortBy); + } + + return (IQueryable)querySet.OrderBy(filter.SortBy); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/DepartmentRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/DepartmentRepository.cs new file mode 100644 index 0000000..fc0ee7c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/DepartmentRepository.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using Microsoft.EntityFrameworkCore; +using Serilog; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Results; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public class DepartmentRepository : BaseRepository, IDepartmentRepository +{ + private const string BranchStr = "branch"; + + public DepartmentRepository(IApplicationDbContext context) : base(context) + { + } + + public async Task> GetAllDepartmentsAsync() + { + return await _context.Departments.OrderBy(x => x.DepartmentName).ToListAsync(); + } + + public async Task FindDepartmentByIdAsync(int id) + { + return await _context.Departments.FindAsync(id); + } + + public async Task SaveDepartmentAsync(Department department) + { + await _context.Departments.AddAsync(department); + return department; + } + + public async Task FindOrCreateDepartment(ActiveEmployeeResult employee) + { + var departmentName = DetermineDepartmentName(employee.OrgUnit); + var orgCode = employee.OrgCode.ToLower(); + var businessLine = BusinessLine.TryLookup(employee.OrgUnit); + var department = await _context.Departments.FirstOrDefaultAsync(x => + x.DayForceGlCode.ToLower() == orgCode || x.DepartmentName.ToLower() == departmentName.ToLower()); + + if (department == null) + { + department = new Department() + { + BusinessLineCode = businessLine.Code, + State = DepartmentState.Active, + DepartmentName = departmentName, + DayforceDepartmentName = employee.OrgUnit, + DayForceGlCode = employee.OrgCode, + DnaOrgNbr = employee.GetDnaOrgCode() + }; + await SaveDepartmentAsync(department); + } + + department.DepartmentName = departmentName; + return department; + } + + public static string DetermineDepartmentName(string employeeOrgUnit) + { + if (string.IsNullOrWhiteSpace(employeeOrgUnit)) + { + return null; + } + + if (employeeOrgUnit.Contains("-")) + { + var unitNames = employeeOrgUnit.Split("-"); + var leftHalf = unitNames.First().Trim(); + var rightHalf = unitNames.ElementAt(1).Trim(); + + if (string.Equals(rightHalf, BranchStr, StringComparison.CurrentCultureIgnoreCase)) + { + return leftHalf; + } + + return rightHalf; + } + + return employeeOrgUnit.Trim(); + } + + public async Task> FindDepartmentAssignments(DepartmentPersonnelSearchRequest searchRequest) + { + var userDepartments = await _context.DepartmentAssignments + .Include(x => x.Employee) + .ThenInclude(x => x.AdditionalPermissions) + .Where(x => x.DepartmentId == searchRequest.DepartmentId) + .ToListAsync(); + + var range = new MonthRange(searchRequest.Year, searchRequest.Month); + var usersInRange = userDepartments + .Where(x => + x.StartDate.Date <= range.EndTime.Date && + (x.EndDate == null || x.EndDate.Value.Date >= range.StartTime.Date)); + + var users = usersInRange.Where(x => searchRequest.IncludeAllPersonnel || + x.Role.Permissions.Any(p => + p == Permission.HaveSalesGoals) || + x.Employee.AdditionalPermissions.Any(p => + p.Permission == Permission.HaveSalesGoals)); + + return users.Select(x => x.Employee).ToList(); + } + + public async Task FindUserHomeDepartment(AppUser appUser) + { + try + { + var userDepartment = await _context.DepartmentAssignments.Include(x => x.Department) + .Where(x => x.IsHome == true && x.EndDate == null + && x.Employee.EmployeeId.Equals(appUser.EmployeeId)).SingleOrDefaultAsync(); + return userDepartment?.Department; + } + catch (Exception ex) + { + Log.Error(ex, + $"Error Finding User Home Department. User with EmployeeId {appUser.EmployeeId} may have no or multiple home assignments."); + return null; + } + } + + public async Task FindDepartmentByBranchName(string name) + { + return await _context.Departments.SingleOrDefaultAsync(x => x.DepartmentName.ToLower() == name.ToLower()); + } + + public async Task GetDepartmentByDayforceGlCode(int glCode) + { + return await _context.Departments.AsNoTracking().FirstOrDefaultAsync(x => x.DayForceGlCode == glCode.ToString()); + } + + public async Task> FindDepartmentsByRegionAsync(Region region) + { + return await _context.Departments + .Where(x => x.RegionCode == region.Code) + .OrderBy(x => x.DepartmentName) + .ToListAsync(); + } + + public async Task> FindDepartmentsByBusinessLineAsync(BusinessLine businessLine) + { + return await _context.Departments + .Where(x => x.BusinessLineCode == businessLine.Code) + .OrderBy(x => x.DepartmentName) + .ToListAsync(); + } + + public async Task> FindCurrentDepartmentAssignments(int departmentId) + { + return await FindDepartmentAssignmentsForRange(departmentId, new MonthRange()); + } + + public async Task> FindDepartmentAssignmentsForRange(int departmentId, + MonthRange monthRange) + { + return await _context.DepartmentAssignments + .AsNoTracking() + .Include(assignment => assignment.Employee) + .Where(assignment => assignment.DepartmentId == departmentId && + assignment.StartDate.Date <= monthRange.EndTime.Date && + (assignment.EndDate == null || + assignment.EndDate.Value.Date >= monthRange.StartTime.Date)) + .ToListAsync(); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/GoalChangeRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/GoalChangeRepository.cs new file mode 100644 index 0000000..bcec87c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/GoalChangeRepository.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using Microsoft.EntityFrameworkCore; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public class GoalChangeRepository : BaseRepository, IGoalChangeRepository +{ + public GoalChangeRepository(IApplicationDbContext context) : base(context) + { + } + + public async Task SaveGoalChangeAsync(GoalChange goalChange) + { + await _context.GoalChanges.AddAsync(goalChange); + return goalChange.Id; + } + + public async Task> GetGoalChangesAsync() + { + return await _context.GoalChanges + .OrderBy(t => t.Id) + .ToListAsync(); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/GoalRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/GoalRepository.cs new file mode 100644 index 0000000..cd4eadf --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/GoalRepository.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using Microsoft.EntityFrameworkCore; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public class GoalRepository : BaseRepository, IGoalRepository +{ + public GoalRepository(IApplicationDbContext context) : base(context) + { + } + + public async Task SaveGoalAsync(Goal goalChange) + { + await _context.Goals.AddAsync(goalChange); + return goalChange.Id; + } + + public async Task> GetGoalsAsync(GoalSearchRequest goalSearchRequest) + { + var result = _context.Goals.AsQueryable(); + + if (goalSearchRequest != null) + { + if (goalSearchRequest.departmentId.HasValue) + { + result = result.Where(x => x.DepartmentId == goalSearchRequest.departmentId); + } + + if (goalSearchRequest.DateRange != null && goalSearchRequest.DateRange.ValidFromRange) + { + var fromDate = goalSearchRequest.DateRange.StartTime; + result = result.Where(x => x.StartDate.Date >= fromDate.Date); + } + + if (goalSearchRequest.DateRange != null && goalSearchRequest.DateRange.ValidToRange) + { + var toDate = goalSearchRequest.DateRange.EndTime; + result = result.Where(x => x.EndDate.Date <= toDate.Date); + } + + if (goalSearchRequest.IncludePersonnel) + { + result = result.Where(x => x.EmployeeId != null); + } + else + { + result = result.Where(x => x.EmployeeId == null); + } + } + + return await result.OrderBy(t => t.Id).ToListAsync(); + } + + public async Task FindExistingGoal(GoalChange goalChange) + { + return await _context.Goals.FirstOrDefaultAsync(x => x.Product == goalChange.Product && + x.StartDate.Date == goalChange.StartDate.Date && + x.EndDate.Date == goalChange.EndDate.Date && + x.DepartmentId == goalChange.DepartmentId && + x.EmployeeId == goalChange.PersonId); + } + + public async Task> FindDepartmentGoals(int departmentId, DateTime start, DateTime end) + { + return await _context.Goals.Where(x => x.StartDate.Date >= start.Date && + x.EndDate.Date <= end.Date && + x.EmployeeId == null && + x.DepartmentId == departmentId).ToListAsync(); + } + + public async Task> FindAnnualDepartmentGoals(int departmentId, int year) + { + return await _context.Goals.Where(x => x.StartDate.Year == year && + x.EndDate.Year == year && + x.EmployeeId == null && + x.DepartmentId == departmentId).ToListAsync(); + } + + public async Task> FindPersonnelGoals(int departmentId, List users, DateTime start, + DateTime end) + { + var employeeIds = users.Select(user => user.EmployeeId).ToList(); + return await _context.Goals.Where(x => x.StartDate.Date == start.Date && + x.EndDate.Date == end.Date && + x.DepartmentId == departmentId && + employeeIds.Contains(x.EmployeeId)).ToListAsync(); + } + + public async Task> FindMonthlyPersonnelGoals(int departmentId, List users, int year, + int month) + { + var employeeIds = users.Select(user => user.EmployeeId).ToList(); + return await _context.Goals.Where(x => x.StartDate.Year == year && x.StartDate.Month == month && + x.EndDate.Year == year && x.EndDate.Month == month && + x.DepartmentId == departmentId && + employeeIds.Contains(x.EmployeeId)).ToListAsync(); + } + + public async Task> FindPersonnelGoalsByMonthRange(int departmentId, List users, MonthRange monthRange) + { + var employeeIds = users.Select(user => user.EmployeeId).ToList(); + return await _context.Goals.AsNoTracking().Where(x => x.StartDate.Date >= monthRange.StartTime.Date && + x.EndDate.Date <= monthRange.EndTime.Date && + x.DepartmentId == departmentId && + employeeIds.Contains(x.EmployeeId)).ToListAsync(); + } + + public async Task> FindPersonnelGoalsByMonthRange(int departmentId, List employeeIds, MonthRange monthRange) + { + return await _context.Goals.Where(x => x.StartDate.Date >= monthRange.StartTime.Date && + x.EndDate.Date <= monthRange.EndTime.Date && + x.DepartmentId == departmentId && + employeeIds.Contains(x.EmployeeId)).ToListAsync(); + } + + public async Task> FindDepartmentGoalsByMonthRange(int departmentId, MonthRange monthRange) + { + return await _context.Goals.AsNoTracking().Where(x => x.StartDate.Date >= monthRange.StartTime.Date && + x.EndDate.Date <= monthRange.EndTime.Date && + x.EmployeeId == null && + x.DepartmentId == departmentId).ToListAsync(); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IAccountingRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IAccountingRepository.cs new file mode 100644 index 0000000..0fde28b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IAccountingRepository.cs @@ -0,0 +1,11 @@ +using System; +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public interface IAccountingRepository +{ + Task SaveAccountingSummaryAsync(AccountingSummary summary); + Task GetAccountingSummaryAsync(DateTime payoutPeriod); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IActivityChangeRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IActivityChangeRepository.cs new file mode 100644 index 0000000..9b4b8e6 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IActivityChangeRepository.cs @@ -0,0 +1,11 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public interface IActivityChangeRepository +{ + public Task SaveActivityStateChange(ActivityStateChange stateChange); + public Task> GetActivityChangesByActivityId(int activityId); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IActivityDetailUpdateRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IActivityDetailUpdateRepository.cs new file mode 100644 index 0000000..a001909 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IActivityDetailUpdateRepository.cs @@ -0,0 +1,12 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public interface IActivityDetailUpdateRepository +{ + Task SaveActivityDetailUpdate(ActivityDetailUpdate activityDetailUpdate); + Task> GetActivityDetailUpdatesByActivityDetailId(int activityDetailId); + Task GetActivityDetailById(int activityDetailId); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IActivityRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IActivityRepository.cs new file mode 100644 index 0000000..c7ffdaa --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IActivityRepository.cs @@ -0,0 +1,36 @@ +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public interface IActivityRepository +{ + Task SaveActivityAsync(Activity activity); + Task SaveActivityRangeAsync(List activities); + Task> GetActivitiesAsync(); + Task> GetActivitiesAsync(int page, int limit); + Task> GetPagedFilteredActivities(ActivityFilter filter); + Task GetActivityById(int activityId); + Task> FindEmployeeActivityByMonthRange(AppUser appUser, MonthRange monthRange); + Task> FindDepartmentActivityByMonthRange(int departmentId, MonthRange monthRange); + Task GetActivityIncludingPayouts(int activityId); + Task> FindUnmatchedReferrals(); + Task> FindPendingEStatementsReferrals(); + Task FindUnmatchedSaleForReferral(Activity referral); + EStatementDeliveryMethod FindDeliveryMethodForEStatement(Activity referral, List eStatementActivities); + Task FindDuplicateSaleAsync(Activity activity); + Task FindDuplicateReferralAsync(Activity activity); + Task FindActivityForStateUpdate(Activity activity); + Task FindActivityForDetailUpdate(Activity activity); + public void UpdateActivityRangeAsync(List activities); + Task> GetActivitiesBySourceId(List sourceId); + Task> GetAllActivitiesWithPayoutsForEmployee(string employeeId); + Task FindDuplicateReferralBySourceIdAsync(Activity activity); + Task> FindById(List ids, bool withDetail = true, bool withTracking = true); + Task> FindLatestBySourceId(List sourceIds, bool withDetail = true, bool withTracking = true); + Task> FindGapInsuranceEligible(MonthRange monthRange, bool withTracking = true); + Task> GetFilteredActivities(MonthRange monthRange, Product product, ActivityState activityState, bool withTracking = true); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IActivityTransferActionRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IActivityTransferActionRepository.cs new file mode 100644 index 0000000..e410f2b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IActivityTransferActionRepository.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public interface IActivityTransferActionRepository +{ + Task SaveTransferAsync(ActivityTransferAction activityTransferAction); + Task GetTransferActionById(int id); + Task GetRequestedTransferActionByActivityId(int requestedActivityId); + Task> GetActiveTransfersForStateAsync(ActivityState state); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/ICommissionRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/ICommissionRepository.cs new file mode 100644 index 0000000..d400272 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/ICommissionRepository.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public interface ICommissionRepository +{ + Task FindExistingCommission(int id); + Task FindExistingCommission(Role role, Product product); + Task> GetCommissionsAsync(); + Task SaveCommission(Commission commission); + Task> FindProductCommissions(Role employeeRole); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IDeadActivityRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IDeadActivityRepository.cs new file mode 100644 index 0000000..4dad52d --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IDeadActivityRepository.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public interface IDeadActivityRepository +{ + public Task SaveDeadActivity(DeadActivity newActivity); + public Task> SaveDeadActivityRange(List newActivities); + public Task> GetAllDeadActivities(); + public Task GetDeadActivityById(int activityId); + public Task> GetPagedDeadActivities(int page, int limit); + public Task> GetDeadActivitiesBySourceIds(List sourceIds); + public Task> GetFilteredDeadActivities(DeadActivityFilter filter); + +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IDepartmentRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IDepartmentRepository.cs new file mode 100644 index 0000000..872428c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IDepartmentRepository.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Results; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public interface IDepartmentRepository +{ + Task> GetAllDepartmentsAsync(); + Task FindDepartmentByIdAsync(int id); + Task SaveDepartmentAsync(Department department); + Task FindOrCreateDepartment(ActiveEmployeeResult employee); + Task> FindDepartmentAssignments(DepartmentPersonnelSearchRequest searchRequest); + Task FindUserHomeDepartment(AppUser appUser); + Task FindDepartmentByBranchName(string name); + Task> FindDepartmentsByRegionAsync(Region region); + Task> FindCurrentDepartmentAssignments(int departmentId); + Task GetDepartmentByDayforceGlCode(int glCode); + Task> FindDepartmentAssignmentsForRange(int departmentId, MonthRange monthRange); + Task> FindDepartmentsByBusinessLineAsync(BusinessLine businessLine); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IGoalChangeRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IGoalChangeRepository.cs new file mode 100644 index 0000000..8376b42 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IGoalChangeRepository.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public interface IGoalChangeRepository +{ + Task SaveGoalChangeAsync(GoalChange goalChange); + Task> GetGoalChangesAsync(); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IGoalRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IGoalRepository.cs new file mode 100644 index 0000000..b83558b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IGoalRepository.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public interface IGoalRepository +{ + Task SaveGoalAsync(Goal goalChange); + Task> GetGoalsAsync(GoalSearchRequest goalSearchRequest); + Task FindExistingGoal(GoalChange goalChange); + Task> FindDepartmentGoals(int departmentId, DateTime start, DateTime end); + Task> FindAnnualDepartmentGoals(int departmentId, int year); + Task> FindPersonnelGoals(int departmentId, List users, DateTime start, DateTime end); + Task> FindMonthlyPersonnelGoals(int departmentId, List users, int year, int month); + Task> FindPersonnelGoalsByMonthRange(int departmentId, List users, MonthRange monthRange); + Task> FindPersonnelGoalsByMonthRange(int departmentId, List employeeIds, MonthRange monthRange); + Task> FindDepartmentGoalsByMonthRange(int departmentId, MonthRange monthRange); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IPayoutRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IPayoutRepository.cs new file mode 100644 index 0000000..0f7938c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IPayoutRepository.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public interface IPayoutRepository +{ + Task SavePayoutLineItemAsync(PayoutLineItem lineItem); + Task SavePayoutLineItemRangeAsync(IReadOnlyCollection lineItems); + Task> GetPayoutLineItemsForPeriodAsync(DateTime forPeriod); + Task GetPayoutLineItemById(int payoutId); + Task> GetEmployeePayoutsForPeriod(AppUser appUser, DateTime forPeriod); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IProcessorHistoryRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IProcessorHistoryRepository.cs new file mode 100644 index 0000000..bb792ef --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IProcessorHistoryRepository.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public interface IProcessorHistoryRepository +{ + public Task SaveProcessorRunHistory(ProcessorHistoryRecord newHistoryRecord); + public Task GetProcessorRunHistory(Process process); + public Task> GetAllProcessorRunHistory(); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IRoleMappingRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IRoleMappingRepository.cs new file mode 100644 index 0000000..55c0b9f --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IRoleMappingRepository.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public interface IRoleMappingRepository +{ + Task> GetAllRoleMappingsAsync(); + Task GetRoleMappingByIdAsync(int id); + Task UpdateRoleMapping(RoleMapping roleMapping); + Task SaveRoleMappingAsync(RoleMapping roleMapping); + Task SaveRoleMappingRangeAsync(IEnumerable newRoleMappings); + Task SaveNewRoleMappingRangeAsync(IEnumerable newRoleMappings); + Role FindMatchingRoleFromBusinessRole(string queryRole); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IUnitOfWork.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IUnitOfWork.cs new file mode 100644 index 0000000..3e16c34 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IUnitOfWork.cs @@ -0,0 +1,8 @@ +using System.Threading.Tasks; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public interface IUnitOfWork +{ + Task CompleteAsync(); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IUserAssignmentRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IUserAssignmentRepository.cs new file mode 100644 index 0000000..a3ccc38 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IUserAssignmentRepository.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public interface IUserAssignmentRepository +{ + Task> GetAllUserAssignmentsAsync(); + Task FindExistingAssignmentAsync(AssignmentType assignmentType, string key); + Task IsUserAssigned(AssignmentType assignmentType, AppUser appUser); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IUserDepartmentHistoryRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IUserDepartmentHistoryRepository.cs new file mode 100644 index 0000000..373a96a --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IUserDepartmentHistoryRepository.cs @@ -0,0 +1,12 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public interface IUserDepartmentHistoryRepository +{ + Task> SaveUserDepartmentUpdate(UserDepartment currentUserDepartment, DepartmentUpdateRequest departmentUpdateRequest); + Task> GetUserDepartmentHistory(int employeeId); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IUserPermissionHistoryRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IUserPermissionHistoryRepository.cs new file mode 100644 index 0000000..b0d03c8 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IUserPermissionHistoryRepository.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Entities; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public interface IUserPermissionHistoryRepository +{ + Task> SaveUserPermissionsInsertion(IEnumerable newPermissions); + Task> SaveUserPermissionsDeletion(IEnumerable newPermissions); + Task> GetUserPermissionHistory(int employeeId); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IUserRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IUserRepository.cs new file mode 100644 index 0000000..b8069a4 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/IUserRepository.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public interface IUserRepository +{ + Task> GetAllUsersAsync(); + Task> GetAllUsersToPayoutAsync(MonthRange range); + Task FindUserByEmployeeIdAsync(string employeeId); + Task FindUserByAdEmailAsync(string adEmail); + Task FindActiveUserByEmployeeIdAsync(string employeeId); + Task FindActiveUserByAdEmailAsync(string adEmail); + Task FindUserAsync(string employeeId, string adEmail); + Task> FindUsersByHomeDepartmentsAsync(List homeDepartments); + Task> FindUsersByBaseRolesAsync(List baseRoles); + Task SaveUserAsync(AppUser newAppUser); + Task> SaveUserRangeAsync(List newUsers); + Task> SearchUsersAsync(UserSearchRequest userSearchRequest); + Task> GetAllUserSummariesAsync(); + IReadOnlyCollection GetTerminatedUsersAsync(MonthRange monthRange); +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/PayoutRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/PayoutRepository.cs new file mode 100644 index 0000000..5f4e2d7 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/PayoutRepository.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using Microsoft.EntityFrameworkCore; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public class PayoutRepository : BaseRepository, IPayoutRepository +{ + public PayoutRepository(IApplicationDbContext context) : base(context) + { + } + + public async Task SavePayoutLineItemAsync(PayoutLineItem lineItem) + { + await _context.PayoutLineItems.AddAsync(lineItem); + return lineItem.Id; + } + + public async Task SavePayoutLineItemRangeAsync(IReadOnlyCollection lineItems) + { + await _context.PayoutLineItems.AddRangeAsync(lineItems); + } + + public async Task> GetPayoutLineItemsForPeriodAsync(DateTime forPeriod) + { + var checkDate = new DateTime(forPeriod.Year, forPeriod.Month, 1); + return await _context.PayoutLineItems + .Where(p => p.ActivityPeriod.Date == checkDate.Date) + .OrderBy(t => t.Id) + .ToListAsync(); + } + + public async Task GetPayoutLineItemById(int payoutId) + { + return await _context.PayoutLineItems.FindAsync(payoutId); + } + + public async Task> GetEmployeePayoutsForPeriod(AppUser appUser, DateTime forPeriod) + { + var checkDate = new DateTime(forPeriod.Year, forPeriod.Month, 1); + return await _context.PayoutLineItems.AsNoTracking() + .Include(x => x.ActivityPayouts).ThenInclude(y => y.Activity) + .Where(p => p.ActivityPeriod.Date == checkDate.Date && + p.EmployeeId == appUser.EmployeeId) + .OrderBy(t => t.Id) + .ToListAsync(); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/ProcessorHistoryRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/ProcessorHistoryRepository.cs new file mode 100644 index 0000000..e950a86 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/ProcessorHistoryRepository.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using Microsoft.EntityFrameworkCore; +using Serilog; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public class ProcessorHistoryRepository : BaseRepository, IProcessorHistoryRepository +{ + public ProcessorHistoryRepository(IApplicationDbContext context) : base(context) + { + } + + public async Task SaveProcessorRunHistory(ProcessorHistoryRecord newHistoryRecord) + { + var existingProcess = await _context.ProcessorRunHistory.FindAsync(newHistoryRecord.Process); + if (existingProcess == null) + { + await _context.ProcessorRunHistory.AddAsync(newHistoryRecord); + return newHistoryRecord.Process; + } + + if (newHistoryRecord.LastSuccessfulRunTime < existingProcess.LastSuccessfulRunTime) + { + Log.Warning( + $"ProcessorHistoryRepository Saved over a record with older data." + + $"Old LastSuccess {existingProcess.LastSuccessfulRunTime} New LastSuccess: {newHistoryRecord.LastSuccessfulRunTime}"); + } + + if (newHistoryRecord.LastProcessedRecordTimestamp < existingProcess.LastProcessedRecordTimestamp) + { + Log.Warning( + $"ProcessorHistoryRepository Saved over a record with older data." + + $"Old LastProcessed {existingProcess.LastProcessedRecordTimestamp} New LastProcessed: {newHistoryRecord.LastProcessedRecordTimestamp}"); + } + + if (newHistoryRecord.LastProcessedRecordId < existingProcess.LastProcessedRecordId) + { + Log.Warning( + $"ProcessorHistoryRepository Saved over a record with older data." + + $"Old ID {existingProcess.LastProcessedRecordId} New ID: {newHistoryRecord.LastProcessedRecordId}"); + } + + existingProcess.LastProcessedRecordId = newHistoryRecord.LastProcessedRecordId; + existingProcess.LastProcessedRecordTimestamp = newHistoryRecord.LastProcessedRecordTimestamp; + existingProcess.LastSuccessfulRunTime = newHistoryRecord.LastSuccessfulRunTime; + return existingProcess.Process; + } + + public async Task GetProcessorRunHistory(Process process) + { + return await _context.ProcessorRunHistory.FindAsync(process); + } + + public async Task> GetAllProcessorRunHistory() + { + return await _context.ProcessorRunHistory.ToListAsync(); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/RoleMappingRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/RoleMappingRepository.cs new file mode 100644 index 0000000..86230a5 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/RoleMappingRepository.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using Microsoft.EntityFrameworkCore; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public class RoleMappingRepository : BaseRepository, IRoleMappingRepository +{ + public RoleMappingRepository(IApplicationDbContext context) : base(context) + { + } + + public async Task> GetAllRoleMappingsAsync() + { + return await _context.RoleMappings.OrderBy(x => x.BusinessRole).ToListAsync(); + } + + public async Task GetRoleMappingByIdAsync(int id) + { + return await _context.RoleMappings.FirstOrDefaultAsync(x => x.Id == id); + } + + public async Task UpdateRoleMapping(RoleMapping roleMapping) + { + _context.RoleMappings.Update(roleMapping); + return roleMapping.Id; + } + + public async Task SaveRoleMappingAsync(RoleMapping roleMapping) + { + await _context.RoleMappings.AddAsync(roleMapping); + return roleMapping.Id; + } + + public async Task SaveRoleMappingRangeAsync(IEnumerable roleMappings) + { + await _context.RoleMappings.AddRangeAsync(roleMappings); + } + + public async Task SaveNewRoleMappingRangeAsync(IEnumerable roleMappings) + { + await _context.RoleMappings.AddRangeAsync(roleMappings); + } + + public Role FindMatchingRoleFromBusinessRole(string queryRole) + { + if (string.IsNullOrWhiteSpace(queryRole)) + { + return Role.MISSING_ROLE; + } + + var returnRole = _context.RoleMappings.Where(x => x.BusinessRole.ToUpper() == queryRole.ToUpper()).FirstOrDefault() + ?.ApplicationRole; + + if (returnRole == null) + { + returnRole = Role.MISSING_ROLE; + } + + return returnRole; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/UnitOfWork.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/UnitOfWork.cs new file mode 100644 index 0000000..239cbd3 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/UnitOfWork.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public class UnitOfWork : IUnitOfWork +{ + private readonly ApplicationDbContext _context; + + public UnitOfWork(ApplicationDbContext context) + { + _context = context; + } + + public async Task CompleteAsync() + { + await _context.SaveChangesAsync(); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/UserAssignmentRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/UserAssignmentRepository.cs new file mode 100644 index 0000000..ec1ea80 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/UserAssignmentRepository.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using Microsoft.EntityFrameworkCore; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public class UserAssignmentRepository : BaseRepository, IUserAssignmentRepository +{ + public UserAssignmentRepository(IApplicationDbContext context) : base(context) + { + } + + public async Task> GetAllUserAssignmentsAsync() + { + return await _context.UserAssignments.Include(x => x.AppUser).ToListAsync(); + } + + public async Task FindExistingAssignmentAsync(AssignmentType assignmentType, string key) + { + return await _context.UserAssignments + .Include(x => x.AppUser) + .FirstOrDefaultAsync(x => x.Type == assignmentType && x.Key == key); + } + + public async Task IsUserAssigned(AssignmentType assignmentType, AppUser appUser) + { + return await _context.UserAssignments + .Include(x => x.AppUser) + .FirstOrDefaultAsync(x => x.Type == assignmentType + && x.AppUser.EmployeeId == appUser.EmployeeId); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/UserDepartmentHistoryRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/UserDepartmentHistoryRepository.cs new file mode 100644 index 0000000..dbafece --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/UserDepartmentHistoryRepository.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Requests; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public class UserDepartmentHistoryRepository : BaseRepository, IUserDepartmentHistoryRepository +{ + public UserDepartmentHistoryRepository(IApplicationDbContext context) : base(context) + { + } + + public async Task> SaveUserDepartmentUpdate(UserDepartment currentUserDepartment, DepartmentUpdateRequest departmentUpdateRequest) + { + var userDepartmentHistoryUpdates = new List(); + + if (currentUserDepartment.Role.Name != departmentUpdateRequest.Role) + { + userDepartmentHistoryUpdates.Add(new UserDepartmentHistory + { + UserDepartmentId = currentUserDepartment.Id, + FieldName = "Role", + NewValue = departmentUpdateRequest.Role, + OldValue = currentUserDepartment.Role.Name + }); + } + + if (currentUserDepartment.SlidingPayout != departmentUpdateRequest.SlidingPayout) + { + userDepartmentHistoryUpdates.Add(new UserDepartmentHistory + { + UserDepartmentId = currentUserDepartment.Id, + FieldName = "SlidingPayout", + NewValue = departmentUpdateRequest.SlidingPayout.ToString(CultureInfo.InvariantCulture), + OldValue = currentUserDepartment.SlidingPayout.ToString(CultureInfo.InvariantCulture) + }); + } + + if (currentUserDepartment.EndDate?.Date != departmentUpdateRequest.EndDate?.Date) + { + userDepartmentHistoryUpdates.Add(new UserDepartmentHistory + { + UserDepartmentId = currentUserDepartment.Id, + FieldName = "EndDate", + NewValue = departmentUpdateRequest.EndDate.ToString(), + OldValue = currentUserDepartment.EndDate.ToString() + }); + } + + await _context.UserDepartmentHistories.AddRangeAsync(userDepartmentHistoryUpdates); + return userDepartmentHistoryUpdates; + } + + public async Task> GetUserDepartmentHistory(int employeeId) + { + return await _context.UserPermissionHistories.Where(history => history.EmployeeId == employeeId).ToListAsync(); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/UserPermissionHistoryRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/UserPermissionHistoryRepository.cs new file mode 100644 index 0000000..959ae47 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/UserPermissionHistoryRepository.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using Microsoft.EntityFrameworkCore; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public class UserPermissionHistoryRepository : BaseRepository, IUserPermissionHistoryRepository +{ + public UserPermissionHistoryRepository(IApplicationDbContext context) : base(context) + { + } + + public async Task> SaveUserPermissionsInsertion(IEnumerable newPermissions) + { + var newPermissionHistory = newPermissions.Select(userPermission => new UserPermissionHistory + { + Id = 0, + Source = "Manual Assignment", + EmployeeId = Convert.ToInt32(userPermission.AppUser.EmployeeId), + Permission = userPermission.Permission.Code, + PermissionAdded = true + }) + .ToList(); + + await _context.UserPermissionHistories.AddRangeAsync(newPermissionHistory); + return newPermissionHistory; + } + + public async Task> SaveUserPermissionsDeletion(IEnumerable newPermissions) + { + var newPermissionHistory = newPermissions.Select(userPermission => new UserPermissionHistory + { + Id = 0, + Source = "Manual Assignment", + EmployeeId = Convert.ToInt32(userPermission.AppUser.EmployeeId), + Permission = userPermission.Permission.Code, + PermissionRemoved = true + }) + .ToList(); + + await _context.UserPermissionHistories.AddRangeAsync(newPermissionHistory); + return newPermissionHistory; + } + + public async Task> GetUserPermissionHistory(int employeeId) + { + return await _context.UserPermissionHistories.Where(history => history.EmployeeId == employeeId).ToListAsync(); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/UserRepository.cs b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/UserRepository.cs new file mode 100644 index 0000000..948626c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiLib/Repositories/UserRepository.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; +using LCU.Library.Internal.IncentiveApiModels.Common; +using LCU.Library.Internal.IncentiveApiModels.Entities; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using Microsoft.EntityFrameworkCore; + +namespace LCU.API.Internal.Operations.IncentiveApiLib.Repositories; + +public class UserRepository : BaseRepository, IUserRepository +{ + public UserRepository(IApplicationDbContext context) : base(context) + { + } + + public async Task> GetAllUserSummariesAsync() + { + return await _context.AppUsers + .Select(x => new UserSummary() + { + EmployeeId = x.EmployeeId, + AdEmail = x.AdEmail, + FullName = x.FullName, + ManagerKey = x.ManagerKey, + Terminated = x.Terminated + }) + .OrderBy(x => x.EmployeeId) + .ToListAsync(); + } + + public IReadOnlyCollection GetTerminatedUsersAsync(MonthRange monthRange) + { + if (!monthRange.IsSingleMonth) + { + throw new ArgumentException("Can only request terminated employees for a single month"); + } + + return _context.AppUsers + .Where(x => x.Terminated != null + && x.Terminated >= monthRange.StartTime + && x.Terminated <= monthRange.EndTime) + .ToList() + .GroupJoin(_context.PayoutLineItems, + user => user.EmployeeId, + pli => pli.EmployeeId, + (x, pli) => new UserSummary() + { + EmployeeId = x.EmployeeId, + AdEmail = x.AdEmail, + FullName = x.FullName, + ManagerKey = x.ManagerKey, + Terminated = x.Terminated, + AssociatedPayouts = pli.ToList() + }) + .OrderBy(x => x.EmployeeId) + .ToList(); + } + + public async Task> GetAllUsersAsync() + { + return await _context.AppUsers + .Include(user => user.Departments) + .ThenInclude(userDept => userDept.Department) + .ToListAsync(); + } + + public async Task> GetAllUsersToPayoutAsync(MonthRange range) + { + return await _context.AppUsers + .AsNoTracking() + .Include(user => user.Departments) + .ThenInclude(userDept => userDept.Department) + .Where(x => x.Terminated == null + && x.Departments.Any(y => y.Department.State == DepartmentState.Active + && y.StartDate.Date <= range.StartTime.Date + && (y.EndDate == null || y.EndDate >= range.EndTime.Date))) + .Distinct() + .ToListAsync(); + } + + public async Task FindUserAsync(string employeeId, string adEmail) + { + if (!string.IsNullOrWhiteSpace(employeeId)) + { + return await FindUserByEmployeeIdAsync(employeeId); + } + + return await FindUserByAdEmailAsync(adEmail); + } + + public async Task FindUserByEmployeeIdAsync(string employeeId) + { + return await _context.AppUsers + .Include(user => user.AdditionalPermissions) + .Include(user => user.Departments) + .ThenInclude(userDept => userDept.Department) + .Include(user => user.Assignments) + .Where(user => user.EmployeeId == employeeId) + .OrderByDescending(user => user.Terminated) + .FirstOrDefaultAsync(); + } + + public async Task FindUserByAdEmailAsync(string adEmail) + { + return await _context.AppUsers + .Include(user => user.AdditionalPermissions) + .Include(user => user.Departments) + .ThenInclude(userDept => userDept.Department) + .Include(user => user.Assignments) + .Where(user => user.AdEmail.ToLower() == adEmail.ToLower()) + .OrderByDescending(user => user.Terminated) + .FirstOrDefaultAsync(); + } + + public async Task FindActiveUserByEmployeeIdAsync(string employeeId) + { + return await _context.AppUsers + .Include(user => user.AdditionalPermissions) + .Include(user => user.Departments) + .ThenInclude(userDept => userDept.Department) + .Include(user => user.Assignments) + .Where(user => user.EmployeeId == employeeId && user.Terminated == null) + .FirstOrDefaultAsync(); + } + + public async Task FindActiveUserByAdEmailAsync(string adEmail) + { + return await _context.AppUsers + .Include(user => user.AdditionalPermissions) + .Include(user => user.Departments) + .ThenInclude(userDept => userDept.Department) + .Include(user => user.Assignments) + .Where(user => user.AdEmail.ToLower() == adEmail.ToLower() && user.Terminated == null) + .FirstOrDefaultAsync(); + } + + public async Task> FindUsersByHomeDepartmentsAsync(List homeDepartments) + { + return await _context.AppUsers.Where(user => homeDepartments.Contains(user.HomeDepartment)).ToListAsync(); + } + + public async Task> FindUsersByBaseRolesAsync(List baseRoles) + { + return await _context.AppUsers.Where(user => baseRoles.Contains(user.BaseRole)).ToListAsync(); + } + + public async Task SaveUserAsync(AppUser newAppUser) + { + await _context.AppUsers.AddAsync(newAppUser); + return newAppUser; + } + + public async Task> SaveUserRangeAsync(List newUsers) + { + await _context.AppUsers.AddRangeAsync(newUsers); + return newUsers; + } + + public async Task> SearchUsersAsync(UserSearchRequest userSearchRequest) + { + var users = _context.AppUsers.Include(user => user.Departments).AsQueryable(); + if (!string.IsNullOrWhiteSpace(userSearchRequest.Query)) + { + var cleanQuery = userSearchRequest.Query.ToLower().Trim(); + var emailQuery = BuildEmailQuery(cleanQuery); + + users = users.Where(user => user.AdEmail.ToLower().Contains(cleanQuery) || + user.AdEmail.ToLower().Contains(emailQuery) || + user.FullName.ToLower().Contains(cleanQuery) || + user.EmployeeId.ToLower().Contains(cleanQuery)); + } + + if (!string.IsNullOrWhiteSpace(userSearchRequest.FirstName)) + { + users = users.Where(user => user.FullName.ToLower().Contains(userSearchRequest.FirstName.ToLower())); + } + + if (!string.IsNullOrWhiteSpace(userSearchRequest.LastName)) + { + users = users.Where(user => user.FullName.ToLower().Contains(userSearchRequest.LastName.ToLower())); + } + + if (!string.IsNullOrWhiteSpace(userSearchRequest.MiddleInit)) + { + users = users.Where(user => user.FullName.ToLower().EndsWith($" {userSearchRequest.MiddleInit.ToLower()}")); + } + + if (!string.IsNullOrWhiteSpace(userSearchRequest.Role)) + { + var foundRoles = Role.SearchRoles(userSearchRequest.Role); + users = users.Where(user => user.Departments.Any(dept => foundRoles.Contains(dept.Role))); + } + + if (userSearchRequest.DnaOrgNbr != null) + { + users = users.Where(user => user.Departments.Any(dept => dept.Department.DnaOrgNbr == userSearchRequest.DnaOrgNbr)); + } + + return await users.ToListAsync(); + } + + private string BuildEmailQuery(string cleanQuery) + { + if (cleanQuery.Contains(",")) + { + var transform = cleanQuery.Split(","); + if (transform?.Length == 2) + { + return transform[1].Trim() + transform[0].Trim(); + } + } + else if (cleanQuery.Contains(" ")) + { + return cleanQuery.Replace(" ", ""); + } + + return cleanQuery; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/Activity.sql b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/Activity.sql new file mode 100644 index 0000000..f9146ae --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/Activity.sql @@ -0,0 +1,29 @@ +CREATE TABLE [dbo].[Activity]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [CreatedBy] [nvarchar](max) NULL, + [Created] [datetime2](7) NOT NULL, + [CreatedByType] [int] NOT NULL, + [LastUpdatedBy] [nvarchar](max) NULL, + [LastUpdated] [datetime2](7) NOT NULL, + [LastUpdatedByType] [int] NOT NULL, + [Product] [nvarchar](max) NOT NULL, + [Type] [nvarchar](max) NOT NULL, + [SourceSystem] [nvarchar](max) NOT NULL, + [SourceId] [nvarchar](max) NOT NULL, + [SourceCreated] [datetime2](7) NOT NULL, + [DepartmentId] [int] NOT NULL, + [Accepted] [datetime2](7) NOT NULL, + [State] [nvarchar](max) NOT NULL, + [EmployeeEmail] [nvarchar](max) NULL, + [EmployeeId] [nvarchar](max) NOT NULL DEFAULT N'', + [MatchingActivityId] [int] NOT NULL DEFAULT 0, + [MatchingDpAccessoryActivityId] [int] NOT NULL DEFAULT 0, + [AppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + [CreatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + [LastUpdatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + [MatchingGapInsuranceActivityId] [int] NOT NULL DEFAULT 0, + CONSTRAINT [PK_Activity] PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/ActivityDetail.sql b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/ActivityDetail.sql new file mode 100644 index 0000000..e95204c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/ActivityDetail.sql @@ -0,0 +1,27 @@ +CREATE TABLE [dbo].[ActivityDetail]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [AccountNumber] [nvarchar](max) NULL, + [AccountStatus] [nvarchar](max) NULL, + [AgreementNumber] [nvarchar](max) NULL, + [MemberName] [nvarchar](max) NULL, + [AccountBalance] [float] NULL, + [AccountNewBalance] [float] NULL, + [ActivityRef] [int] NOT NULL, + [BranchOrgName] [nvarchar](max) NULL, + [BranchOrgNbr] [nvarchar](max) NULL, + [DnaPersonNbr] [nvarchar](max) NULL, + [StationName] [nvarchar](max) NULL, + [SubjectOrgNbr] [nvarchar](max) NULL, + [SubjectPersonNbr] [nvarchar](max) NULL, + [DebtProtectionAddDate] [datetime2](7) NULL, + [IsDebtProtectionEligible] [bit] NOT NULL, + [DebtProtectionRemoveDate] [datetime2](7) NULL, + [SubAccountNumber] [nvarchar](max) NULL DEFAULT N'', + [GapInsuranceAddDate] [datetime2](7) NULL, + [GapInsuranceRemoveDate] [datetime2](7) NULL, + [IsGapInsuranceEligible] [bit] NOT NULL, + CONSTRAINT [PK_ActivityDetail] PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/ActivityDetailUpdates.sql b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/ActivityDetailUpdates.sql new file mode 100644 index 0000000..2a6a65c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/ActivityDetailUpdates.sql @@ -0,0 +1,13 @@ +CREATE TABLE [dbo].[ActivityDetailUpdates]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [CreatedBy] [nvarchar](max) NULL, + [Created] [datetime2](7) NOT NULL, + [CreatedByType] [int] NOT NULL, + [ActivityDetailId] [int] NOT NULL, + [Note] [nvarchar](max) NULL, + [CreatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + CONSTRAINT [PK_ActivityDetailUpdates] PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/ActivityMetaData.sql b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/ActivityMetaData.sql new file mode 100644 index 0000000..fb98ac3 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/ActivityMetaData.sql @@ -0,0 +1,11 @@ +CREATE TABLE [dbo].[ActivityMetaData]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [MetaData] [nvarchar](max) NULL, + [MetaDataType] [nvarchar](max) NOT NULL, + [ActivityRef] [int] NOT NULL, + CONSTRAINT [PK_ActivityMetaData] PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] +GO diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/ActivityPayouts.sql b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/ActivityPayouts.sql new file mode 100644 index 0000000..a4ca578 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/ActivityPayouts.sql @@ -0,0 +1,10 @@ +CREATE TABLE [dbo].[ActivityPayouts]( + [ActivityId] [int] NOT NULL, + [PayoutLineItemId] [int] NOT NULL, + CONSTRAINT [PK_ActivityPayouts] PRIMARY KEY CLUSTERED +( + [ActivityId] ASC, + [PayoutLineItemId] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY] +) ON [PRIMARY] +GO diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/ActivityStateChanges.sql b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/ActivityStateChanges.sql new file mode 100644 index 0000000..d3bc882 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/ActivityStateChanges.sql @@ -0,0 +1,17 @@ +CREATE TABLE [dbo].[ActivityStateChanges]( + [Id] [int] IDENTITY(1,1) NOT NULL , + [CreatedBy] [nvarchar](max) NULL, + [Created] [datetime2](7) NOT NULL, + [CreatedByType] [int] NOT NULL, + [ActivityId] [int] NOT NULL, + [State] [nvarchar](max) NOT NULL, + [Note] [nvarchar](max) NULL, + [ActivityMatchId] [int] NOT NULL, + [AcceptedDate] [datetime2](7) NULL, + [CreatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + CONSTRAINT [PK_ActivityStateChanges] PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] + diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/ActivityTransferActions.sql b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/ActivityTransferActions.sql new file mode 100644 index 0000000..5146ee2 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/ActivityTransferActions.sql @@ -0,0 +1,26 @@ +CREATE TABLE [dbo].[ActivityTransferActions]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [CreatedBy] [nvarchar](max) NULL, + [Created] [datetime2](7) NOT NULL, + [CreatedByType] [int] NOT NULL, + [LastUpdatedBy] [nvarchar](max) NULL, + [LastUpdated] [datetime2](7) NOT NULL, + [LastUpdatedByType] [int] NOT NULL, + [ActivityStateBeforeTransfer] [nvarchar](max) NOT NULL, + [RequestedActivityId] [int] NOT NULL, + [State] [nvarchar](max) NOT NULL, + [NewActivityId] [int] NULL, + [NewActivityDepartmentId] [int] NOT NULL, + [NewActivityEmployeeId] [nvarchar](max) NOT NULL, + [NewDepartmentApproved] [datetime2](7) NULL, + [NewDepartmentApprover] [nvarchar](max) NULL, + [RequestedDepartmentApproved] [datetime2](7) NULL, + [RequestedDepartmentApprover] [nvarchar](max) NULL, + [CreatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + [LastUpdatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + [NewAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + CONSTRAINT [PK_ActivityTransferActions] PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/AppUser.sql b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/AppUser.sql new file mode 100644 index 0000000..d90ca35 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/AppUser.sql @@ -0,0 +1,24 @@ +CREATE TABLE [dbo].[AppUser]( + [AdEmail] [nvarchar](max) NULL, + [PersonId] [int] NOT NULL DEFAULT 0, + [EmployeeId] [nvarchar](max) NULL, + [CreatedBy] [nvarchar](max) NULL, + [Created] [datetime2](7) NOT NULL, + [CreatedByType] [int] NOT NULL, + [LastUpdatedBy] [nvarchar](max) NULL, + [LastUpdated] [datetime2](7) NOT NULL, + [LastUpdatedByType] [int] NOT NULL, + [HomeDepartment] [nvarchar](max) NOT NULL, + [BaseRole] [nvarchar](max) NOT NULL, + [ManagerKey] [nvarchar](max) NULL, + [Role] [nvarchar](max) NOT NULL, + [FullName] [nvarchar](max) NOT NULL, + [Terminated] [datetime2](7) NULL, + [CreatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + [Id] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + [LastUpdatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + CONSTRAINT [PK_AppUser] PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/Commissions.sql b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/Commissions.sql new file mode 100644 index 0000000..86ca8f4 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/Commissions.sql @@ -0,0 +1,18 @@ +CREATE TABLE [dbo].[Commissions]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [CreatedBy] [nvarchar](max) NULL, + [Created] [datetime2](7) NOT NULL, + [CreatedByType] [int] NOT NULL, + [LastUpdatedBy] [nvarchar](max) NULL, + [LastUpdated] [datetime2](7) NOT NULL, + [LastUpdatedByType] [int] NOT NULL, + [Role] [nvarchar](450) NOT NULL, + [RatePerProduct] [int] NOT NULL, + [Product] [nvarchar](max) NOT NULL, + [CreatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + [LastUpdatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + CONSTRAINT [PK_Commissions] PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/DeadActivity.sql b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/DeadActivity.sql new file mode 100644 index 0000000..c01e0cb --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/DeadActivity.sql @@ -0,0 +1,29 @@ +CREATE TABLE [dbo].[DeadActivity]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [CreatedBy] [nvarchar](max) NULL, + [Created] [datetime2](7) NOT NULL, + [CreatedByType] [int] NOT NULL, + [LastUpdatedBy] [nvarchar](max) NULL, + [LastUpdated] [datetime2](7) NOT NULL, + [LastUpdatedByType] [int] NOT NULL, + [Reason] [nvarchar](max) NOT NULL, + [ReasonNote] [nvarchar](max) NULL, + [Product] [nvarchar](max) NULL, + [Type] [nvarchar](max) NULL, + [SourceSystem] [nvarchar](max) NULL, + [SourceId] [nvarchar](max) NULL, + [SourceCreated] [datetime2](7) NULL, + [DepartmentId] [int] NULL, + [EmployeeId] [nvarchar](max) NULL, + [EmployeeEmail] [nvarchar](max) NULL, + [Accepted] [datetime2](7) NULL, + [State] [nvarchar](max) NULL, + [LivingActivityId] [int] NULL, + [AppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + [CreatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + [LastUpdatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + CONSTRAINT [PK_DeadActivity] PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/DeadActivityDetail.sql b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/DeadActivityDetail.sql new file mode 100644 index 0000000..c4515eb --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/DeadActivityDetail.sql @@ -0,0 +1,27 @@ +CREATE TABLE [dbo].[DeadActivityDetail]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [DnaPersonNbr] [nvarchar](max) NULL, + [AccountNumber] [nvarchar](max) NULL, + [AccountStatus] [nvarchar](max) NULL, + [AgreementNumber] [nvarchar](max) NULL, + [SubjectPersonNbr] [nvarchar](max) NULL, + [SubjectOrgNbr] [nvarchar](max) NULL, + [MemberName] [nvarchar](max) NULL, + [StationName] [nvarchar](max) NULL, + [BranchOrgNbr] [nvarchar](max) NULL, + [BranchOrgName] [nvarchar](max) NULL, + [AccountBalance] [float] NULL, + [AccountNewBalance] [float] NULL, + [DeadActivityRef] [int] NOT NULL, + [DebtProtectionAddDate] [datetime2](7) NULL, + [IsDebtProtectionEligible] [bit] NULL, + [DebtProtectionRemoveDate] [datetime2](7) NULL, + [SubAccountNumber] [nvarchar](max) NULL, + [GapInsuranceAddDate] [datetime2](7) NULL, + [GapInsuranceRemoveDate] [datetime2](7) NULL, + [IsGapInsuranceEligible] [bit] NOT NULL DEFAULT 0, + CONSTRAINT [PK_DeadActivityDetail] PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/DepartmentLineItems.sql b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/DepartmentLineItems.sql new file mode 100644 index 0000000..4accf5f --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/DepartmentLineItems.sql @@ -0,0 +1,20 @@ +CREATE TABLE [dbo].[DepartmentLineItems]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [CreatedBy] [nvarchar](max) NULL, + [Created] [datetime2](7) NOT NULL, + [CreatedByType] [int] NOT NULL, + [LastUpdatedBy] [nvarchar](max) NULL, + [LastUpdated] [datetime2](7) NOT NULL, + [LastUpdatedByType] [int] NOT NULL, + [DepartmentId] [int] NOT NULL, + [DepartmentName] [nvarchar](max) NULL, + [PayoutTotal] [decimal](12, 2) NOT NULL, + [PayoutPercentage] [decimal](5, 2) NOT NULL, + [PayoutMonth] [datetime2](7) NOT NULL, + [CreatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + [LastUpdatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + CONSTRAINT [PK_DepartmentLineItems] PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/Departments.sql b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/Departments.sql new file mode 100644 index 0000000..5b1a064 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/Departments.sql @@ -0,0 +1,22 @@ +CREATE TABLE [dbo].[Departments]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [CreatedBy] [nvarchar](max) NULL, + [Created] [datetime2](7) NOT NULL, + [CreatedByType] [int] NOT NULL, + [LastUpdatedBy] [nvarchar](max) NULL, + [LastUpdated] [datetime2](7) NOT NULL, + [LastUpdatedByType] [int] NOT NULL, + [DepartmentName] [nvarchar](255) NOT NULL, + [BusinessLineCode] [nvarchar](max) NOT NULL, + [RegionCode] [nvarchar](max) NULL, + [State] [nvarchar](max) NOT NULL, + [DayForceGlCode] [nvarchar](max) NOT NULL DEFAULT N'', + [DayforceDepartmentName] [nvarchar](255) NOT NULL DEFAULT N'', + [DnaOrgNbr] [nvarchar](max) NOT NULL DEFAULT N'', + [CreatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + [LastUpdatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + CONSTRAINT [PK_Departments] PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/DeptAssignments.sql b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/DeptAssignments.sql new file mode 100644 index 0000000..aafee89 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/DeptAssignments.sql @@ -0,0 +1,23 @@ +CREATE TABLE [dbo].[DeptAssignments]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [CreatedBy] [nvarchar](max) NULL, + [Created] [datetime2](7) NOT NULL, + [CreatedByType] [int] NOT NULL, + [LastUpdatedBy] [nvarchar](max) NULL, + [LastUpdated] [datetime2](7) NOT NULL, + [LastUpdatedByType] [int] NOT NULL, + [DepartmentId] [int] NOT NULL, + [StartDate] [datetime2](7) NOT NULL, + [EndDate] [datetime2](7) NULL, + [Note] [nvarchar](max) NULL, + [IsHome] [bit] NOT NULL, + [Role] [nvarchar](max) NOT NULL DEFAULT N'', + [SlidingPayout] [decimal](5, 2) NOT NULL, + [AppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + [CreatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + [LastUpdatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + CONSTRAINT [PK_DeptAssignments] PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/GoalChanges.sql b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/GoalChanges.sql new file mode 100644 index 0000000..e488aad --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/GoalChanges.sql @@ -0,0 +1,18 @@ +CREATE TABLE [dbo].[GoalChanges]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [CreatedBy] [nvarchar](max) NULL, + [Created] [datetime2](7) NOT NULL, + [CreatedByType] [int] NOT NULL, + [Product] [nvarchar](max) NOT NULL, + [StartDate] [datetime2](7) NOT NULL, + [EndDate] [datetime2](7) NOT NULL, + [DepartmentId] [int] NOT NULL, + [PersonId] [nvarchar](max) NULL, + [Value] [int] NOT NULL, + [AppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + [CreatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + CONSTRAINT [PK_GoalChanges] PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/Goals.sql b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/Goals.sql new file mode 100644 index 0000000..57a4501 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/Goals.sql @@ -0,0 +1,22 @@ +CREATE TABLE [dbo].[Goals]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [CreatedBy] [nvarchar](max) NULL, + [Created] [datetime2](7) NOT NULL, + [CreatedByType] [int] NOT NULL, + [LastUpdatedBy] [nvarchar](max) NULL, + [LastUpdated] [datetime2](7) NOT NULL, + [LastUpdatedByType] [int] NOT NULL, + [DepartmentId] [int] NOT NULL, + [StartDate] [datetime2](7) NOT NULL, + [EndDate] [datetime2](7) NOT NULL, + [Product] [nvarchar](max) NOT NULL, + [Value] [int] NOT NULL, + [EmployeeId] [nvarchar](max) NULL, + [AppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + [CreatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + [LastUpdatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + CONSTRAINT [PK_Goals] PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/LCU.API.Internal.Operations.IncentiveApiSql.sqlproj b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/LCU.API.Internal.Operations.IncentiveApiSql.sqlproj new file mode 100644 index 0000000..e047be0 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/LCU.API.Internal.Operations.IncentiveApiSql.sqlproj @@ -0,0 +1,85 @@ + + + + Debug + AnyCPU + LCU.API.Internal.Operations.IncentiveApiSql + 2.0 + 4.1 + {5d6dc990-f038-45d7-bbfa-06cab0682b4f} + Microsoft.Data.Tools.Schema.Sql.Sql130DatabaseSchemaProvider + Database + + + LCU.API.Internal.Operations.IncentiveApiSql + LCU.API.Internal.Operations.IncentiveApiSql + 1033, CI + BySchemaAndSchemaType + True + v4.7.2 + CS + Properties + False + True + True + + + bin\Release\ + $(MSBuildProjectName).sql + False + pdbonly + true + false + true + prompt + 4 + + + bin\Debug\ + $(MSBuildProjectName).sql + false + true + full + false + true + true + prompt + 4 + + + 11.0 + + True + 11.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/PayoutLineItems.sql b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/PayoutLineItems.sql new file mode 100644 index 0000000..4869652 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/PayoutLineItems.sql @@ -0,0 +1,25 @@ +CREATE TABLE [dbo].[PayoutLineItems]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [CreatedBy] [nvarchar](max) NULL, + [Created] [datetime2](7) NOT NULL, + [CreatedByType] [int] NOT NULL, + [EmployeeId] [nvarchar](max) NOT NULL, + [DepartmentId] [int] NOT NULL, + [Role] [nvarchar](max) NOT NULL, + [Product] [nvarchar](max) NOT NULL, + [ActivityPeriod] [datetime2](7) NOT NULL, + [GoalId] [int] NOT NULL, + [GoalValue] [int] NOT NULL, + [CommissionId] [int] NOT NULL, + [CommissionValue] [int] NOT NULL, + [PayoutProfileId] [int] NOT NULL, + [ActivityTotal] [decimal](12, 2) NOT NULL, + [PayoutTotal] [decimal](12, 2) NOT NULL, + [SlidingPayout] [decimal](5, 2) NOT NULL, + [AppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + [CreatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + CONSTRAINT [PK_PayoutLineItems] PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/ProcessorRunHistory.sql b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/ProcessorRunHistory.sql new file mode 100644 index 0000000..3a70fee --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/ProcessorRunHistory.sql @@ -0,0 +1,10 @@ +CREATE TABLE [dbo].[ProcessorRunHistory]( + [Process] [nvarchar](450) NOT NULL, + [LastSuccessfulRunTime] [datetime2](7) NOT NULL, + [LastProcessedRecordId] [bigint] NOT NULL, + [LastProcessedRecordTimestamp] [datetime2](7) NOT NULL, + CONSTRAINT [PK_ProcessorRunHistory] PRIMARY KEY CLUSTERED +( + [Process] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY] +) ON [PRIMARY] diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/RoleMappings.sql b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/RoleMappings.sql new file mode 100644 index 0000000..606b494 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/RoleMappings.sql @@ -0,0 +1,17 @@ +CREATE TABLE [dbo].[RoleMappings]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [ApplicationRole] [nvarchar](max) NOT NULL, + [BusinessRole] [nvarchar](max) NOT NULL, + [CreatedBy] [nvarchar](max) NULL, + [Created] [datetime2](7) NOT NULL, + [CreatedByType] [int] NOT NULL, + [LastUpdatedBy] [nvarchar](max) NULL, + [LastUpdated] [datetime2](7) NOT NULL, + [LastUpdatedByType] [int] NOT NULL, + [CreatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + [LastUpdatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + CONSTRAINT [PK_RoleMappings] PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/UserAssignments.sql b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/UserAssignments.sql new file mode 100644 index 0000000..cc983e3 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/UserAssignments.sql @@ -0,0 +1,10 @@ +CREATE TABLE [dbo].[UserAssignments]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [Type] [nvarchar](max) NOT NULL, + [Key] [nvarchar](max) NOT NULL, + [AppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + CONSTRAINT [PK_UserAssignments] PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/UserDepartmentHistory.sql b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/UserDepartmentHistory.sql new file mode 100644 index 0000000..3467e02 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/UserDepartmentHistory.sql @@ -0,0 +1,15 @@ +CREATE TABLE [dbo].[UserDepartmentHistory]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [UserDepartmentId] [int] NOT NULL, + [FieldName] [nvarchar](max) NULL, + [OldValue] [nvarchar](max) NULL, + [NewValue] [nvarchar](max) NULL, + [CreatedBy] [nvarchar](max) NULL, + [Created] [datetime2](7) NOT NULL, + [CreatedByType] [int] NOT NULL, + [CreatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + CONSTRAINT [PK_UserDepartmentHistory] PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/UserPermissionHistory.sql b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/UserPermissionHistory.sql new file mode 100644 index 0000000..7f2afcc --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/UserPermissionHistory.sql @@ -0,0 +1,17 @@ +CREATE TABLE [dbo].[UserPermissionHistory]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [EmployeeId] [int] NOT NULL, + [Source] [nvarchar](max) NULL, + [PermissionAdded] [bit] NOT NULL, + [PermissionRemoved] [bit] NOT NULL, + [CreatedBy] [nvarchar](max) NULL, + [Created] [datetime2](7) NOT NULL, + [CreatedByType] [int] NOT NULL, + [Permission] [nvarchar](max) NULL, + [AppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + [CreatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + CONSTRAINT [PK_UserPermissionHistory] PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] diff --git a/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/UserPermissions.sql b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/UserPermissions.sql new file mode 100644 index 0000000..a9fedf2 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApiSql/UserPermissions.sql @@ -0,0 +1,13 @@ +CREATE TABLE [dbo].[UserPermissions]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [CreatedBy] [nvarchar](max) NULL, + [Created] [datetime2](7) NOT NULL, + [CreatedByType] [int] NOT NULL, + [Permission] [nvarchar](max) NOT NULL, + [AppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + [CreatedByAppUserId] [uniqueidentifier] NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + CONSTRAINT [PK_UserPermissions] PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/ActivityFilter.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/ActivityFilter.cs new file mode 100644 index 0000000..1603808 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/ActivityFilter.cs @@ -0,0 +1,42 @@ +#nullable enable +using System; +using System.Collections.Generic; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; + +namespace LCU.Library.Internal.IncentiveApiModels.Common +{ + public class ActivityFilter: PageFilter + { + [FromQuery(Name = "state")] + public List? State { get; set; } + + [FromQuery(Name = "query")] + public string? Query { get; set; } + + [FromQuery(Name = "product")] + public List? Product { get; set; } + + [FromQuery(Name = "type")] + public List? Type { get; set; } + + [FromQuery(Name = "dateLow")] + public DateTime? DateLow { get; set; } + + [FromQuery(Name = "dateHigh")] + public DateTime? DateHigh { get; set; } + + [FromQuery(Name = "departmentId")] + public int DepartmentId { get; set; } + + [FromQuery(Name = "employeeId")] + public string? EmployeeId { get; set; } + + public override object Clone() + { + var jsonString = JsonConvert.SerializeObject(this); + return JsonConvert.DeserializeObject(jsonString, this.GetType()); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/AuditCreate.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/AuditCreate.cs new file mode 100644 index 0000000..7792a85 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/AuditCreate.cs @@ -0,0 +1,16 @@ +using System; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace LCU.Library.Internal.IncentiveApiModels.Common +{ + public abstract class AuditCreate + { + public string CreatedBy { get; set; } + public Guid CreatedByAppUserId { get; set; } + public DateTime Created { get; set; } + [JsonConverter(typeof(StringEnumConverter))] + public UserType CreatedByType { get; set; } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/Auditable.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/Auditable.cs new file mode 100644 index 0000000..2324795 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/Auditable.cs @@ -0,0 +1,17 @@ +using System; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace LCU.Library.Internal.IncentiveApiModels.Common +{ + public abstract class Auditable : AuditCreate + { + public string LastUpdatedBy { get; set; } + public Guid LastUpdatedByAppUserId { get; set; } + public DateTime LastUpdated { get; set; } + + [JsonConverter(typeof(StringEnumConverter))] + public UserType LastUpdatedByType { get; set; } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/DeadActivityFilter.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/DeadActivityFilter.cs new file mode 100644 index 0000000..274eefd --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/DeadActivityFilter.cs @@ -0,0 +1,6 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Common +{ + public class DeadActivityFilter: ActivityFilter + { + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/IViewTransformable.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/IViewTransformable.cs new file mode 100644 index 0000000..cfafe4d --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/IViewTransformable.cs @@ -0,0 +1,7 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Common +{ + public interface IViewTransformable + { + public T TransformForView(); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/MonthRange.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/MonthRange.cs new file mode 100644 index 0000000..0e9e737 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/MonthRange.cs @@ -0,0 +1,68 @@ +using System; + +namespace LCU.Library.Internal.IncentiveApiModels.Common +{ + public sealed class MonthRange + { + public int FromMonth { get; } + public int FromYear { get; } + public int ToMonth { get; } + public int ToYear { get; } + + public MonthRange() + { + var today = DateTime.Today; + FromMonth = today.Month; + ToMonth = today.Month; + FromYear = today.Year; + ToYear = today.Year; + } + + public MonthRange(DateTime period) + { + FromMonth = period.Month; + ToMonth = period.Month; + FromYear = period.Year; + ToYear = period.Year; + } + + public MonthRange(DateTime fromPeriod, DateTime toPeriod) + { + FromMonth = fromPeriod.Month; + ToMonth = toPeriod.Month; + FromYear = fromPeriod.Year; + ToYear = toPeriod.Year; + } + + public MonthRange(int year, int month) + { + if (year < 1900 || month > 12) + { + throw new Exception("First Argument is Year, not month"); + } + FromMonth = month; + ToMonth = month; + FromYear = year; + ToYear = year; + } + + public MonthRange(int fromYear, int fromMonth, int toYear, int toMonth) + { + if (fromYear < 1900 || fromMonth > 12 || toYear < 1900 || toMonth > 12) + { + throw new Exception("Year arguments are first, they are likely swapped."); + } + FromMonth = fromMonth; + ToMonth = toMonth; + FromYear = fromYear; + ToYear = toYear; + } + + public bool IsSingleMonth => FromMonth == ToMonth && FromYear == ToYear; + + public DateTime StartTime => new DateTime(FromYear, FromMonth, 1); + public DateTime EndTime => new DateTime(ToYear, ToMonth, 1).AddMonths(1).AddSeconds(-1); + public bool ValidFromRange => FromMonth != 0 && FromYear != 0; + public bool ValidToRange => ToMonth != 0 && ToYear != 0; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/PageFilter.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/PageFilter.cs new file mode 100644 index 0000000..6795332 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/PageFilter.cs @@ -0,0 +1,32 @@ +using System; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace LCU.Library.Internal.IncentiveApiModels.Common +{ + public class PageFilter : ICloneable + { + [Range(1, 1000)] + public int Page { get; set; } + + [Range(1, 1000)] + public int Limit { get; set; } + + public string? SortBy { get; set; } + + public bool? SortDescending { get; set; } + + public PageFilter() + { + this.Page = 1; + this.Limit = 100; + this.SortDescending = false; + } + + public virtual object Clone() + { + var jsonString = JsonConvert.SerializeObject(this); + return JsonConvert.DeserializeObject(jsonString, this.GetType()); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/PagedResponse.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/PagedResponse.cs new file mode 100644 index 0000000..5904180 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/PagedResponse.cs @@ -0,0 +1,41 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; + +namespace LCU.Library.Internal.IncentiveApiModels.Common +{ + public class PagedResponse where T : class + { + public PagedResponse() { } + + public PagedResponse(PagedResult pagedResult, PageFilter filter, IUrlHelper url, HttpRequest request, string uriAction = "Get") + { + TotalItems = pagedResult.Total; + Items = pagedResult.Items; + CurrentPage = filter.Page; + TotalPages = pagedResult.Total == 0 ? 1 : (int) Math.Ceiling((decimal) pagedResult.Total / filter.Limit); + + PageFilter newFilter = filter.Clone() as PageFilter; + if (pagedResult.Total > (filter.Page * filter.Limit)) + { + newFilter.Page = filter.Page + 1; + NextPage = new Uri(url.Action(uriAction, null, newFilter, request.Scheme)); + } + + if (filter.Page > 1) + { + newFilter.Page = filter.Page - 1; + PreviousPage = new Uri(url.Action(uriAction, null, newFilter, request.Scheme)); + } + + } + + public IEnumerable Items { get; set; } + public Uri NextPage { get; set; } + public Uri PreviousPage { get; set; } + public int TotalItems { get; set; } + public int CurrentPage { get; set; } + public int TotalPages { get; set; } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/PagedResult.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/PagedResult.cs new file mode 100644 index 0000000..9b321eb --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/PagedResult.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace LCU.Library.Internal.IncentiveApiModels.Common +{ + public class PagedResult where T : class + { + public IEnumerable Items { get; set; } + public int Total { get; set; } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/PropertyValueVariance.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/PropertyValueVariance.cs new file mode 100644 index 0000000..dd8fb2e --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/PropertyValueVariance.cs @@ -0,0 +1,9 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Common +{ + public class PropertyValueVariance + { + public string ComparedProperty { get; set; } + public string PropertyValueOne { get; set; } + public string PropertyValueTwo { get; set; } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/RoleConverter.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/RoleConverter.cs new file mode 100644 index 0000000..8403a1d --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/RoleConverter.cs @@ -0,0 +1,32 @@ +using LCU.Library.Internal.IncentiveApiModels.Entities; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Linq; + +namespace LCU.Library.Internal.IncentiveApiModels.Common +{ + public class RoleConverter : JsonConverter + { + public override bool CanWrite => false; + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + + public override bool CanRead => base.CanRead; + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var roleName = (string)JObject.Load(reader)["name"]; + var role = Role.GetRolesList().FirstOrDefault(role => role.Name.Equals(roleName)); + return role; + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Role); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/UpdateAllowed.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/UpdateAllowed.cs new file mode 100644 index 0000000..d31efd3 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Common/UpdateAllowed.cs @@ -0,0 +1,9 @@ +using System; + +namespace LCU.Library.Internal.IncentiveApiModels.Common +{ + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] + public sealed class UpdateAllowed : Attribute + { + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/AccountingSummary.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/AccountingSummary.cs new file mode 100644 index 0000000..8f4c2d1 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/AccountingSummary.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class AccountingSummary + { + public IReadOnlyCollection DepartmentPayouts { get; set; } + public decimal Total { get; set; } + public DateTime PayoutPeriod { get; } + + public AccountingSummary(DateTime payoutPeriod) + { + PayoutPeriod = payoutPeriod; + DepartmentPayouts = new List(); + Total = 0; + } + + public void Apply(List allNewPayouts, Dictionary deptNames) + { + var departmentSet = new Dictionary(); + foreach (var payoutLineItem in allNewPayouts) + { + if (!departmentSet.ContainsKey(payoutLineItem.DepartmentId)) + { + departmentSet.Add(payoutLineItem.DepartmentId, + new DepartmentLineItem(payoutLineItem.DepartmentId, deptNames[payoutLineItem.DepartmentId], + PayoutPeriod)); + } + + departmentSet[payoutLineItem.DepartmentId].Apply(payoutLineItem); + Total += payoutLineItem.PayoutTotal; + } + + foreach (var accountingSummaryLineItem in departmentSet.Values) + { + accountingSummaryLineItem.CalculatePercentage(Total); + } + + DepartmentPayouts = departmentSet.Values; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/Activity.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/Activity.cs new file mode 100644 index 0000000..3ba5374 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/Activity.cs @@ -0,0 +1,218 @@ +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Common; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class Activity : Auditable, IViewTransformable + { + public int Id { get; set; } + public Guid AppUserId { get; set; } + + [Required] + [JsonConverter(typeof(StringEnumConverter))] + public Product Product { get; set; } + + [Required] + [JsonConverter(typeof(StringEnumConverter))] + public ActivityType Type { get; set; } = ActivityType.Sale; + + [Required] + [JsonConverter(typeof(StringEnumConverter))] + public SystemType SourceSystem { get; set; } + + public string? SourceId { get; set; } + public DateTime SourceCreated { get; set; } + + [Required] + public ActivityDetail Detail { get; set; } + + public List MetaData { get; set; } + + [Required] + public int DepartmentId { get; set; } + + [Required] + public string EmployeeId { get; set; } + + public string? EmployeeEmail { get; set; } + + public int MatchingActivityId { get; set; } + + public int MatchingDpAccessoryActivityId { get; set; } + + public DateTime Accepted { get; set; } + + [JsonConverter(typeof(StringEnumConverter))] + public ActivityState State { get; set; } = ActivityState.Created; + + public virtual IList ActivityPayouts { get; set; } + + public int MatchingGapInsuranceActivityId { get; set; } + + public static Activity CopyActivityForTransfer( + Activity activity, + ActivityTransferAction activityTransferAction, + string employeeEmail + ) + { + return new Activity + { + Accepted = activity.Accepted, + DepartmentId = activityTransferAction.NewActivityDepartmentId, + Detail = new ActivityDetail + { + AccountBalance = activity.Detail.AccountBalance, + AccountNewBalance = activity.Detail.AccountNewBalance, + AccountNumber = activity.Detail.AccountNumber, + AccountStatus = activity.Detail.AccountStatus, + AgreementNumber = activity.Detail.AgreementNumber, + BranchOrgName = activity.Detail.BranchOrgName, + BranchOrgNbr = activity.Detail.BranchOrgNbr, + DnaPersonNbr = activity.Detail.DnaPersonNbr, + DebtProtectionAddDate = activity.Detail.DebtProtectionAddDate, + DebtProtectionRemoveDate = activity.Detail.DebtProtectionRemoveDate, + GapInsuranceAddDate = activity.Detail.GapInsuranceAddDate, + GapInsuranceRemoveDate = activity.Detail.GapInsuranceRemoveDate, + IsDebtProtectionEligible = activity.Detail.IsDebtProtectionEligible, + IsGapInsuranceEligible = activity.Detail.IsGapInsuranceEligible, + MemberName = activity.Detail.MemberName, + StationName = activity.Detail.StationName, + SubAccountNumber = activity.Detail.SubAccountNumber, + SubjectOrgNbr = activity.Detail.SubjectOrgNbr, + SubjectPersonNbr = activity.Detail.SubjectPersonNbr, + }, + EmployeeEmail = employeeEmail, + EmployeeId = activityTransferAction.NewActivityEmployeeId, + MatchingActivityId = activity.MatchingActivityId, + MatchingDpAccessoryActivityId = activity.MatchingDpAccessoryActivityId, + MatchingGapInsuranceActivityId = activity.MatchingGapInsuranceActivityId, + MetaData = activity.MetaData, + Product = activity.Product, + SourceCreated = activity.SourceCreated, + SourceId = activity.SourceId, + SourceSystem = activity.SourceSystem, + State = activityTransferAction.ActivityStateBeforeTransfer, + Type = activity.Type + }; + } + + public static Activity CopyActivityForDpAccessory(Activity activity) + { + return new Activity + { + Accepted = activity.Detail.DebtProtectionAddDate ?? activity.Accepted, + DepartmentId = activity.DepartmentId, + Detail = new ActivityDetail + { + AccountBalance = activity.Detail.AccountBalance, + AccountNewBalance = activity.Detail.AccountNewBalance, + AccountNumber = activity.Detail.AccountNumber, + AccountStatus = activity.Detail.AccountStatus, + AgreementNumber = activity.Detail.AgreementNumber, + BranchOrgName = activity.Detail.BranchOrgName, + BranchOrgNbr = activity.Detail.BranchOrgNbr, + DnaPersonNbr = activity.Detail.DnaPersonNbr, + DebtProtectionAddDate = activity.Detail.DebtProtectionAddDate, + DebtProtectionRemoveDate = activity.Detail.DebtProtectionRemoveDate, + GapInsuranceAddDate = activity.Detail.GapInsuranceAddDate, + GapInsuranceRemoveDate = activity.Detail.GapInsuranceRemoveDate, + IsDebtProtectionEligible = activity.Detail.IsDebtProtectionEligible, + IsGapInsuranceEligible = activity.Detail.IsGapInsuranceEligible, + MemberName = activity.Detail.MemberName, + StationName = activity.Detail.StationName, + SubAccountNumber = activity.Detail.SubAccountNumber, + SubjectOrgNbr = activity.Detail.SubjectOrgNbr, + SubjectPersonNbr = activity.Detail.SubjectPersonNbr + }, + EmployeeEmail = activity.EmployeeEmail, + EmployeeId = activity.EmployeeId, + MatchingActivityId = activity.MatchingActivityId, + MatchingDpAccessoryActivityId = activity.Id, + MatchingGapInsuranceActivityId = activity.MatchingGapInsuranceActivityId, + MetaData = activity.MetaData, + Product = Product.DebtProtection, + SourceCreated = activity.Detail.DebtProtectionAddDate ?? activity.SourceCreated, + SourceId = activity.SourceId, + SourceSystem = activity.SourceSystem, + State = ActivityState.Created, + Type = activity.Type + }; + } + + public bool CanAutoDecision() + { + return State != ActivityState.Rejected; + } + + public double CalculatedValueTowardsGoal(Product countsForProduct) + { + if (Type == ActivityType.Other + || State != ActivityState.Accepted && State != ActivityState.PendingTransfer) + { + return 0; + } + + var details = ProductDetail.DetailsForProduct(Product); + if (countsForProduct == Product.DebtProtectionEligibleLoan && Detail != null) + { + return Detail.IsDebtProtectionEligible && Detail.DebtProtectionAddDate != null && Detail.DebtProtectionRemoveDate == null ? 1 : 0; + } + + if (countsForProduct == Product && details.ValueType == ProductValue.Numeric) + { + return 1; + } + + if (countsForProduct == Product && Detail is { AccountBalance: { } }) + { + return Detail.AccountBalance.Value; + } + + return 0; + } + + public int CalculatedValueTowardsOther(Product countsForProduct) + { + if (Detail != null && countsForProduct == Product.DebtProtectionEligibleLoan && Detail.IsDebtProtectionEligible) + { + return 1; + } + + return 0; + } + + public int CalculatedValueTowardsGapInsurance() => + Detail?.IsGapInsuranceEligible ?? false ? 1 : 0; + + public bool HasDpAccessory() + { + return Type == ActivityType.Sale + && Detail.IsDebtProtectionEligible + && ( + MatchingDpAccessoryActivityId != 0 + || Detail.DebtProtectionAddDate?.Date != null + || Detail.DebtProtectionRemoveDate?.Date != null + ); + } + + public bool HasActiveGapAccessory() + { + return Type == ActivityType.Sale + && MatchingGapInsuranceActivityId != 0 + && Detail.IsGapInsuranceEligible + && Detail.GapInsuranceAddDate != null + && Detail.GapInsuranceRemoveDate?.Date == null; + } + + public Activity TransformForView() + { + Detail?.TransformForView(); + return this; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ActivityDetail.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ActivityDetail.cs new file mode 100644 index 0000000..ca5834a --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ActivityDetail.cs @@ -0,0 +1,59 @@ +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Common; +using Newtonsoft.Json; +using System; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class ActivityDetail : IViewTransformable + { + public int Id { get; set; } + public string? DnaPersonNbr { get; set; } + public string? AccountNumber { get; set; } + [UpdateAllowed] + public string? SubAccountNumber { get; set; } + [UpdateAllowed] + public string? AccountStatus { get; set; } + public string? AgreementNumber { get; set; } + public string? SubjectPersonNbr { get; set; } + public string? SubjectOrgNbr { get; set; } + public string? MemberName { get; set; } + [UpdateAllowed] + public string? StationName { get; set; } + public string? BranchOrgNbr { get; set; } + public string? BranchOrgName { get; set; } + public double? AccountBalance { get; set; } + public double? AccountNewBalance { get; set; } + public int ActivityRef { get; set; } + [JsonIgnore] + public Activity Activity { get; set; } + [UpdateAllowed] + public DateTime? DebtProtectionAddDate { get; set; } + [UpdateAllowed] + public DateTime? DebtProtectionRemoveDate { get; set; } + public bool IsDebtProtectionEligible { get; set; } + public bool IsGapInsuranceEligible { get; set; } + public DateTime? GapInsuranceAddDate { get; set; } + public DateTime? GapInsuranceRemoveDate { get; set; } + + public ActivityDetail TransformForView() + { + if ((Activity?.Product == Product.CreditCard || Activity?.Product == Product.CreditCardBalanceTransfer) + && Activity?.Type == ActivityType.Referral) + { + AccountNumber = GetAccountNumber(); + } + + return this; + } + + private string GetAccountNumber() + { + if (string.IsNullOrEmpty(AccountNumber) || AccountNumber.Length <= 4) return AccountNumber; + + var lastDigits = AccountNumber.Substring(AccountNumber.Length - 4, 4); + var maskDigits = new string('X', AccountNumber.Length - 4); + return string.Concat(maskDigits, lastDigits); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ActivityDetailUpdate.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ActivityDetailUpdate.cs new file mode 100644 index 0000000..52613bf --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ActivityDetailUpdate.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class ActivityDetailUpdate : AuditCreate + { + public int Id { get; set; } + [Required] + public int ActivityDetailId { get; set; } + public string Note { get; set; } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ActivityMetaData.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ActivityMetaData.cs new file mode 100644 index 0000000..ee83b38 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ActivityMetaData.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class ActivityMetaData + { + public int Id { get; set; } + public string MetaData { get; set; } + [Required] + [JsonConverter(typeof(StringEnumConverter))] + public MetaDataType MetaDataType { get; set; } + public int ActivityRef { get; set; } + [JsonIgnore] + public Activity Activity { get; set; } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ActivityPayoutLineItem.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ActivityPayoutLineItem.cs new file mode 100644 index 0000000..9671155 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ActivityPayoutLineItem.cs @@ -0,0 +1,10 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class ActivityPayoutLineItem + { + public int ActivityId { get; set; } + public int PayoutLineItemId { get; set; } + public virtual Activity Activity { get; set; } + public virtual PayoutLineItem PayoutLineItem { get; set; } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ActivityStateChange.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ActivityStateChange.cs new file mode 100644 index 0000000..80df0c8 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ActivityStateChange.cs @@ -0,0 +1,23 @@ +using System; +using System.ComponentModel.DataAnnotations; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Common; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class ActivityStateChange : AuditCreate + { + public int Id { get; set; } + [Required] + public int ActivityId { get; set; } + [Required] + [JsonConverter(typeof(StringEnumConverter))] + public ActivityState State { get; set; } + public DateTime? AcceptedDate { get; set; } + public int ActivityMatchId { get; set; } + public string? Note { get; set; } + + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ActivityStateDetail.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ActivityStateDetail.cs new file mode 100644 index 0000000..465c245 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ActivityStateDetail.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class ActivityStateDetail + { + private ActivityStateDetail(ActivityState state) + { + State = state; + } + + public ActivityState State { get; } + + public static readonly ActivityStateDetail Created = new ActivityStateDetail(ActivityState.Created); + public static readonly ActivityStateDetail Accepted = new ActivityStateDetail(ActivityState.Accepted); + public static readonly ActivityStateDetail Rejected = new ActivityStateDetail(ActivityState.Rejected); + public static readonly ActivityStateDetail Transferred = new ActivityStateDetail(ActivityState.Transferred); + public static readonly ActivityStateDetail ManualReview = new ActivityStateDetail(ActivityState.ManualReview); + public static readonly ActivityStateDetail PendingReferral = new ActivityStateDetail(ActivityState.PendingReferral); + public static readonly ActivityStateDetail PendingTransfer = new ActivityStateDetail(ActivityState.PendingTransfer); + + public static readonly List ActivityStateDetails = new List() + { + Created, + Accepted, + Rejected, + Transferred, + ManualReview, + PendingReferral, + PendingTransfer + }; + + public static readonly List ActivityStatesEligibleForTransfer = new List() + { + Accepted, + ManualReview, + PendingReferral + }; + + public static ActivityStateDetail DetailsForActivityState(ActivityState state) + { + return ActivityStateDetails.Find(x => x.State == state); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ActivityTransferAction.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ActivityTransferAction.cs new file mode 100644 index 0000000..27d382a --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ActivityTransferAction.cs @@ -0,0 +1,44 @@ +using LCU.Library.Internal.IncentiveApiModels.Common; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System; +using System.Linq; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class ActivityTransferAction : Auditable, IViewTransformable + { + public int Id { get; set; } + + [JsonConverter(typeof(StringEnumConverter))] + public ActivityState ActivityStateBeforeTransfer { get; set; } + public int RequestedActivityId { get; set; } + + [JsonConverter(typeof(StringEnumConverter))] + public ActivityTransferState State { get; set; } + public int? NewActivityId { get; set; } + public int NewActivityDepartmentId { get; set; } + public string NewActivityEmployeeId { get; set; } + public Guid NewAppUserId { get; set; } + public string? RequestedDepartmentApprover { get; set; } + public DateTime? RequestedDepartmentApproved { get; set; } + public string? NewDepartmentApprover { get; set; } + public DateTime? NewDepartmentApproved { get; set; } + public Activity RequestedActivity { get; set; } + public Activity NewActivity { get; set; } + + public static bool IsEligibleForTransfer(ActivityState activityState) + { + return ActivityStateDetail.ActivityStatesEligibleForTransfer + .Select(x => x.State).Contains(activityState); + } + + public ActivityTransferAction TransformForView() + { + RequestedActivity?.TransformForView(); + NewActivity?.TransformForView(); + return this; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/AppUser.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/AppUser.cs new file mode 100644 index 0000000..cbe90df --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/AppUser.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class AppUser : Auditable + { + public AppUser() + { + Departments = new List(); + Assignments = new List(); + Terminated = null; + } + + public Guid Id { get; set; } + public string AdEmail { get; set; } + public int PersonId { get; set; } + public string EmployeeId { get; set; } + public string HomeDepartment { get; set; } + public string BaseRole { get; set; } + public string? ManagerKey { get; set; } + [Obsolete("Start referring to UserDepartment.Role when possible. 09/08/2021")] + public Role Role { get; set; } + public List AdditionalPermissions { get; set; } + public string FullName { get; set; } + public List Departments { get; set; } + public List Assignments { get; set; } + public DateTime? Terminated { get; set; } + + public bool UserHasPermission(Permission perm) + { + var departments = ActiveDepartmentsForRange(new MonthRange()); + foreach (var userDepartment in departments) + { + if (userDepartment.Role.Permissions.Any(x => x.Code == perm.Code)) + { + return true; + } + } + return (AdditionalPermissions ?? new List()).Any(x => x.Permission.Code == perm.Code); + } + + public bool UserHasPermission(string permissionCode) + { + var departments = ActiveDepartmentsForRange(new MonthRange()); + foreach (var userDepartment in departments) + { + if (userDepartment.Role.Permissions.Any(x => x.Code == permissionCode)) + { + return true; + } + } + return (AdditionalPermissions ?? new List()).Any(x => x.Permission.Code == permissionCode); + } + + public IEnumerable ActiveDepartmentsForRange(MonthRange monthRange) + { + return Departments.Where(department => department.StartDate.Date <= monthRange.EndTime.Date && + (department.EndDate == null || + monthRange.StartTime.Date <= department.EndDate.Value.Date)); + } + + public UserDepartment FindDepartmentForRole(Role role) + { + var departments = ActiveDepartmentsForRange(new MonthRange()); + return departments.FirstOrDefault(x => x.Role == role); + } + + public UserDepartment FindHomeDepartment() + { + return Departments.FirstOrDefault(x => x.IsHome == true + && x.StartDate.Date <= DateTime.Today.Date && + (x.EndDate == null || DateTime.Today.Date <= x.EndDate.Value.Date)); + } + + public bool HasDepartment(int departmentId) + { + return ActiveDepartmentsForRange(new MonthRange()).Any(x => x.DepartmentId == departmentId); + } + + public bool HasRoleAnywhere(Role role) + { + var departments = ActiveDepartmentsForRange(new MonthRange()); + return departments.Any(userDepartment => userDepartment.Role == role); + } + + public bool HasAnyRoleForActiveDepartment(int departmentId, Role[] roles) + { + var departments = ActiveDepartmentsForRange(new MonthRange()); + return departments.Any(userDepartment => roles.Contains(userDepartment.Role) && userDepartment.DepartmentId == departmentId); + } + + public string AppUserFullNameInFromContextFormat() + { + if (!string.IsNullOrWhiteSpace(FullName)) + { + var nameParts = FullName.Split(","); + return $"{nameParts[1]} {nameParts[0]}"; + } + return null; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ApplicationUser.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ApplicationUser.cs new file mode 100644 index 0000000..2e0f836 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ApplicationUser.cs @@ -0,0 +1,23 @@ +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class ApplicationUser + { + public string FirstName { get; set; } + public string LastName { get; set; } + public string Email { get; set; } + public string[] Roles { get; set; } + public bool IsSystem { get; set; } + + public string UserName() + { + return string.IsNullOrWhiteSpace(LastName) ? Email : $"{FirstName} {LastName}"; + } + + public UserType UserType() + { + return IsSystem ? Library.Internal.IncentiveApiModels.Enums.UserType.System : Library.Internal.IncentiveApiModels.Enums.UserType.Associate; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/BusinessLine.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/BusinessLine.cs new file mode 100644 index 0000000..9185dc0 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/BusinessLine.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.Linq; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class BusinessLine + { + public string Name { get; } + public string Code { get; } + public string OrgCode { get; } + + private const string HeadQuarters = "headquarters"; + private const string BranchName = "branch"; + private const string OperationsName = "operations"; + + private BusinessLine(string name, string code, string orgcode) + { + Name = name; + Code = code; + OrgCode = orgcode; + } + + public static BusinessLine BranchOperations = new BusinessLine("Branches", "BCHOPS", Organization.MemberExperience.OrgCode); + public static BusinessLine MemberServices = new BusinessLine("Member Services", "MEMSRV", Organization.MemberExperience.OrgCode); + public static BusinessLine Operations = new BusinessLine("Operations", "OPS", Organization.MemberExperience.OrgCode); + + public static List BusinessLines = new List + { + BranchOperations, + MemberServices, + Operations + }; + + public static BusinessLine FindByCode(string code) => BusinessLines.FirstOrDefault(bl => bl.Code == code); + + public static BusinessLine TryLookup(string employeeOrgUnit) + { + if (string.IsNullOrWhiteSpace(employeeOrgUnit)) + { + return MemberServices; + } + + if (employeeOrgUnit.ToLower().Contains(BranchName)) + { + return BranchOperations; + } + + if (employeeOrgUnit.ToLower().Contains(OperationsName)) + { + return Operations; + } + + return MemberServices; + } + } + +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/Commission.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/Commission.cs new file mode 100644 index 0000000..8b2b220 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/Commission.cs @@ -0,0 +1,28 @@ +using LCU.Library.Internal.IncentiveApiModels.Common; +using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class Commission : Auditable + { + [Required] + public int Id { get; set; } + + [Required] + [JsonConverter(typeof(RoleConverter))] + public Role Role { get; set; } + + [Required] + public int RatePerProduct { get; set; } + + [Required] + public Product Product { get; set; } + + public bool HasSameRoleAndProduct(Commission commission) + { + return Role == commission.Role && Product == commission.Product; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/DeadActivity.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/DeadActivity.cs new file mode 100644 index 0000000..88ccb22 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/DeadActivity.cs @@ -0,0 +1,95 @@ +using System; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Common; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class DeadActivity : Auditable, IViewTransformable + { + public int Id { get; set; } + public Guid AppUserId { get; set; } + [JsonConverter(typeof(StringEnumConverter))] + public DeadActivityReason Reason { get; set; } + public string ReasonNote { get; set; } + [JsonConverter(typeof(StringEnumConverter))] + public Product? Product { get; set; } + [JsonConverter(typeof(StringEnumConverter))] + public ActivityType? Type { get; set; } + [JsonConverter(typeof(StringEnumConverter))] + public SystemType? SourceSystem { get; set; } + public string? SourceId { get; set; } + public DateTime? SourceCreated { get; set; } + public DeadActivityDetail DeadDetail { get; set; } + public int? DepartmentId { get; set; } + public string? EmployeeId { get; set; } + public string? EmployeeEmail { get; set; } + public DateTime? Accepted { get; set; } + [JsonConverter(typeof(StringEnumConverter))] + public ActivityState? State { get; set; } + public int? LivingActivityId { get; set; } + + public DeadActivity(Activity activity, DeadActivityReason reason, string note) + { + Accepted = activity.Accepted; + AppUserId = activity.AppUserId; + DeadDetail = new DeadActivityDetail + { + AccountBalance = activity.Detail.AccountBalance, + AccountNewBalance = activity.Detail.AccountNewBalance, + AccountNumber = activity.Detail.AccountNumber, + AccountStatus = activity.Detail.AccountStatus, + AgreementNumber = activity.Detail.AgreementNumber, + BranchOrgName = activity.Detail.BranchOrgName, + BranchOrgNbr = activity.Detail.BranchOrgNbr, + DnaPersonNbr = activity.Detail.DnaPersonNbr, + DebtProtectionAddDate = activity.Detail.DebtProtectionAddDate, + DebtProtectionRemoveDate = activity.Detail.DebtProtectionRemoveDate, + GapInsuranceAddDate = activity.Detail.GapInsuranceAddDate, + GapInsuranceRemoveDate = activity.Detail.GapInsuranceRemoveDate, + IsDebtProtectionEligible = activity.Detail.IsDebtProtectionEligible, + IsGapInsuranceEligible = activity.Detail.IsGapInsuranceEligible, + MemberName = activity.Detail.MemberName, + StationName = activity.Detail.StationName, + SubAccountNumber = activity.Detail.SubAccountNumber, + SubjectOrgNbr = activity.Detail.SubjectOrgNbr, + SubjectPersonNbr = activity.Detail.SubjectPersonNbr + }; + DepartmentId = activity.DepartmentId < 0 ? null : activity.DepartmentId; + EmployeeId = activity.EmployeeId; + EmployeeEmail = activity.EmployeeEmail; + Product = activity.Product; + Reason = reason; + ReasonNote = note; + SourceCreated = activity.SourceCreated; + SourceId = activity.SourceId; + SourceSystem = activity.SourceSystem; + State = activity.State; + Type = activity.Type; + } + + public DeadActivity(string sourceId, SystemType systemType, DeadActivityReason reason, string note) + { + Reason = reason; + ReasonNote = note; + SourceId = sourceId; + SourceSystem = systemType; + DeadDetail = new DeadActivityDetail() + { + DeadActivity = this + }; + } + + private DeadActivity() + { + + } + + public DeadActivity TransformForView() + { + DeadDetail?.TransformForView(); + return this; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/DeadActivityDetail.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/DeadActivityDetail.cs new file mode 100644 index 0000000..414f729 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/DeadActivityDetail.cs @@ -0,0 +1,59 @@ +using System; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Common; +using Newtonsoft.Json; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class DeadActivityDetail : IViewTransformable + { + public int Id { get; set; } + public string? DnaPersonNbr { get; set; } + public string? AccountNumber { get; set; } + public string? SubAccountNumber { get; set; } + public string? AccountStatus { get; set; } + public string? AgreementNumber { get; set; } + public string? SubjectPersonNbr { get; set; } + public string? SubjectOrgNbr { get; set; } + public string? MemberName { get; set; } + public string? StationName { get; set; } + public string? BranchOrgNbr { get; set; } + public string? BranchOrgName { get; set; } + public double? AccountBalance { get; set; } + public double? AccountNewBalance { get; set; } + public int DeadActivityRef { get; set; } + [JsonIgnore] + public DeadActivity DeadActivity { get; set; } + public DateTime? DebtProtectionAddDate { get; set; } + public DateTime? DebtProtectionRemoveDate { get; set; } + public bool? IsDebtProtectionEligible { get; set; } + public bool IsGapInsuranceEligible { get; set; } + public DateTime? GapInsuranceAddDate { get; set; } + public DateTime? GapInsuranceRemoveDate { get; set; } + + public DeadActivityDetail TransformForView() + { + if (DeadActivity == null || DeadActivity.Product == null || DeadActivity.Type == null) + { + return this; + } + + if ((DeadActivity?.Product == Product.CreditCard || DeadActivity?.Product == Product.CreditCardBalanceTransfer) + && DeadActivity?.Type == ActivityType.Referral) + { + AccountNumber = GetAccountNumber(); + } + + return this; + } + + private string GetAccountNumber() + { + if (string.IsNullOrEmpty(AccountNumber) || AccountNumber.Length <= 4) return AccountNumber; + + var lastDigits = AccountNumber.Substring(AccountNumber.Length - 4, 4); + var maskDigits = new string('X', AccountNumber.Length - 4); + return string.Concat(maskDigits, lastDigits); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/Department.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/Department.cs new file mode 100644 index 0000000..d04020e --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/Department.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Reflection; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Common; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class Department : Auditable, IValidatableObject + { + public int? Id { get; set; } + + [Required] + [StringLength(255, MinimumLength = 3)] + public string DepartmentName { get; set; } + + [Required] + [StringLength(255, MinimumLength = 3)] + public string DayforceDepartmentName { get; set; } + + public string? DnaOrgNbr { get; set; } + + [Required] + public string? DayForceGlCode { get; set; } + + [Required] + public string? BusinessLineCode { get; set; } + + public string? RegionCode { get; set; } + + [Required] + [JsonConverter(typeof(StringEnumConverter))] + public DepartmentState State { get; set; } + + public List Assignments { get; set; } + + public IEnumerable Validate(ValidationContext validationContext) + { + var results = new List(); + + if (!typeof(BusinessLine).GetFields(BindingFlags.Static | BindingFlags.Public) + .Where(x => x.FieldType == typeof(BusinessLine)) + .Select(x => (x.GetValue(x.Name) as BusinessLine)?.Code).Contains(BusinessLineCode)) + { + results.Add(new ValidationResult("The supplied BusinessLineCode is not associated with any known BusinessLine", new[] { "BusinessLine" })); + } + + if (RegionCode != null && Region.FindByCode(RegionCode) == null) + { + results.Add(new ValidationResult("The supplied RegionCode is not associated with any known Region", new[] { "Region" })); + } + + return results; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/DepartmentLineItem.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/DepartmentLineItem.cs new file mode 100644 index 0000000..bff384b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/DepartmentLineItem.cs @@ -0,0 +1,37 @@ +using System; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class DepartmentLineItem : Auditable + { + public DepartmentLineItem() + { + PayoutPercentage = 0; + PayoutTotal = 0; + } + + public DepartmentLineItem(int departmentId, string departmentName, DateTime payoutMonth) : this() + { + DepartmentId = departmentId; + DepartmentName = departmentName; + PayoutMonth = payoutMonth; + } + + public int Id { get; set; } + public int DepartmentId { get; set; } + public string DepartmentName { get; set; } + public decimal PayoutTotal { get; set; } + public decimal PayoutPercentage { get; private set; } + public DateTime PayoutMonth { get; set; } + public void Apply(PayoutLineItem payoutLineItem) + { + PayoutTotal += payoutLineItem.PayoutTotal; + } + + public void CalculatePercentage(decimal total) + { + PayoutPercentage = total == 0 ? 0 : Math.Round(PayoutTotal / total * 100, 2); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/EStatementDeliveryMethod.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/EStatementDeliveryMethod.cs new file mode 100644 index 0000000..0135a72 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/EStatementDeliveryMethod.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class EStatementDeliveryMethod + { + public long AccountNumber { get; set; } + public long ActivityNumber { get; set; } + public DateTime ActivityDateTime { get; set; } + public string MinorAccountType { get; set; } + public string PersonNumber { get; set; } + public string OrganizationNumber { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string PhoneNumbers { get; set; } + public string EmailAddresses { get; set; } + public List MetaData { get; set; } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/Goal.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/Goal.cs new file mode 100644 index 0000000..783563c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/Goal.cs @@ -0,0 +1,23 @@ +using System; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class Goal : Auditable + { + public int Id { get; set; } + public Guid AppUserId { get; set; } + public int DepartmentId { get; set; } + public string? EmployeeId { get; set; } + public DateTime EndDate { get; set; } + public Product Product { get; set; } + public DateTime StartDate { get; set; } + public int Value { get; set; } + + public bool IsDepartmentGoal() + { + return string.IsNullOrWhiteSpace(EmployeeId); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/GoalChange.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/GoalChange.cs new file mode 100644 index 0000000..55261e6 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/GoalChange.cs @@ -0,0 +1,25 @@ +using System; +using System.ComponentModel.DataAnnotations; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class GoalChange : AuditCreate + { + public int Id { get; set; } + [Required] + public Guid AppUserId { get; set; } + public int DepartmentId { get; set; } + public DateTime EndDate { set; get; } + public string? PersonId { get; set; } + public Product Product { get; set; } + public DateTime StartDate { get; set; } + public int Value { get; set; } + + public bool IsDepartmentGoalChange() + { + return string.IsNullOrWhiteSpace(PersonId); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/Organization.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/Organization.cs new file mode 100644 index 0000000..376850e --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/Organization.cs @@ -0,0 +1,16 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class Organization + { + public string Name { get; set; } + public string OrgCode { get; set; } + + private Organization(string name, string code) + { + Name = name; + OrgCode = code; + } + + public static Organization MemberExperience = new Organization("Member Experience", "MEMEXP"); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/PayoutLineItem.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/PayoutLineItem.cs new file mode 100644 index 0000000..d820261 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/PayoutLineItem.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using LCU.Library.Internal.IncentiveApiModels.Enums; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class PayoutLineItem : AuditCreate + { + public PayoutLineItem() + { + } + + public PayoutLineItem(Product product, PayoutProfile profile, int goalValue, decimal slidingPayout, IEnumerable activities) + { + Product = product; + PayoutProfileId = profile.Id; + GoalValue = goalValue; + SlidingPayout = slidingPayout; + ApplyActivity(activities); + } + + public PayoutLineItem(Product product, PayoutProfile profile, Goal goal, decimal slidingPayout, IEnumerable activities, DateTime runDate) + { + Product = product; + PayoutProfileId = profile.Id; + GoalId = goal.Id; + GoalValue = goal.Value; + SlidingPayout = slidingPayout; + ApplyActivity(activities); + PayoutTotal = profile.CalculateLineItemPayout(GoalValue, ActivityTotal, runDate, SlidingPayout); + } + + public PayoutLineItem(Product product, Commission commission, IEnumerable activities) + { + Product = product; + CommissionId = commission.Id; + CommissionValue = commission.RatePerProduct; + ApplyActivity(activities); + PayoutTotal = ActivityTotal * CommissionValue; + SlidingPayout = 100; + } + + public int Id { get; set; } + public Guid AppUserId { get; set; } + public string EmployeeId { get; set; } + public int DepartmentId { get; set; } + public decimal SlidingPayout { get; set; } + public Role Role { get; set; } + public Product Product { get; set; } + public DateTime ActivityPeriod { get; set; } + public int GoalId { get; set; } + public int GoalValue { get; set; } + public int CommissionId { get; set; } + public int CommissionValue { get; set; } + public int PayoutProfileId { get; set; } + public virtual IList ActivityPayouts { get; set; } + public decimal ActivityTotal { get; set; } + public decimal PayoutTotal { get; set; } + + private void ApplyActivity(IEnumerable activities) + { + ActivityPayouts = new List(); + foreach (var activity in activities) + { + var payoutActivity = new ActivityPayoutLineItem + { + Activity = activity, + PayoutLineItem = this + }; + + ActivityPayouts.Add(payoutActivity); + if (activity.Product == Product) + { + if (PayoutProfileId != 0) + { + ActivityTotal += Convert.ToDecimal(activity.CalculatedValueTowardsGoal(activity.Product)); + } + else + { + ActivityTotal++; + } + } + } + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/PayoutProfile.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/PayoutProfile.cs new file mode 100644 index 0000000..2bdb3e7 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/PayoutProfile.cs @@ -0,0 +1,363 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class PayoutProfile + { + public int Id { get; } + public Product Product { get; } + public Role[] Roles { get; } + public Dictionary Payouts { get; } + public PayoutType Type { get; } + public bool PayEach { get; } + public bool IsEnabled { get; } + private const bool PayForEach = true; + + private PayoutProfile( + int id, + Product product, + Role[] roles, + PayoutRange[] defaultPayout, + PayoutType type = PayoutType.Personal, + bool payEach = false, + bool isEnabled = true + ) + { + Id = id; + Product = product; + Roles = roles; + Payouts = new Dictionary + { + { DateTime.MinValue, defaultPayout } + }; + Type = type; + PayEach = payEach; + IsEnabled = isEnabled; + } + + private PayoutProfile( + int id, + Product product, + Role[] roles, + Dictionary payouts, + PayoutType type = PayoutType.Personal, + bool payEach = false, + bool isEnabled = true + ) + { + Id = id; + Product = product; + Roles = roles; + Payouts = payouts; + Type = type; + PayEach = payEach; + IsEnabled = isEnabled; + } + + public static readonly PayoutProfile BMMortgage = new PayoutProfile(1, + Product.Mortgage, new[] { Role.BM }, + new Dictionary + { + { + DateTime.MinValue, new[] + { + new PayoutRange(1.0, 1.24, 200), + new PayoutRange(1.25, 1.49, 250), + new PayoutRange(1.5, PayoutRange.NOLIMIT, 325) + } + }, + { + DateTime.ParseExact("2021-11-01", "yyyy-MM-dd", CultureInfo.InvariantCulture), new[] + { + new PayoutRange(1.0, 1.24, 250), + new PayoutRange(1.25, 1.49, 325), + new PayoutRange(1.5, PayoutRange.NOLIMIT, 425) + } + } + }, + PayoutType.Department + ); + + public static readonly PayoutProfile ABMMortgage = new PayoutProfile(2, + Product.Mortgage, new[] { Role.ABM, Role.ManagementTrainee }, + new Dictionary + { + { + DateTime.MinValue, new[] + { + new PayoutRange(1.0, 1.24, 125), + new PayoutRange(1.25, 1.49, 175), + new PayoutRange(1.5, PayoutRange.NOLIMIT, 250) + } + }, + { + DateTime.ParseExact("2021-11-01", "yyyy-MM-dd", CultureInfo.InvariantCulture), new[] + { + new PayoutRange(1.0, 1.24, 175), + new PayoutRange(1.25, 1.49, 250), + new PayoutRange(1.5, PayoutRange.NOLIMIT, 350) + } + } + }, + PayoutType.Department + ); + + public static readonly PayoutProfile PFOMSLOMortgage = new PayoutProfile(3, + Product.Mortgage, new[] { Role.PFO, Role.MSLO }, + new Dictionary + { + { + DateTime.MinValue, new[] + { + new PayoutRange(0.74, 0.99, 50), + new PayoutRange(1.0, 1.24, 100), + new PayoutRange(1.25, 1.49, 150), + new PayoutRange(1.5, PayoutRange.NOLIMIT, 200) + } + }, + { + DateTime.ParseExact("2021-11-01", "yyyy-MM-dd", CultureInfo.InvariantCulture), new[] + { + new PayoutRange(0.74, 0.99, 75), + new PayoutRange(1.0, 1.24, 150), + new PayoutRange(1.25, 1.49, 225), + new PayoutRange(1.5, PayoutRange.NOLIMIT, 300) + } + } + }, + PayoutType.Personal + ); + + public static readonly PayoutProfile MSCPersonalMortgage = new PayoutProfile(4, + Product.Mortgage, new[] { Role.MSCTeamLead }, + new Dictionary + { + { + DateTime.MinValue, new[] + { + new PayoutRange(0.74, 0.99, 50), + new PayoutRange(1.0, 1.24, 100), + new PayoutRange(1.25, 1.49, 150), + new PayoutRange(1.5, PayoutRange.NOLIMIT, 200) + } + }, + { + DateTime.ParseExact("2021-11-01", "yyyy-MM-dd", CultureInfo.InvariantCulture), new[] + { + new PayoutRange(0.74, 0.99, 75), + new PayoutRange(1.0, 1.24, 150), + new PayoutRange(1.25, 1.49, 225), + new PayoutRange(1.5, PayoutRange.NOLIMIT, 300) + } + } + }, + PayoutType.Personal + ); + + public static readonly PayoutProfile MSCMortgage = new PayoutProfile(5, + Product.Mortgage, new[] { Role.MSCTeamLead }, + new Dictionary + { + { + DateTime.MinValue, new[] + { + new PayoutRange(1.0, 1.24, 50), + new PayoutRange(1.25, 1.49, 75), + new PayoutRange(1.5, PayoutRange.NOLIMIT, 100) + } + }, + { + DateTime.ParseExact("2021-11-01", "yyyy-MM-dd", CultureInfo.InvariantCulture), new[] + { + new PayoutRange(1.0, 1.24, 100), + new PayoutRange(1.25, 1.49, 150), + new PayoutRange(1.5, PayoutRange.NOLIMIT, 200) + } + } + }, + PayoutType.Department + ); + + public static readonly PayoutProfile BMVehicle = new PayoutProfile(6, + Product.Vehicle, new[] { Role.BM }, + new[] + { + new PayoutRange(1.0, 1.24, 125), + new PayoutRange(1.25, 1.49, 175), + new PayoutRange(1.5, PayoutRange.NOLIMIT, 225) + }, + PayoutType.Department); + + public static readonly PayoutProfile ABMVehicle = new PayoutProfile(7, + Product.Vehicle, new[] { Role.ABM, Role.ManagementTrainee }, + new[] + { + new PayoutRange(1.0, 1.24, 50), + new PayoutRange(1.25, 1.49, 100), + new PayoutRange(1.5, PayoutRange.NOLIMIT, 125) + }, + PayoutType.Department); + + public static readonly PayoutProfile PFOMSLOVehicle = new PayoutProfile(8, + Product.Vehicle, new[] { Role.PFO, Role.MSLO }, + new[] + { + new PayoutRange(0.74, 0.99, 25), + new PayoutRange(1.0, 1.24, 50), + new PayoutRange(1.25, 1.49, 75), + new PayoutRange(1.5, PayoutRange.NOLIMIT, 100) + }, + PayoutType.Personal); + + public static readonly PayoutProfile MSCPersonalVehicle = new PayoutProfile(9, + Product.Vehicle, new[] { Role.MSCTeamLead }, + new[] + { + new PayoutRange(0.74, 0.99, 25), + new PayoutRange(1.0, 1.24, 50), + new PayoutRange(1.25, 1.49, 75), + new PayoutRange(1.5, PayoutRange.NOLIMIT, 100) + }, + PayoutType.Personal); + + public static readonly PayoutProfile BMNewChecking = new PayoutProfile(10, + Product.NewChecking, new[] { Role.BM }, + new[] + { + new PayoutRange(1.0, 1.24, 200), + new PayoutRange(1.25, 1.49, 250), + new PayoutRange(1.5, PayoutRange.NOLIMIT, 325) + }, + PayoutType.Department); + + public static readonly PayoutProfile ABMBOMNewChecking = new PayoutProfile(11, + Product.NewChecking, new[] { Role.ABM, Role.BOM, Role.ManagementTrainee }, + new[] + { + new PayoutRange(1.0, 1.24, 125), + new PayoutRange(1.25, 1.49, 175), + new PayoutRange(1.5, PayoutRange.NOLIMIT, 250) + }, + PayoutType.Department); + + public static readonly PayoutProfile PFOMSLONewChecking = new PayoutProfile(12, + Product.NewChecking, new[] { Role.PFO, Role.UniversalSpecialistPFO }, + new[] + { + new PayoutRange(0.74, 0.99, 50), + new PayoutRange(1.0, 1.24, 100), + new PayoutRange(1.25, 1.49, 150), + new PayoutRange(1.5, PayoutRange.NOLIMIT, 200) + }, + PayoutType.Personal); + + public static readonly PayoutProfile MSCNewChecking = new PayoutProfile(13, + Product.NewChecking, new[] { Role.MSCTeamLead }, + new[] + { + new PayoutRange(1.0, 1.24, 50), + new PayoutRange(1.25, 1.49, 75), + new PayoutRange(1.5, PayoutRange.NOLIMIT, 100) + }, + PayoutType.Department); + + public static readonly PayoutProfile MSCPersonalNewChecking = new PayoutProfile(14, + Product.NewChecking, new[] { Role.MSCTeamLead }, + new[] + { + new PayoutRange(0.74, 0.99, 50), + new PayoutRange(1.0, 1.24, 100), + new PayoutRange(1.25, 1.49, 150), + new PayoutRange(1.5, PayoutRange.NOLIMIT, 200) + }, + PayoutType.Personal); + + public static readonly PayoutProfile BMCreditCard = new PayoutProfile(15, + Product.CreditCard, new[] { Role.BM }, + new[] + { + new PayoutRange(1.0, 1.24, 125), + new PayoutRange(1.25, 1.49, 175), + new PayoutRange(1.5, PayoutRange.NOLIMIT, 225) + }, + PayoutType.Department); + + public static readonly PayoutProfile ABMBOMCreditCardPayout = new PayoutProfile(16, + Product.CreditCard, new[] { Role.ABM, Role.BOM, Role.ManagementTrainee }, + new[] + { + new PayoutRange(1.0, 1.24, 50), + new PayoutRange(1.25, 1.49, 100), + new PayoutRange(1.5, PayoutRange.NOLIMIT, 125) + }, + PayoutType.Department); + + public static readonly PayoutProfile MSCCreditCardPayout = new PayoutProfile(17, + Product.CreditCard, new[] { Role.MSCTeamLead }, + new[] + { + new PayoutRange(1.0, 1.24, 50), + new PayoutRange(1.25, 1.49, 75), + new PayoutRange(1.5, PayoutRange.NOLIMIT, 100) + }, + PayoutType.Department); + + public static readonly PayoutProfile BMABMPFOGAPPayout = new PayoutProfile(18, + Product.GAPInsurance, + new[] { Role.BM, Role.ABM, Role.PFO, Role.ManagementTrainee, Role.MSCTeamLead, Role.MSLO, Role.UniversalSpecialistPFO }, + new[] + { + new PayoutRange(0.3, 0.39, 30), + new PayoutRange(0.4, 0.49, 40), + new PayoutRange(0.5, 0.59, 50), + new PayoutRange(0.6, PayoutRange.NOLIMIT, 60) + }, + PayoutType.Personal, PayForEach, false); + + public static readonly List PayoutProfiles = new List + { + ABMBOMCreditCardPayout, + ABMBOMNewChecking, + ABMMortgage, + ABMVehicle, + BMABMPFOGAPPayout, + BMCreditCard, + BMMortgage, + BMNewChecking, + BMVehicle, + MSCCreditCardPayout, + MSCMortgage, + MSCNewChecking, + MSCPersonalMortgage, + MSCPersonalNewChecking, + MSCPersonalVehicle, + PFOMSLOMortgage, + PFOMSLONewChecking, + PFOMSLOVehicle, + }; + + public static readonly List ActivePayoutProfiles = PayoutProfiles.Where(x => x.IsEnabled).ToList(); + + public static IEnumerable FindForRole(Role employeeRole) + { + return ActivePayoutProfiles.Where(profile => profile.Roles.Contains(employeeRole)); + } + + public static PayoutProfile FindById(int id) + { + return PayoutProfiles.First(profile => profile.Id == id); + } + + public static PayoutProfile LookupProfile(Product product, Role role, PayoutType type) + { + return ActivePayoutProfiles + .Find(profile => profile.Product == product + && profile.Roles.Contains(role) + && profile.Type == type); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/PayoutProfileExtensions.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/PayoutProfileExtensions.cs new file mode 100644 index 0000000..ea1f512 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/PayoutProfileExtensions.cs @@ -0,0 +1,57 @@ +using System; +using System.Linq; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public static class PayoutProfileExtensions + { + public static PayoutRange FindPayoutRange(this PayoutProfile profile, double percentage, DateTime runDate) + { + var percentageToHundreds = Math.Truncate(100 * percentage) / 100; + var defaultPayout = profile.Payouts.First(defaultPayout => defaultPayout.Key == DateTime.MinValue).Value; + if (profile.Payouts.Count == 1) + { + return defaultPayout.FirstOrDefault(payoutRange => payoutRange.IsInRange(percentageToHundreds)); + } + + var payout = profile.Payouts.FirstOrDefault(payout => + { + var dateTimeOfPayout = payout.Key; + return dateTimeOfPayout.Month == runDate.Month && dateTimeOfPayout.Year == runDate.Year; + }).Value; + + var t = payout == null ? defaultPayout.FirstOrDefault(payoutRange => payoutRange.IsInRange(percentageToHundreds)) : payout.FirstOrDefault(payoutRange => payoutRange.IsInRange(percentageToHundreds)); + return t; + } + + public static decimal CalculateLineItemPayout(this PayoutProfile profile, int goal, decimal actual, DateTime runDate, decimal payoutPercent = 100) + { + if (profile == null) + { + throw new Exception("Attempt to calculate payout with null profile"); + } + + if (goal == 0) return 0; + + var goalPercentage = (double)actual / goal; + var payoutRange = profile.FindPayoutRange(goalPercentage, runDate); + + if (payoutRange == null) + { + return 0; + } + + decimal payoutAmt = payoutRange.Payout; + if (profile.PayEach) + { + payoutAmt *= actual; + } + + double payoutPercentage = (double)payoutPercent / 100; + double finalPayout = (double)payoutAmt * payoutPercentage; + payoutAmt = (decimal)Math.Round(finalPayout, 2); + + return payoutAmt; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/PayoutRange.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/PayoutRange.cs new file mode 100644 index 0000000..07c8148 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/PayoutRange.cs @@ -0,0 +1,22 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class PayoutRange + { + public const double NOLIMIT = -1.0; + public double MinimumPercentage { get; } + public double MaximumPercentage { get; } + public int Payout { get; } + + public PayoutRange(double minimumPercentage, double maximumPercentage, int payout) + { + MinimumPercentage = minimumPercentage; + MaximumPercentage = maximumPercentage; + Payout = payout; + } + + public bool IsInRange(double percentage) + { + return percentage >= MinimumPercentage && (percentage <= MaximumPercentage || MaximumPercentage == NOLIMIT); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/Permission.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/Permission.cs new file mode 100644 index 0000000..30efedd --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/Permission.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class Permission + { + public string Code { get; } + public string Description { get; } + + private Permission(string code, string description) + { + Code = code; + Description = description; + } + + public static Permission LookupPermissionFromCode(string code) + { + var foundPermissions = GetAllPermissions().Where(x => x.Code == code).ToList(); + if (foundPermissions.Count != 1) + { + throw new Exception($"{foundPermissions.Count} found using Code: {code}"); + } + + return foundPermissions.FirstOrDefault(); + } + + public static readonly Permission AccessTestEnvironments = new Permission("ACCESS_TEST_ENVIRONMENT", "User can access the test environments"); + + public static readonly Permission AssignAssociatesToDepartment = + new Permission("ASSIGN_ASSOCIATES_TO_DEPARTMENT", "User can assign associates to additional departments"); + + public static readonly Permission AssignPermissionsToAssociates = + new Permission("ASSIGN_PERMISSIONS_TO_ASSOCIATES", "User can add permissions to an associate's user role"); + + public static readonly Permission AssignRegionalManager = new Permission("ASSIGN_REGIONAL_MANAGER", "User can set the regional manager for a region"); + public static readonly Permission CreateActivity = new Permission("CREATE_ACTIVITY", "User can create manual activity"); + public static readonly Permission EligibleForReferrals = new Permission("ELIGIBLE_FOR_REFERRALS", "User is eligible for referrals"); + public static readonly Permission GeneratePayoutFiles = new Permission("GENERATE_PAYOUT_FILES", "User can generate payout files for HR"); + public static readonly Permission HaveSalesGoals = new Permission("HAVE_SALES_GOALS", "User can have sales goals"); + public static readonly Permission ManageCommissions = new Permission("MANAGE_COMMISSIONS", "User can manage the commission table"); + public static readonly Permission ManageDepartments = new Permission("MANAGE_DEPARTMENTS", "User can manage departments"); + public static readonly Permission ManageDepartmentGoals = new Permission("MANAGE_DEPARTMENT_GOALS", "User can manage their department goals"); + public static readonly Permission ManageEmployeeDetails = new Permission("MANAGE_EMPLOYEE_DETAILS", "User can manage employee details"); + public static readonly Permission ManagePersonnelGoals = new Permission("MANAGE_PERSONNEL_GOALS", "User can manage personnel goals"); + + public static readonly Permission ManageRoleMappings = + new Permission("MANAGE_ROLE_MAPPINGS", "User can manage the business to application role mappings"); + + public static readonly Permission SetActivityAcceptedDate = new Permission("SET_ACTIVITY_ACCEPTED_DATE", "User can set activity accepted date"); + + public static readonly Permission SetActivityToAcceptedFromManualReview = + new Permission("SET_ACTIVITY_TO_ACCEPTED_FROM_MANUAL_REVIEW", "User can set activity to Accepted from ManualReview"); + + public static readonly Permission SetActivityToAcceptedFromPendingReferral = new Permission("SET_ACTIVITY_TO_ACCEPTED_FROM_PENDING_REFERRAL", + "User can set activity to Accepted from PendingReferral"); + + public static readonly Permission SetActivityToAcceptedFromPendingTransfer = new Permission("SET_ACTIVITY_TO_ACCEPTED_FROM_PENDING_TRANSFER", + "User can set activity to Accepted from PendingTransfer"); + + public static readonly Permission SetActivityToAcceptedFromRejected = + new Permission("SET_ACTIVITY_TO_ACCEPTED_FROM_REJECTED", "User can set activity to Accepted from Rejected"); + + public static readonly Permission SetActivityToManualReviewFromAccepted = + new Permission("SET_ACTIVITY_TO_MANUAL_REVIEW_FROM_ACCEPTED", "User can set activity to ManualReview from Accepted"); + + public static readonly Permission SetActivityToManualReviewFromRejected = + new Permission("SET_ACTIVITY_TO_MANUAL_REVIEW_FROM_REJECTED", "User can set activity to ManualReview from Rejected"); + + public static readonly Permission SetActivityToPendingReferralFromAccepted = new Permission("SET_ACTIVITY_TO_PENDING_REFERRAL_FROM_ACCEPTED", + "User can set activity to PendingReferral from Accepted"); + + public static readonly Permission SetActivityToPendingReferralFromRejected = new Permission("SET_ACTIVITY_TO_PENDING_REFERRAL_FROM_REJECTED", + "User can set activity to PendingReferral from Rejected"); + + public static readonly Permission SetActivityToPendingTransferFromAccepted = new Permission("SET_ACTIVITY_TO_PENDING_TRANSFER_FROM_ACCEPTED", + "User can set activity to PendingTransfer from Accepted"); + + public static readonly Permission SetActivityToRejectedFromAccepted = + new Permission("SET_ACTIVITY_TO_REJECTED_FROM_ACCEPTED", "User can set activity to Rejected from Accepted"); + + public static readonly Permission SetActivityToRejectedFromManualReview = + new Permission("SET_ACTIVITY_TO_REJECTED_FROM_MANUAL_REVIEW", "User can set activity to Rejected from ManualReview"); + + public static readonly Permission SetActivityToTransferredFromPendingTransfer = new Permission("SET_ACTIVITY_TO_TRANSFERRED_FROM_PENDING_TRANSFER", + "User can set activity to Transferred from PendingTransfer"); + + public static readonly Permission TransferActivitiesBetweenAssociates = + new Permission("TRANSFER_ACTIVITIES_BETWEEN_ASSOCIATES", "User can transfer activities between associates"); + + public static readonly Permission TransferActivitiesBetweenDepartments = + new Permission("TRANSFER_ACTIVITIES_BETWEEN_DEPARTMENTS", "User can transfer activities between departments"); + + public static readonly Permission Unknown = new Permission("UNKNOWN", "A Permission Could Not Be Parsed By The System. Possibly Removed."); + public static readonly Permission ViewActivityAudit = new Permission("VIEW_ACTIVITY_AUDIT", "User can view the activity audit page"); + public static readonly Permission ViewActivityReport = new Permission("VIEW_ACTIVITY_REPORT", "User can view the activity report"); + public static readonly Permission ViewAuditReport = new Permission("VIEW_AUDIT_REPORT", "User can view the audit report"); + public static readonly Permission ViewAssociateRecordReport = new Permission("VIEW_ASSOCIATE_RECORD_REPORT", "User can view the associate record report"); + + public static readonly Permission ViewDashboard = new Permission("VIEW_DASHBOARD", + "User can see a dashboard showing their referral totals and their sales totals compared to goal"); + + public static readonly Permission ViewDeadActivityQueue = new Permission("VIEW_DEAD_ACTIVITY_QUEUE", "User can see the dead activity queue"); + public static readonly Permission ViewGoalProgressReport = new Permission("VIEW_GOAL_PROGRESS_REPORT", "User can view the goal progress report"); + public static readonly Permission ViewPayoutReport = new Permission("VIEW_PAYOUT_REPORT", "User can view the payout report"); + + public static readonly Permission ViewPerformanceForBusinessLine = + new Permission("VIEW_PERFORMANCE_FOR_BUSINESS_LINE", "User can see performance for their business line"); + + public static readonly Permission ViewPerformanceForDepartment = + new Permission("VIEW_PERFORMANCE_FOR_DEPARTMENT", "User can see performance for their department"); + + public static readonly Permission ViewPerformanceForRegion = new Permission("VIEW_PERFORMANCE_FOR_REGION", "User can see performance for their region"); + public static readonly Permission ViewReports = new Permission("VIEW_REPORTS", "User can access the reports landing page"); + + public static List GetAllPermissions(bool excludeUnknown = false) + { + var foundInstances = new List(); + + foreach (var field in typeof(Permission).GetFields(BindingFlags.Static | BindingFlags.Public)) + { + if (field.FieldType != typeof(Permission)) + { + continue; + } + + if (field.GetValue(field.Name) is Permission instance) + { + foundInstances.Add(instance); + } + } + + return excludeUnknown ? foundInstances.Where(x => x.Code != Unknown.Code).ToList() : foundInstances; + } + + /** Collection of permissions needed for minimal application access */ + public static List BaselinePermissions() + { + return new List + { + ViewActivityAudit, + ViewDashboard + }; + } + + public static List DepartmentManagerPermissions() + { + return new List + { + AssignAssociatesToDepartment, + SetActivityToRejectedFromManualReview, + TransferActivitiesBetweenAssociates, + TransferActivitiesBetweenDepartments, + ViewReports, + ViewActivityReport, + ViewGoalProgressReport, + ViewPerformanceForDepartment + } + .Concat(TransferApprovalPermissions()) + .Concat(TransferRejectionPermissions()) + .Concat(TransferRequestPermissions()) + .ToList(); + } + + public static List TransferApprovalPermissions() + { + return new List + { + SetActivityToTransferredFromPendingTransfer + }; + } + + public static List TransferRejectionPermissions() + { + return new List + { + SetActivityToAcceptedFromPendingTransfer + }; + } + + public static List TransferRequestPermissions() + { + return new List + { + SetActivityToPendingTransferFromAccepted + }; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ProcessorRunHistory.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ProcessorRunHistory.cs new file mode 100644 index 0000000..f093d3c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ProcessorRunHistory.cs @@ -0,0 +1,13 @@ +using System; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class ProcessorHistoryRecord + { + public Process Process { get; set; } + public DateTime LastSuccessfulRunTime { get; set; } + public long LastProcessedRecordId { get; set; } + public DateTime LastProcessedRecordTimestamp { get; set; } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ProductDetail.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ProductDetail.cs new file mode 100644 index 0000000..ee8b25c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/ProductDetail.cs @@ -0,0 +1,277 @@ +using System.Collections.Generic; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class ProductDetail + { + private ProductDetail( + Product product, + string title, + ProductValue valueType, + int minValue, + int maxValue, + bool hasGoals, + bool hasCommissions, + bool isSellable, + bool isReferrable, + bool isNonPaying + ) + { + Product = product; + Title = title; + ValueType = valueType; + MinValue = minValue; + MaxValue = maxValue; + HasGoals = hasGoals; + HasCommissions = hasCommissions; + IsSellable = isSellable; + IsReferrable = isReferrable; + IsNonPaying = isNonPaying; + } + + public Product Product { get; } + public string Title { get; } + public ProductValue ValueType { get; } + public int MinValue { get; } + public int MaxValue { get; } + public bool HasGoals { get; } + public bool HasCommissions { get; } + public bool IsSellable { get; } + public bool IsReferrable { get; } + public bool IsNonPaying { get; } + + public static readonly ProductDetail ATMCard = new( + Product.ATMCard, + "ATM Card", + ProductValue.Numeric, + 1, + 99, + false, + true, + false, + true, + true + ); + + public static readonly ProductDetail CheckCard = new( + Product.CheckCard, + "Check Card", + ProductValue.Numeric, + 1, + 99, + false, + true, + false, + true, + true + ); + + public static readonly ProductDetail CreditCard = new( + Product.CreditCard, + "Credit Card", + ProductValue.Numeric, + 1, + 99, + true, + true, + true, + true, + false + ); + + public static readonly ProductDetail CreditCardBalanceTransfer = new( + Product.CreditCardBalanceTransfer, + "CCBT", + ProductValue.Numeric, + 1, + 99, + false, + true, + false, + true, + false + ); + + public static readonly ProductDetail DebtProtection = new( + Product.DebtProtection, + "Debt Protection", + ProductValue.Numeric, + 1, + 99, + false, + true, + true, + false, + false + ); + + public static readonly ProductDetail DebtProtectionEligibleLoan = new( + Product.DebtProtectionEligibleLoan, + "DP Eligible Loan", + ProductValue.Numeric, + 1, + 99, + false, + false, + true, + false, + true + ); + + public static readonly ProductDetail DirectDeposit = new( + Product.DirectDeposit, + "Direct Deposit", + ProductValue.Numeric, + 1, + 99, + false, + true, + false, + true, + false + ); + + public static readonly ProductDetail eStatement = new( + Product.eStatement, + "eStatement", + ProductValue.Numeric, + 1, + 99, + false, + true, + false, + true, + false + ); + + public static readonly ProductDetail FirstMortgageReferral = new( + Product.FirstMortgageReferral, + "First Mortgage Referral", + ProductValue.Numeric, + 1, + 99, + false, + true, + false, + true, + false + ); + + public static readonly ProductDetail GAPInsurance = new( + Product.GAPInsurance, + "GAPInsurance", + ProductValue.Numeric, + 1, + 99, + false, + true, + true, + false, + false + ); + + public static readonly ProductDetail LIC = new( + Product.LIC, + "LIC", + ProductValue.Numeric, + 1, + 99, + true, + true, + false, + true, + false + ); + + public static readonly ProductDetail MechanicalBreakdownProtection = new( + Product.MechanicalBreakdownProtection, + "MBP", + ProductValue.Numeric, + 1, + 99, + false, + true, + true, + false, + false + ); + + public static readonly ProductDetail Mortgage = new( + Product.Mortgage, + "Mortgage", + ProductValue.Monetary, + 1, + 99999999, + true, + true, + true, + true, + false + ); + + public static readonly ProductDetail NewChecking = new( + Product.NewChecking, + "New Checking", + ProductValue.Numeric, + 1, + 99, + true, + true, + true, + true, + false + ); + + public static readonly ProductDetail Unknown = new( + Product.Unknown, + "Unknown", + ProductValue.Unknown, + 1, + 99, + false, + false, + false, + false, + false + ); + + public static readonly ProductDetail Vehicle = new( + Product.Vehicle, + "Vehicle", + ProductValue.Numeric, + 1, + 99, + true, + true, + true, + true, + false + ); + + public static readonly List ProductDetails = new() + { + ATMCard, + CheckCard, + CreditCard, + CreditCardBalanceTransfer, + DebtProtection, + DebtProtectionEligibleLoan, + DirectDeposit, + eStatement, + FirstMortgageReferral, + GAPInsurance, + LIC, + MechanicalBreakdownProtection, + Mortgage, + NewChecking, + Unknown, + Vehicle + }; + + public static ProductDetail DetailsForProduct(Product product) + { + return ProductDetails.Find(x => x.Product == product); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/Region.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/Region.cs new file mode 100644 index 0000000..7999ab2 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/Region.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Linq; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class Region + { + public string Name { get; } + public string Code { get; } + public string BusinessLineCode { get; } + + private Region(string name, string code, string businessLineCode) + { + Name = name; + Code = code; + BusinessLineCode = businessLineCode; + } + + public static Region East = new Region("East", "EAST", BusinessLine.BranchOperations.Code); + public static Region North = new Region("North", "NORTH", BusinessLine.BranchOperations.Code); + public static Region South = new Region("South", "SOUTH", BusinessLine.BranchOperations.Code); + public static Region West = new Region("West", "WEST", BusinessLine.BranchOperations.Code); + + public static List Regions = new List + { + East, + North, + South, + West + }; + + public static Region FindByCode(string code) => Regions.FirstOrDefault(region => region.Code == code); + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/Role.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/Role.cs new file mode 100644 index 0000000..e114e87 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/Role.cs @@ -0,0 +1,458 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Reflection; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class Role + { + [Required] + [StringLength(255, MinimumLength = 2)] + public string Name { get; } + + public string Description { get; } + public IReadOnlyCollection Permissions { get; } + public bool HasCommissions { get; } + public int Rank { get; } + public bool CanBeAssigned { get; } + + private Role( + string name, + string description, + List permissions, + bool hasCommissions, + int rank = 1, + bool canBeAssigned = true + ) + { + Name = name; + Description = description; + Permissions = permissions; + HasCommissions = hasCommissions; + Rank = rank; + CanBeAssigned = canBeAssigned; + } + + public static List GetRolesList() + { + return typeof(Role).GetFields(BindingFlags.Public | BindingFlags.Static) + .Where(field => field.FieldType == typeof(Role)) + .Select(field => (Role)field.GetValue(field)) + .ToList(); + } + + public static List SearchRoles(string term) + { + term = term.ToLower().Trim(); + return GetRolesList() + .Where(role => role.Name.ToLower().Contains(term) || + role.Description.ToLower().Contains(term)) + .ToList(); + } + + public static Role DetermineRoleOrDefault(string role) + { + return GetRolesList().FirstOrDefault(r => r.Name.Equals(role)) ?? UNKNOWN_ROLE; + } + + public static readonly Role SystemAppUser = new( + "SystemAppUser", + "System App User", new List(), + false, + 0, + false + ); + + public static readonly Role Admin = new( + "Admin", + "Admin", + Permission.GetAllPermissions(true), + false, + 99 + ); + + public static readonly Role BusinessLineManager = new( + "BusinessLineManager", + "Business Line Manager", + new List() + { + Permission.ManageDepartmentGoals, + Permission.ManagePersonnelGoals, + Permission.ViewPerformanceForBusinessLine, + Permission.ViewPerformanceForRegion + }.Concat(Permission.BaselinePermissions()) + .ToList() + .Concat(Permission.DepartmentManagerPermissions()) + .ToList(), + true, + 98 + ); + + public static readonly Role RegionalManager = new( + "RegionalManager", + "Regional Manager", + new List() + { + Permission.ManageDepartmentGoals, + Permission.ViewPerformanceForRegion + }.Concat(Permission.BaselinePermissions()) + .ToList() + .Concat(Permission.DepartmentManagerPermissions()) + .ToList(), + true, + 97 + ); + + public static readonly Role Manager = new( + "Manager", + "Manager", + new List() + { + Permission.ManagePersonnelGoals + }.Concat(Permission.BaselinePermissions()) + .ToList() + .Concat(Permission.DepartmentManagerPermissions()) + .ToList(), + true, + 96 + ); + + public static readonly Role MortgageManager = new( + "MortgageManager", + "Mortgage Manager", + new List() + { + Permission.ManagePersonnelGoals, + Permission.EligibleForReferrals + }.Concat(Permission.BaselinePermissions()) + .ToList() + .Concat(Permission.DepartmentManagerPermissions()) + .ToList(), + true, + 96 + ); + + public static readonly Role BM = new( + "BM", + "Branch Manager", + new List() + { + Permission.EligibleForReferrals, + Permission.HaveSalesGoals, + Permission.ManagePersonnelGoals + }.Concat(Permission.BaselinePermissions()) + .ToList() + .Concat(Permission.DepartmentManagerPermissions()) + .ToList(), + true, + 96 + ); + + public static readonly Role InStoreBranchManager = new( + "InStoreBranchManager", + "In-Store Branch Manager", + new List() + { + Permission.EligibleForReferrals, + Permission.HaveSalesGoals + }.Concat(Permission.BaselinePermissions()) + .ToList() + .Concat(Permission.DepartmentManagerPermissions()) + .ToList(), + true, + 96 + ); + + public static readonly Role ABM = new( + "ABM", + "Assistant Branch Manager", + new List() + { + Permission.EligibleForReferrals, + Permission.HaveSalesGoals, + Permission.ManagePersonnelGoals + }.Concat(Permission.BaselinePermissions()) + .ToList() + .Concat(Permission.DepartmentManagerPermissions()) + .ToList(), + true, + 95 + ); + + public static readonly Role InStoreAssistantBranchManager = new( + "InStoreAssistantBranchManager", + "In-Store Assistant Branch Manager", + new List() + { + Permission.EligibleForReferrals, + Permission.HaveSalesGoals + }.Concat(Permission.BaselinePermissions()) + .ToList() + .Concat(Permission.DepartmentManagerPermissions()) + .ToList(), + true, + 95 + ); + + public static readonly Role ManagementTrainee = new( + "ManagementTrainee", + "Management Trainee", + new List() + { + Permission.EligibleForReferrals, + Permission.HaveSalesGoals, + Permission.ManagePersonnelGoals + }.Concat(Permission.BaselinePermissions()) + .ToList() + .Concat(Permission.DepartmentManagerPermissions()) + .ToList(), + true, + 95 + ); + + public static readonly Role MSCTeamLead = new( + "MSCTeamLead", + "Member Service Center Team Lead", + new List() + { + Permission.AssignAssociatesToDepartment, + Permission.EligibleForReferrals, + Permission.HaveSalesGoals, + Permission.ViewReports, + Permission.ViewActivityReport, + Permission.ViewGoalProgressReport, + Permission.ViewPerformanceForDepartment + }.Concat(Permission.BaselinePermissions()) + .ToList(), + true, + 95 + ); + + public static readonly Role BOM = new( + "BOM", + "Branch Operations Manager", + new List() + { + Permission.AssignAssociatesToDepartment, + Permission.EligibleForReferrals, + Permission.HaveSalesGoals, + Permission.ViewReports, + Permission.ViewActivityReport, + Permission.ViewGoalProgressReport, + Permission.ViewPerformanceForDepartment, + Permission.TransferActivitiesBetweenAssociates, + Permission.TransferActivitiesBetweenDepartments + } + .Concat(Permission.BaselinePermissions()) + .Concat(Permission.TransferApprovalPermissions()) + .Concat(Permission.TransferRejectionPermissions()) + .Concat(Permission.TransferRequestPermissions()) + .ToList(), + true, + 94 + ); + + public static readonly Role MSLO = new( + "MSLO", + "Member Services Loan Officer", + new List() + { + Permission.EligibleForReferrals, + Permission.HaveSalesGoals, + }.Concat(Permission.BaselinePermissions()) + .ToList(), + true, + 93 + ); + + public static readonly Role PFO = new( + "PFO", + "PERS FIN OFFICER", + new List() + { + Permission.EligibleForReferrals, + Permission.HaveSalesGoals, + }.Concat(Permission.BaselinePermissions()) + .ToList(), + true, + 93 + ); + + public static readonly Role UniversalSpecialistPFO = new( + "UniversalSpecialist-PFO", + "Universal Specialist - PFO", + new List() + { + Permission.EligibleForReferrals, + Permission.HaveSalesGoals, + }.Concat(Permission.BaselinePermissions()) + .ToList(), + true, + 93 + ); + + public static readonly Role UniversalSpecialistTeller = new( + "UniversalSpecialist-Teller", + "Universal Specialist - Teller", + new List() + { + Permission.EligibleForReferrals, + }.Concat(Permission.BaselinePermissions()) + .ToList(), + true, + 93 + ); + + public static readonly Role LIC = new( + "LIC", + "Landmark Investment Center", + new List() + { + Permission.ViewPerformanceForDepartment, + }.Concat(Permission.BaselinePermissions()) + .ToList(), + true + ); + + public static readonly Role Mortgage = new( + "Mortgage", + "Mortgage", + new List() + { + Permission.EligibleForReferrals, + }.Concat(Permission.BaselinePermissions()) + .ToList(), + true + ); + + public static readonly Role Teller = new( + "Teller", + "Teller", + new List() + { + Permission.EligibleForReferrals, + }.Concat(Permission.BaselinePermissions()) + .ToList(), + true + ); + + public static readonly Role ConsumerLoanCoordinator = new( + "ConsumerLoanCoordinator", + "Consumer Loan Coordinator", + new List() + { + Permission.EligibleForReferrals, + }.Concat(Permission.BaselinePermissions()) + .ToList(), + true + ); + + public static readonly Role LicAdmin = new( + "LicAdmin", + "LIC Admin", + new List() + { + Permission.SetActivityToAcceptedFromRejected, + Permission.SetActivityToManualReviewFromRejected, + Permission.SetActivityToRejectedFromAccepted, + Permission.SetActivityToRejectedFromManualReview, + Permission.TransferActivitiesBetweenAssociates, + Permission.TransferActivitiesBetweenDepartments + } + .Concat(Permission.BaselinePermissions()) + .Concat(Permission.TransferApprovalPermissions()) + .Concat(Permission.TransferRejectionPermissions()) + .Concat(Permission.TransferRequestPermissions()) + .ToList(), + false + ); + + public static readonly Role MSR = new( + "MSR", + "Member Services Representative", + new List() + { + Permission.EligibleForReferrals, + }.Concat(Permission.BaselinePermissions()) + .ToList(), + true + ); + + public static readonly Role OpsHelpDesk = new( + "OpsHelpDesk", + "Ops Help Desk", + new List() + { + Permission.EligibleForReferrals, + }.Concat(Permission.BaselinePermissions()) + .ToList(), + true + ); + + public static readonly Role ManagementTraineePFO = new( + "ManagementTrainee-PFO", + "Management Trainee - PFO", + new List() + { + Permission.EligibleForReferrals, + Permission.HaveSalesGoals, + }.Concat(Permission.BaselinePermissions()) + .ToList(), + true, + -1, + false + ); + + public static readonly Role UNKNOWN_ROLE = new( + "UNKNOWN", + "Base role has no access", + new List(), + false, + 0, + false + ); + + public static readonly Role MISSING_ROLE = new( + "MISSING", + "User base role unknown.", + new List(), + false, + 0, + false + ); + + public static readonly Role Developer = new( + "Developer", + "Developer", + new List() + { + Permission.AccessTestEnvironments + }.Concat(Permission.BaselinePermissions()) + .ToList(), + false, + 0, + false + ); + + public static List DepartmentManagerRoles => new() + { + ABM, + BOM, + BM, + BusinessLineManager, + InStoreAssistantBranchManager, + InStoreBranchManager, + ManagementTrainee, + Manager, + MortgageManager, + RegionalManager + }; + + public static List LeadershipRoles => new() + { + BusinessLineManager, + RegionalManager + }; + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/RoleMapping.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/RoleMapping.cs new file mode 100644 index 0000000..cff009f --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/RoleMapping.cs @@ -0,0 +1,17 @@ +using LCU.Library.Internal.IncentiveApiModels.Common; +using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class RoleMapping : Auditable + { + [Required] + public int Id { get; set; } + [Required] + [JsonConverter(typeof(RoleConverter))] + public Role ApplicationRole { get; set; } + [Required] + public string BusinessRole { get; set; } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/UserAssignment.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/UserAssignment.cs new file mode 100644 index 0000000..19839de --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/UserAssignment.cs @@ -0,0 +1,15 @@ +using LCU.Library.Internal.IncentiveApiModels.Enums; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class UserAssignment + { + public int Id { get; set; } + [JsonConverter(typeof(StringEnumConverter))] + public AssignmentType Type { get; set; } + public string Key { get; set; } + public AppUser AppUser { get; set; } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/UserDepartment.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/UserDepartment.cs new file mode 100644 index 0000000..506243c --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/UserDepartment.cs @@ -0,0 +1,56 @@ +using System; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class UserDepartment : Auditable + { + public const int PayoutMax = 100; + public int Id { get; set; } + public Guid AppUserId { get; set; } + public int DepartmentId { get; set; } + public Role Role { get; set; } + public DateTime StartDate { get; set; } + public DateTime? EndDate { get; set; } + public string? Note { get; set; } + public bool IsHome { get; set; } + public decimal SlidingPayout { get; set; } + public AppUser Employee { get; set; } + public Department Department { get; set; } + + public int SafeGetDepartmentId() + { + return Department?.Id ?? DepartmentId; + } + + public bool IsConflictingAssignment(UserDepartment newAssignment, out string message) + { + message = string.Empty; + if (newAssignment.Employee.EmployeeId != Employee.EmployeeId || newAssignment.Department.Id != DepartmentId) + { + return false; + } + + if (EndDate == null) + { + message = $"Current assignment to department: {newAssignment.Department.DepartmentName} exists."; + return true; + } + + if (newAssignment.EndDate == null) + { + var startsBeforeEndDate = newAssignment.StartDate.Date <= EndDate.Value.Date; + message = startsBeforeEndDate ? $"Assignment would overlap existing assignment to department: {newAssignment.Department.DepartmentName}" : string.Empty; + return startsBeforeEndDate; + } + + if (newAssignment.StartDate.Date > EndDate.Value.Date || newAssignment.EndDate.Value.Date < StartDate.Date) + { + return false; + } + + message = $"Assignment would overlap existing assignment to department: {newAssignment.Department.DepartmentName}"; + return true; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/UserDepartmentHistory.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/UserDepartmentHistory.cs new file mode 100644 index 0000000..436d68d --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/UserDepartmentHistory.cs @@ -0,0 +1,13 @@ +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class UserDepartmentHistory : AuditCreate + { + public int Id { get; set; } + public int UserDepartmentId { get; set; } + public string FieldName { get; set; } + public string OldValue { get; set; } + public string NewValue { get; set; } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/UserPermission.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/UserPermission.cs new file mode 100644 index 0000000..27c00d0 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/UserPermission.cs @@ -0,0 +1,15 @@ +using System; +using LCU.Library.Internal.IncentiveApiModels.Common; +using Newtonsoft.Json; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class UserPermission : AuditCreate + { + public int Id { get; set; } + public Guid AppUserId { get; set; } + public Permission Permission { get; set; } + [JsonIgnore] + public AppUser AppUser { get; set; } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/UserPermissionHistory.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/UserPermissionHistory.cs new file mode 100644 index 0000000..48c2fa1 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/UserPermissionHistory.cs @@ -0,0 +1,16 @@ +using System; +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class UserPermissionHistory : AuditCreate + { + public int Id { get; set; } + public Guid AppUserId { get; set; } + public int EmployeeId { get; set; } + public string Source { get; set; } + public string Permission { get; set; } + public bool PermissionAdded { get; set; } + public bool PermissionRemoved { get; set; } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/UserSummary.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/UserSummary.cs new file mode 100644 index 0000000..08fe7d8 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Entities/UserSummary.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; + +namespace LCU.Library.Internal.IncentiveApiModels.Entities +{ + public class UserSummary + { + public UserSummary() + { + AssociatedPayouts = new List(); + } + public string AdEmail { get; set; } + public string EmployeeId { get; set; } + public string FullName { get; set; } + public string? ManagerKey { get; set; } + public DateTime? Terminated { get; set; } + public IList AssociatedPayouts { get; set; } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/ActivityState.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/ActivityState.cs new file mode 100644 index 0000000..42d55fe --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/ActivityState.cs @@ -0,0 +1,14 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Enums +{ + public enum ActivityState + { + Accepted, + Created, + ManualReview, + PendingReferral, + PendingTransfer, + Rejected, + Transferred, + Unknown + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/ActivityTransferState.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/ActivityTransferState.cs new file mode 100644 index 0000000..92c95dd --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/ActivityTransferState.cs @@ -0,0 +1,10 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Enums +{ + public enum ActivityTransferState + { + Accepted, + Rejected, + Requested, + Unknown + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/ActivityType.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/ActivityType.cs new file mode 100644 index 0000000..f3822f8 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/ActivityType.cs @@ -0,0 +1,10 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Enums +{ + public enum ActivityType + { + Other, + Referral, + Sale, + Unknown + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/AssignmentType.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/AssignmentType.cs new file mode 100644 index 0000000..d776ea7 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/AssignmentType.cs @@ -0,0 +1,8 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Enums +{ + public enum AssignmentType + { + RegionalManager, + Unknown + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/DeadActivityReason.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/DeadActivityReason.cs new file mode 100644 index 0000000..1a16651 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/DeadActivityReason.cs @@ -0,0 +1,10 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Enums +{ + public enum DeadActivityReason + { + FailureToMap, + MissingRequiredField, + Other, + Unknown + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/DepartmentState.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/DepartmentState.cs new file mode 100644 index 0000000..8ce6fc6 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/DepartmentState.cs @@ -0,0 +1,9 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Enums +{ + public enum DepartmentState + { + Active, + Inactive, + Unknown + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/GoalDispersalError.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/GoalDispersalError.cs new file mode 100644 index 0000000..dbd1337 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/GoalDispersalError.cs @@ -0,0 +1,10 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Enums +{ + public enum GoalDispersalError + { + DispersedToUserWithoutPermission, + NewUserRequiresManualEntry, + UserNoLongerInDepartment, + ErrorsDuringDisperse + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/GoalDispersalStrategy.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/GoalDispersalStrategy.cs new file mode 100644 index 0000000..92a534b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/GoalDispersalStrategy.cs @@ -0,0 +1,10 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Enums +{ + public enum GoalDispersalStrategy + { + CustomWeightedDispersal, + DefaultDisperseWithWeights, + ExtrapolateFromExistingGoals, + ExtrapolateSingleDepartmentProduct + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/IncentiveProcessType.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/IncentiveProcessType.cs new file mode 100644 index 0000000..13dc4a8 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/IncentiveProcessType.cs @@ -0,0 +1,13 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Enums +{ + public enum IncentiveProcessType + { + UpdateDnaCheckingAccountSales = 0, + UpdateDnaDigitalCheckingAccountSales = 1, + UpdateDnaAtmCheckCardAccountSales = 2, + UpdateDnaDpEligibleLoanAccountSales = 3, + UpdateDnaMortgageAccountSales = 4, + UpdateDnaActivityManagerReferrals = 5, + UpdateDnaVehicleAccountSales = 6, + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/MetaDataType.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/MetaDataType.cs new file mode 100644 index 0000000..b2db718 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/MetaDataType.cs @@ -0,0 +1,9 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Enums +{ + public enum MetaDataType + { + Email, + Phone, + Unknown + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/NightlyIncentiveProcessType.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/NightlyIncentiveProcessType.cs new file mode 100644 index 0000000..e83eeb3 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/NightlyIncentiveProcessType.cs @@ -0,0 +1,12 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Enums +{ + public enum NightlyIncentiveProcessType + { + UpdateDirectDepositReferralsProcess = 0, + UpdateReferralStatusProcess = 1, + UpdateAccountDecisionProcess = 2, + UpdateLICSalesProcess = 3, + UpdateEStatementProcess = 4, + UpdateApplicationDbIntegrityProcess = 5, + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/PayoutType.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/PayoutType.cs new file mode 100644 index 0000000..b461ac4 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/PayoutType.cs @@ -0,0 +1,8 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Enums +{ + public enum PayoutType + { + Department, + Personal + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/Process.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/Process.cs new file mode 100644 index 0000000..218cbee --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/Process.cs @@ -0,0 +1,16 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Enums +{ + public enum Process + { + ImportActivityManagerReferralsFromDna, + ImportAtmCheckCardSalesFromDna, + ImportCheckingAccountSalesFromDna, + ImportCheckingAccountSalesFromDigitalBanking, + ImportDpEligibleLoanSalesFromDna, + ImportGapMbpFromAlliedFile, + ImportMortgageSalesFromDna, + ImportVehicleSalesFromDna, + GetDeliveryMethodsFromDna, + Unknown + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/Product.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/Product.cs new file mode 100644 index 0000000..3bc4d5e --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/Product.cs @@ -0,0 +1,22 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Enums +{ + public enum Product + { + ATMCard, + CreditCardBalanceTransfer, + CheckCard, + NewChecking, + CreditCard, + DebtProtection, + DebtProtectionEligibleLoan, + DirectDeposit, + eStatement, + FirstMortgageReferral, + GAPInsurance, + LIC, + Mortgage, + MechanicalBreakdownProtection, + Vehicle, + Unknown + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/ProductValue.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/ProductValue.cs new file mode 100644 index 0000000..6222be3 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/ProductValue.cs @@ -0,0 +1,9 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Enums +{ + public enum ProductValue + { + Monetary, + Numeric, + Unknown + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/StateChangeRequirement.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/StateChangeRequirement.cs new file mode 100644 index 0000000..5a1d2d4 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/StateChangeRequirement.cs @@ -0,0 +1,11 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Enums +{ + public enum StateChangeRequirement + { + AdminOnly, + AdminOrPermission, + Hierarchical, + NotAllowed, + SystemOnly + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/SystemType.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/SystemType.cs new file mode 100644 index 0000000..3063d5e --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/SystemType.cs @@ -0,0 +1,18 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Enums +{ + public enum SystemType + { + ActivityManager = 0, + Action = 1, + Allied = 2, + ClientCentral = 3, + DigitalBanking = 4, + Dna = 5, + IncentiveLog = 6, + IncentiveManager = 7, + Manual = 8, + Redtail = 9, + Unknown = 10, + Temenos = 11 + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/TimeOfDay.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/TimeOfDay.cs new file mode 100644 index 0000000..eb89b44 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/TimeOfDay.cs @@ -0,0 +1,8 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Enums +{ + public enum TimeOfDay + { + Evening, + Morning + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/UserType.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/UserType.cs new file mode 100644 index 0000000..3e1a479 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Enums/UserType.cs @@ -0,0 +1,8 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Enums +{ + public enum UserType + { + Associate, + System + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/LCU.Library.Internal.IncentiveApiModels.csproj b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/LCU.Library.Internal.IncentiveApiModels.csproj new file mode 100644 index 0000000..0acf75f --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/LCU.Library.Internal.IncentiveApiModels.csproj @@ -0,0 +1,38 @@ + + + + netstandard2.1 + 9.0 + True + 1.1.4 + + 1.0.1 - Initial Creation of Models Nugets + 1.0.2 - Added vehicle import models + 1.0.3 - Updated credit card and vehicle import models + 1.1.0 - Refactor Upgrade Mass Transit + 1.1.1 - Monthly Process Model changes + 1.1.2 - Upgraded FileOperations package version + 1.1.3 - Update Goal Dispersal Request + 1.1.4 - Update Goal Dispersal Request Start Date + + + 1.0.1 - Initial Creation of Models Nugets + 1.0.2 - Added vehicle import models + 1.0.3 - Updated credit card and vehicle import models + 1.1.0 - Refactor Upgrade Mass Transit + 1.1.1 - Monthly Process Model changes + 1.1.2 - Upgraded FileOperations package version + 1.1.3 - Update Goal Dispersal Request + 1.1.4 - Update Goal Dispersal Request Start Date + + + + + + + + + + + + diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/ActionCreditCardRequest.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/ActionCreditCardRequest.cs new file mode 100644 index 0000000..4a9298b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/ActionCreditCardRequest.cs @@ -0,0 +1,25 @@ +using LCU.Library.Internal.ApiResponse.Models; +using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations; +using LCU.Library.Internal.FileOperationsModels.Models.NameSettings; + +namespace LCU.Library.Internal.IncentiveApiModels.Requests +{ + public class ActionCreditCardRequest : ReturnResponse + { + [Required] + public string FilePath { get; set; } + [Required] + public string DynamicFileName { get; set; } + [Required] + public DynamicNameSetting DynamicFileSettings { get; set; } + [NotMapped] + public string FinalBasePath + { + get + { + return FilePath.EndsWith("\\") ? FilePath : $"{FilePath}\\"; + } + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/ActionVehicleRequest.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/ActionVehicleRequest.cs new file mode 100644 index 0000000..fcd6715 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/ActionVehicleRequest.cs @@ -0,0 +1,25 @@ +using LCU.Library.Internal.ApiResponse.Models; +using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations; +using LCU.Library.Internal.FileOperationsModels.Models.NameSettings; + +namespace LCU.Library.Internal.IncentiveApiModels.Requests +{ + public class ActionVehicleRequest : ReturnResponse + { + [Required] + public string FilePath { get; set; } + [Required] + public string DynamicFileName { get; set; } + [Required] + public DynamicNameSetting DynamicFileSettings { get; set; } + [NotMapped] + public string FinalBasePath + { + get + { + return FilePath.EndsWith("\\") ? FilePath : $"{FilePath}\\"; + } + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/AlliedGapMbpRequest.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/AlliedGapMbpRequest.cs new file mode 100644 index 0000000..d77b0a9 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/AlliedGapMbpRequest.cs @@ -0,0 +1,25 @@ +using LCU.Library.Internal.ApiResponse.Models; +using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations; +using LCU.Library.Internal.FileOperationsModels.Models.NameSettings; + +namespace LCU.Library.Internal.IncentiveApiModels.Requests +{ + public class AlliedGapMbpRequest : ReturnResponse + { + [Required] + public string FilePath { get; set; } + [Required] + public string DynamicFileName { get; set; } + [Required] + public DynamicNameSetting DynamicFileSettings { get; set; } + [NotMapped] + public string FinalBasePath + { + get + { + return FilePath.EndsWith("\\") ? FilePath : $"{FilePath}\\"; + } + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/DayforceProcessRequest.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/DayforceProcessRequest.cs new file mode 100644 index 0000000..4d6e362 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/DayforceProcessRequest.cs @@ -0,0 +1,11 @@ +using LCU.Library.Internal.ApiResponse.Models; +using System.Collections.Generic; + +namespace LCU.Library.Internal.IncentiveApiModels.Requests +{ + public class DayforceProcessRequest : ReturnResponse + { + public List MissingRoleNotification { get; set; } + public List TerminatedEmployeeNotification { get; set; } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/DepartmentPersonnelSearchRequest.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/DepartmentPersonnelSearchRequest.cs new file mode 100644 index 0000000..f6ed036 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/DepartmentPersonnelSearchRequest.cs @@ -0,0 +1,15 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Requests +{ + public class DepartmentPersonnelSearchRequest + { + public int DepartmentId { get; set; } + public int Year { get; set; } + public int Month { get; set; } + public bool IncludeAllPersonnel { get; set; } + + public DepartmentPersonnelSearchRequest() + { + IncludeAllPersonnel = false; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/DepartmentUpdateRequest.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/DepartmentUpdateRequest.cs new file mode 100644 index 0000000..34285c6 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/DepartmentUpdateRequest.cs @@ -0,0 +1,19 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace LCU.Library.Internal.IncentiveApiModels.Requests +{ + public class DepartmentUpdateRequest + { + [Required] + public string EmployeeId { get; set; } + [Required] + public int AssignmentId { get; set; } + [Required] + [Range(0,100)] + public decimal SlidingPayout { get; set; } + [Required] + public string Role { get; set; } + public DateTime? EndDate { get; set; } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/GoalDispersalRequest.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/GoalDispersalRequest.cs new file mode 100644 index 0000000..f858667 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/GoalDispersalRequest.cs @@ -0,0 +1,23 @@ +using LCU.Library.Internal.ApiResponse.Models; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; + +namespace LCU.Library.Internal.IncentiveApiModels.Requests +{ + public class GoalDispersalRequest : ReturnResponse + { + public int MonthsBack { get; set; } = -1; + public List GoalDispersalNotification { get; set; } + [NotMapped] + public DateTime StartDate + { + get + { + var datePeriod = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); + datePeriod = datePeriod.AddMonths(MonthsBack); + return datePeriod; + } + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/GoalSearchRequest.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/GoalSearchRequest.cs new file mode 100644 index 0000000..9e7ca54 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/GoalSearchRequest.cs @@ -0,0 +1,16 @@ +using LCU.Library.Internal.IncentiveApiModels.Common; + +namespace LCU.Library.Internal.IncentiveApiModels.Requests +{ + public class GoalSearchRequest + { + public MonthRange? DateRange { get; set; } + public bool IncludePersonnel { get; set; } + public int? departmentId { get; set; } + + public GoalSearchRequest() + { + IncludePersonnel = true; + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/ImportCreditCardSalesRequest.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/ImportCreditCardSalesRequest.cs new file mode 100644 index 0000000..e415460 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/ImportCreditCardSalesRequest.cs @@ -0,0 +1,25 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using LCU.Library.Internal.ApiResponse.Models; +using LCU.Library.Internal.FileOperationsModels.Models.NameSettings; + +namespace LCU.Library.Internal.IncentiveApiModels.Requests +{ + public class ImportCreditCardSalesRequest : ReturnResponse + { + [Required] + public string FilePath { get; set; } + [Required] + public string DynamicFileName { get; set; } + [Required] + public DynamicNameSetting DynamicFileSettings { get; set; } + [NotMapped] + public string FinalBasePath + { + get + { + return FilePath.EndsWith("\\") ? FilePath : $"{FilePath}\\"; + } + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/ImportIndirectVehicleSalesRequest.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/ImportIndirectVehicleSalesRequest.cs new file mode 100644 index 0000000..7619f84 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/ImportIndirectVehicleSalesRequest.cs @@ -0,0 +1,29 @@ +using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations; +using LCU.Library.Internal.ApiResponse.Models; +using LCU.Library.Internal.FileOperationsModels.Models.NameSettings; + +namespace LCU.Library.Internal.IncentiveApiModels.Requests +{ + public class ImportIndirectVehicleSalesRequest : ReturnResponse + { + [Required] + public string FilePath { get; set; } + [Required] + public string IndirectDynamicFileName { get; set; } + [Required] + public DynamicNameSetting IndirectDynamicFileSettings { get; set; } + [Required] + public string DirectDynamicFileName { get; set; } + [Required] + public DynamicNameSetting DirectDynamicFileSettings { get; set; } + [NotMapped] + public string FinalBasePath + { + get + { + return FilePath.EndsWith("\\") ? FilePath : $"{FilePath}\\"; + } + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/IncentiveProcessRequest.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/IncentiveProcessRequest.cs new file mode 100644 index 0000000..a2c5268 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/IncentiveProcessRequest.cs @@ -0,0 +1,10 @@ +using LCU.Library.Internal.ApiResponse.Models; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.Library.Internal.IncentiveApiModels.Requests +{ + public class IncentiveProcessRequest : ReturnResponse + { + public IncentiveProcessType IncentiveProcessType { get; set; } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/NightlyIncentiveProcessRequest.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/NightlyIncentiveProcessRequest.cs new file mode 100644 index 0000000..db5001f --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/NightlyIncentiveProcessRequest.cs @@ -0,0 +1,10 @@ +using LCU.Library.Internal.ApiResponse.Models; +using LCU.Library.Internal.IncentiveApiModels.Enums; + +namespace LCU.Library.Internal.IncentiveApiModels.Requests +{ + public class NightlyIncentiveProcessRequest : ReturnResponse + { + public NightlyIncentiveProcessType NightlyIncentiveProcessType { get; set; } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/PayoutIncentiveRequest.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/PayoutIncentiveRequest.cs new file mode 100644 index 0000000..af41ad6 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/PayoutIncentiveRequest.cs @@ -0,0 +1,9 @@ +using LCU.Library.Internal.ApiResponse.Models; + +namespace LCU.Library.Internal.IncentiveApiModels.Requests +{ + public class PayoutIncentiveRequest : ReturnResponse + { + public int MonthsBack { get; set; } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/ReportingRequest.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/ReportingRequest.cs new file mode 100644 index 0000000..c89f531 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/ReportingRequest.cs @@ -0,0 +1,10 @@ +using LCU.Library.Internal.ApiResponse.Models; +using System; + +namespace LCU.Library.Internal.IncentiveApiModels.Requests +{ + public class ReportingRequest : ReturnResponse + { + public int MonthsBack { get; set; } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/UserSearchRequest.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/UserSearchRequest.cs new file mode 100644 index 0000000..f2641da --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Requests/UserSearchRequest.cs @@ -0,0 +1,12 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Requests +{ + public class UserSearchRequest + { + public string Query { get; set; } + public string Role { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string MiddleInit { get; set; } + public string DnaOrgNbr { get; set; } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Results/ActiveEmployeeResult.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Results/ActiveEmployeeResult.cs new file mode 100644 index 0000000..26a600f --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Results/ActiveEmployeeResult.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; + +namespace LCU.Library.Internal.IncentiveApiModels.Results +{ + public class ActiveEmployeeResult + { + public ActiveEmployeeResult(IReadOnlyDictionary row) + { + DisplayName = row["Employee_DisplayName"]; + EmployeeNumber = row["EmployeeEmploymentStatus_EmployeeNumber"]; + var hireDate = row["Employee_HireDate"]; + HireDate = string.IsNullOrWhiteSpace(hireDate) ? DateTime.Now : DateTime.Parse(hireDate); + LoginName = row["IdLibrary_LoginName"]; + if (row.ContainsKey("EmployeeManager_ManagerEmployeeNumber")) + { + ManagerKey = row["EmployeeManager_ManagerEmployeeNumber"]; + } + + OrgCode = row["OrgUnit_LedgerCode"]; + OrgUnit = row["OrgUnit_ShortName"]; + Role = row["Job_ShortName"]; + Status = row["EmploymentStatus_ShortName"]; + } + + public string DisplayName { get; } + public string EmployeeNumber { get; } + public DateTime HireDate { get; } + public string LoginName { get; } + public string? ManagerKey { get; } + public string OrgCode { get; } + public string OrgUnit { get; } + public string Role { get; } + public string Status { get; } + + public bool CanProcess() + { + return !string.IsNullOrWhiteSpace(EmployeeNumber) + && !string.IsNullOrWhiteSpace(LoginName) + && !string.IsNullOrWhiteSpace(OrgUnit) + && !string.IsNullOrWhiteSpace(Role) + && !string.IsNullOrWhiteSpace(Status) + && Status == "Active"; + } + + public string GetDnaOrgCode() + { + return "1" + OrgCode; + } + + public DateTime DetermineHireDate() + { + if (HireDate != DateTime.MinValue) + { + return HireDate; + } + + return new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); + } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Results/DataIntegrityAuditResult.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Results/DataIntegrityAuditResult.cs new file mode 100644 index 0000000..d68481b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Results/DataIntegrityAuditResult.cs @@ -0,0 +1,8 @@ +namespace LCU.Library.Internal.IncentiveApiModels.Results +{ + public class DataIntegrityAuditResult + { + public bool MaintenanceAutomationFailed { get; set; } + public bool MaintenanceWasRequired { get; set; } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Results/ProcessResult.cs b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Results/ProcessResult.cs new file mode 100644 index 0000000..d0968c2 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/LCU.Library.Internal.IncentiveApiModels/Results/ProcessResult.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace LCU.Library.Internal.IncentiveApiModels.Results +{ + public class ProcessResult + { + public ProcessResult() + { + Success = true; + MessageStack = new List(); + } + + public bool Success { get; set; } + public List MessageStack { get; set; } + } +} diff --git a/LCU.API.Operations.IncentiveAPI/README.md b/LCU.API.Operations.IncentiveAPI/README.md new file mode 100644 index 0000000..c750b3a --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/README.md @@ -0,0 +1,506 @@ +## Running The API Locally + +### 1. Pull down docker compose test environment setup +https://landmarkcu.visualstudio.com/Platform/_git/LCU.Setup.Internal.EnvironmentSetup +run 'docker compose up at root of docker compose directory to start all required testing containers' + +### 2. [OPTIONAL] Change Core Wrapper Target Instance As Needed Ex: If you are testing a change in core wrapper that will bring in more data for new feature update, point at local instance +- If you need up to date live data you can run core wrapper locally, and understand how to run the core wrapper against production locally in a read only manner. +- The Incentive API will only query for data and should never write anything to core, but you should always make sure nothing else is running on your local that may write to core through your local Core Wrapper instance. +- If you are unsure whether or not your local Core Wrapper instance is configured to run safely against production in a read only manager reach out to your manager before attempting to do so. +is for Query only, do not use any post endpoints + +### 3. Update WebApi Launch Settings As Needed + +Update your launchsettings.json in the WebApi project similar to sample provided bellow as to not collide with your own port configurations for local app run instances +```json +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:22532", + "sslPort": 44380 + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Local" + } + }, + "WepApi": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Local" + }, + "applicationUrl": "https://localhost:20001;http://localhost:20000" + } + } +} +```` + +### 4. Point IncentiveAPI to your local instance of CoreWrapper as needed. Ex: testing a change in core wrapper that will bring in more data for new feature update + +```json + "Http": { + "CoreWrapper": "http://localhost:5000", //<--<< Like this +``` +To execute actions from the swagger page you'll need to get a bearer token through the browser and enter it in the auth field on the swagger page +You will need to do this any time you are starting the application with a fresh database for the first time. +You can do this through either your local instance of the UI or you can use QA/UAT/PROD since the Azure AD auth token will not be tied to an environment. + +Auth token location in browser, copy value +![img.png](img.png) + +Paste value into here on the API Swagger page, be sure to chop off the highlighted front part "Bearer " +![img_1.png](img_1.png) + +You can now run an initial employee sync through swagger to get up and running +![img_2.png](img_2.png) + +### Application +This contains the service layer and repositories with business logic. + +### Domain +This contains the domain bounded entities that are saved to the DB, as well as the models and DTO objects used +to link the service layer and the web api. + +### WebApi +This project implements the scaffolding and RESTful controllers by communicating with the services in the +application layer via models. + +## Vocabulary +* TODO + +## Departments +This is how multiple departments works. A user can be assigned to multiple departments, all department assignments have a start +and end date. They have a home department which cannot be changed except by changing their department in Day Force. Below is an explanation. + +Assumptions: +* A users "Home Department" can technically change, meaning it also has a start/end date. This is correct. +* A user can be in multiple floating/guest departments, and they also have start/end dates. This is correct. + +Problems: + +* If an activity arrives without a department, find the user's current department and credit that department? + * This is indeterminate - no way to determine department! +* If an activity arrives with a department, credit the user/department pair. +* If the user has more than one department covering the period, this points to crediting the department on the activity. + +Example: +If I'm covering someone at a branch and I cause activity there - three things that can happen. + +Credit the activity to the Associate and their home department. +Credit the activity to the Associate and their float department(s). +Credit the activity to the Associate and Department that came with the activity. + +Outcome: +If an activity does not include a department, then we have a problem. We should require activity to enter the system with an OPERATOR and DEPARTMENT (EX: Someone did it, somewhere) + +## Calculating Payouts +To calculate a payout your start with a person (Employee ID). Then you follow a series of data collection steps to build up the inputs so that you can +calculate payouts. + +### Gather +* Person - Employee ID +* Time Period - Usually a specific Month or Year +* Departments assigned to Employee Id for Time Period +* Commissions that apply to the person's role +* For The Time Period: + * Personal Goals for that person, per department + * Department Goals for that person, per department + * Activity for that person + * Activity for department, per department + +### Payout Profiles +The data involved in calculating a payout has no real parent or hierarchy to it. This means in order to create a database +row for this data, many of the rows look exactly the same except for one or two columns. When you include the exclusion of +creating a payout editor in the UI due to project time constraints; it didn't make sense to try and force non-tabular data +into a table. + +So the Payout Profiles were created; and they encompass the various way to pay a person for a given Product, Role, and range +of progress on a goal. These could in the future be moved to a document store like Mongo, or moved into a tabular format give they +won't be edited directly by a human - which means creating a UI based payout editor. + +In the short to medium term, the Payout Profile provides a readable and testable compromise for tracking how a product, role, progress, +amount combination pays out for a given situation. + +### Calculate +* For each Commission, calculate progress based on activities, this gives commission payout. +* For each Goal, calculate Target Vs Actual (progress) based on activities. +* For each Goal, select a payout calculator for the Goal Product and Person Role. +* For each Goal, calculate payout. + +At the end of the process you should have person, departments, goals, commissions, activities, progress, payout rules, and payouts for a time period. + +## Day Force +Had to make some adjustments to the extract criteria condition orders after adding the 365 days for term date requirement. +You may see records that are an active status but have a term date. This is a situation where the employee has been rehired, and as +such the hire date will show as a date later than the term date. That is just how the fields are used in Dayforce. + +## Mapping Fields From Day Force +How do I determine business line for a department? +- If it has branch in the full name, set business line to branch +- Otherwise use Member Services + +How do I determine branches? +- Split on "-" +- If you see branch then the department is what is on the left of the "-" +- If you see Headquarters then the department is what is listed on the right of the "-" + +## Syncing Activities +### The ProcessorHistoryTable +The ProcessorHistory Database table holds a the last successful runtime of all importers as well as a 'Bookmark' of the last entry in an external system that was +processed. There are two fields `[LastProcessedRecordId]` and `[LastProcessedRecordTimestamp]`. One or both of these can be used to query the latest entries in an external system. +The fields that are used for each process are documented in the 'Go-live' section of this document. + +These values have to be seeded on go live. If the values for all processes are not seeded, then activities related to tha process will not be imported. + +**Why don't you fall back to 0?** + +There could potentially be millions of records in DNA by the time this project goes live. The API +serving this information sometimes times out after only hundreds of thousands of records. + +**Why don't you fall back to the start of the day/month/year?** + +Requiring manual entry, enables the system to start exactly where it needs to be at go-live. + +### Seeding the Database For DNA +Syncing activities from External Systems is done via a hosted service and the DnaActivitySyncService. The process determines +where in DNAs activity database to begin its search using data from the ProcessorRunHistory table. On it's first run, this +database will need to be seeded with a pre-determined activity number. Every process can be set to the same activity number as after the first run +will settle into their respective place in DNAs activity table. + +## Go-Live Tasks +### Seeding the Database +The ProcessorHistory table keeps a 'Bookmark' of which activities have already been processed in any external system. At go live the business will have to determine where +in any given external system we should start searching from. Keep in mind that starting too far in the past could overwhelm any API that you are calling. +|Process|MostSignificantBookmarkField|What to populate it with| +|--|--|--| +ImportAtmCheckCardSalesFromDna | LastProcessedRecordTimestamp| An ActvDateTime for an Activity in the OsiBank.ACTV table. +ImportCheckingAccountSalesFromDna| LastProcessedRecordTimestamp| An ActvDateTime for an Activity in the OsiBank.ACTV table. +ImportDpEligibleLoanSalesFromDna| LastProcessedRecordTimestamp| An ActvDateTime for an Activity in the OsiBank.ACTV table. +ImportMortgageSalesFromDna| LastProcessedRecordTimestamp| An ActvDateTime for an Activity in the OsiBank.ACTV table. +ImportVehicleSalesFromDna| LastProcessedRecordTimestamp| An ActvDateTime for an Activity in the OsiBank.ACTV table. +ImportActivityManagerReferralsFromDna| LastProcessedRecordId| An ID for an Activity in the ActivityManager.LF_LEAD table. + +****Note that the LastProcessedRecordTimestamp field is save as 'Unspecified' no conversions should be used on it. This is so it represents the exact timestamp as a string that is in its source system.** + + +## Feature Management +This application uses Microsoft's FeatureManagement package to enable/disable features in the code. + +This is initialized in startup like so: +`services.AddFeatureManagement();` + +The feature flags are configured at their default location in any of the appsettings: + +"FeatureFlags" : { "EnableControllers" : true, "RunHostedServices": false }After adding an item to the config it is suggested to add the Flag's string to the **FeatureFlag.cs** static class under the configuration folder of the main project. + +Feature flags can be used in `[FeatureGate(*nameOfFlag*)]` attributes in controllers to enable and disable them dynamically by changing the config: + +```c# +namespace LCU.API.Collections.RepoApi.Controllers +{ + [Route("api/[controller]")] [ApiController] [FeatureGate(FeatureFlags.EnableControllers)] public class RefundStatusController : RepoControllerBase { +``` +After injecting IFeatureManagement they can also be used inline to prevent entire bloks of code from running: + +```c# + if (await featureManager.IsEnabledAsync(FeatureFlags.FeatureA)) { /* Run the following code */ } +``` + + +Learn more about Microsoft.FeatureManagement : +(https://docs.microsoft.com/en-us/azure/azure-app-configuration/use-feature-flags-dotnet-core?tabs=core5x) + +Learn more about the history and use cases of Feature Management: +(https://martinfowler.com/articles/feature-toggles.html) + +## Ef Migration +This project currently uses EF Migration to handle setting up the database instead of a DACPAC. The files efmigrate.ps1 and efupdate.ps1 contain sample instructions +that you can use, and will continue to be updated. It's important to note which environment you want to run against, as well as making sure that inMemory = false. + +Within the code, this migration is called with ```dbContext.Database.Migrate()``` in startup.cs + +You can read about EF Migrations here: https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli + + +## Important Tags +The following are tags for historic changes in the codebase that may be of interest to future developers +|Tag|Reason| +|--|--| + +## Developer Notes +### Goal Month Ranges +A Goal's Month Range in the IncentiveAPI run from a Start DateTime (_midnight on the first of the month_) to an End DateTime (_One second before midnight the next month_). +```c# + goal.StartDate = DateTime.Now(); goal.EndDate = DateTime.Now().AddMonth(1).AddSeconds(-1); //Avoid AddMilliseconds(-1) as this can cause issues matching table entries down-stream. +``` +Whenever comparing a Goal's Month Range to another Start DateTime or End DateTime to mitigate any issues that could arise from differing timestamps. +```c# + if(goal.StartDate.Date == otherGoal.StartDate.Date){ /*...*/ } +``` + +# TimeZones +Per **ADR_0006** when a DateTime enters the system without a specified TimeZone it should be converted to UTC using the sources system's assumed timezone. +```c# +//Convert Uspecified DateTime to UTC by first defining the timezone as Central Time +TimeZoneInfo AssumedSourceTimezone = TimeZoneConverter.TZConvert.GetTimeZoneInfo("America/Chicago"); +SourceCreated = TimeZoneInfo.ConvertTime(ActivityDateTime, AssumedSourceTimezone, TimeZoneInfo.Utc), +``` +Note, there are 'Windows' Timezone Id strings and 'Linux' Timezone ID strings. The TimeZoneConverter nuget package allows us to use either interchangeably. Linux Type strings are preferred as it matches our hosting evnironment + +Note, if the DateTime coming in is intended to be DATE only, we do not do any explicit conversion. Leave it uncasted. + +#### Assumed Timezone Chart + +|Source System| Assumed Timezone | Activities (may be incomplete)| +|--|--|--| +| Dna OSIBANK Database| America/Chicago | `New Checking` `New ATM Card` `New Check Card` `Debt Protection Life` `Debt Protection Diability ` `Credit Card Sale`| +| Dna Osibank Database - Loans| *None Date Only* | `Mortgage Loan` `Vehicle Loan` +|Dna ActivityManager Database|America/Chicago| `Direct Deposit Referral` `Vehicle Referral` `Mortgage Referral` `New Checking Referral` `Credit Card Balance Transfer Referral` `ATM Card Referral` `Check Card Referral`| +|Allied GAP and MBP File| *None Date Only* | `GAP Sale` `MBP sale`| +|Indirect Vehicle Sale Report| *To Be Determined* | `Indirect Vehicle Referral`| + +## DNA Activity Process +Here is the basic way an activity enters and saves in Activity Manager +**For DNA:** +1. A Processor Runs and grabs new ExternalActivities for a particular product +2. For all External Activities we use a ExternalActivity Strategy to Map it to an ActivityManager Activity + 1. The most basic mapping can be found in each External Activity Model `ExternalActivity.MapToActivity()` + 2. The External Activity Strategy attempts to look up any values that our system holds the truth for + 1. Employee Information + 2. Department Information + 3. All Activity Arrives in the 'Created State' +3. This Incentive Manager Activity is Issued to its Product/Type topic to the Queue +4. An Activity Arrives from the Queue and We immediately try to save it + 1. The save will fail if we are missing required data and a Dead Activity is Created +5. If the save is successful we will then use the product/types AutoDecisionStrategy to try to update the state based on the current Activity Data + 1. If the decisioning fails the state will not be updated, and in the next morning we will retry decisioning on all created activities + +Notes: +- Some activities that are saved to 'ManualReview' also have processes that attempt to update them to other states. These processors run every morning. +- File Processors work slightly differently (they have file rows not external activities). They enter the system differently and typically only consistently use step 4 and 5. + - These activities still get saved to the dead activity queue if they are incomplete + - These activities still immediately get AutoDecisisoned after saving + + +## Audit Record Information: +Hi Richard, + +ActivityTransfer (and Activity Status Changes in general) can be found in the following table: ActivityStateChanges +The ActivityTransferAction table holds the current request status (if both parties have accepted the transfer, which dictates if the state should be changed). + +If an Activity is pending transfer its state will be ‘PendingTransfer’ if the transfer is accepted its state will be ‘Transferred’. If the Transfer has been rejected the state will return to what it was before the ‘PendingTransfer’ request. + +An example query to show an accepted transfer in QA is: +SELECT * FROM [IncentiveManager].[dbo].[ActivityStateChanges] WHERE ActivityId='28603' + +An example query to show a rejected transfer in QA is: +SELECT * FROM [IncentiveManager].[dbo].[ActivityStateChanges] WHERE ActivityId= ‘35769’ + +Activity Approval – can be found in the ActivityStateChanges table as well and will have the status ‘Accepted’ + +These can come from automated acceptance criteria in the system such as the following example: + +SELECT * FROM [IncentiveManager].[dbo].[ActivityStateChanges] WHERE ActivityId='484'. + +They can also come from manual acceptance by employees which the following query is an example of: +SELECT *FROM [IncentiveManager].[dbo].[ActivityStateChanges] WHERE ActivityId='636' + +The ActivityStateChanges table has a ‘Notes’ field that captures various details on why the change occurred – ie. A sale occurred, an account was opened, etc. + +Permissions Change – the permissions changes can be found in the UserPermissionHistory table which details if an extra permission was added to a specific user (separate from their current role permissions). + +The PermissionAdded column dictates if a permission was added via the Boolean 0/1 and the PermissionRemoved column dictates if a permission was removed via the Boolean 0/1 – both cannot be set per a row. The Permission column holds the string literal for the permission that was added/removed, and the CreatedBy row holds who assigned the permission (currently the Source will always be ‘Manual Assignment’ and the CreatedByType will always be 0 as the system doesn’t assign permissions. + +Example Query: +SELECT * FROM [IncentiveManager].[dbo].[UserPermissionHistory] Where EmployeeId = ‘11748’ + +Department Change on A User – These can be found in the DeptAssignments table. If a user gets assigned to a new Department, it will be added to this table + +Role Change on a user as well as Payout Percentage Change (SlidingPayout) (Role, SlidingPayout, EndDate) – if any of these currently supported changes occur to a user’s role assignments at a department it will be listed in the UserDepartmentHistory table. The table captures the UserDeparmentId a ForeignKey to the DepartmentAssignments Table for that unique assignment, the ‘FieldName’ (Role, SlidingPayout, EndDate) that changed, the ‘OldValue’ the ‘NewValue’ and the ‘CreatedBy’ – who made the change, and ‘Created’ when the change occurred. + +Example Query: +SELECT * FROM [IncentiveManager].[dbo].[UserDepartmentHistory] Where UserDepartmentId='1209' + +DepartmentGoalChange and Personal Goal Change - can be found in the [GoalChanges] table. If a PersonId is present then the change was for a Person for the specific DepartmentId, if a PersonId is null then the change was for the entire Department + +### Revised Changes - 8/2024 + +To prepare for Azure Kubernetes migration the LCU.API.Operations.IncentiveAPI repository has been changed to include: + - Follow design patterns as a guide url for reference: https://landmarkcu.visualstudio.com/Platform/_git/LCU.Platform.PatternTemplates + - Removal of Projects that were used to help load data from XP2 before DNA launch. (DnaDayforceDataExtract, LegacyIncentiveImporter) + - Follow project layer structure and naming closely to design patterns + - Removal of EF migration for table defintion, replaced with dacpac + - Update versions to support net 6.0 + - Added LCU nuget packages LcuHttpClient, LcuLogging, LcuStatusHealthCheck + - Remove some of the seeding logic, new fixed entries should be scripted + - Revise some configuration classes to populate with implementation + - Revise/add some microservices to call Data Access object classes + +### Selected Comments Removed from previous version - 8/2024 + +Authorization\RequirePermissionAttribute.cs + Require permissions on a method + - Comma separated list of allowed permission codes + +Configuration\FeatureFlags.cs + - These feature flags should match what is in the appsettings under "FeatureFlags" to prevent Magic Strings. + +Startup + - https://docs.microsoft.com/en-us/azure/azure-app-configuration/use-feature-flags-dotnet-core?tabs=core5x + - This is not the right way to do feature flags, as this cannot be dynamically changed after startup. + Until we add IFeatureManager to the queue lib, we will be unable to disable the HostedTaskScheduler + With feature flags. + +ActivityController.cs + - Detect user here with UserService. May need to pass user in from client in the future + +DataIntegrityException.cs + - Exception to be used in tasks run to verify the integrity of the data in the database + - This was added following the application wide transition of AppUsers from using a composite key of EmployeeId + Email to a GUID + - Ideally this will become obsolete and marked for removal in the future once all data linking is strictly enforced to use the new Ids + +DeadActivityExpection.cs + - Throw this exception when an activity is missing required data to enter the system normally. + - So far this is DepartmentId or EmployeeId. Use the message from this exception to save a 'note' in the Dead Activity entry. + +NotAvailableDecisionStrategy.cs + - If there is no decision strategy available for a product/activity type combination (ex Check Card Referral), then this is thrown. + - You may have added a new type of product and forgotten to create a new strategy or register an existing strategy. See the services/autodecision folder. + +ServerErrorException.cs + - Do not want to expose database and related exceptions to the controllers / web layer. + - The lower layers log what happened for developers, then return a general error; unless a more specific error can be returned in a way to allow the client to act (such as NotFound). + +AlliedSaleRow.cs + - Represents a row in the CSV file provided by Allied for downloading. Rows represents GP and MBP sales. + - Contacts Used: bryce.elmore@alliedsolutions.net, posdeliverysystems@alliedsolutions.net + - Contacts Used: john.urbanski@alliedsolutions.net, trina.mckenzie@alliedsolutions.net + - These constants are from the Allied CSV files, and the value should only be MBP or GAP + +DateTimeService.cs + - Convert DNA TimeStamp that has unspecified timezone to UTC by assuming DNA Time is in America/Chicago TZ + - Some Timestamps from DNA may come with Timezone Data, + +DefaultPayoutStrategy.cs + - The employee at this point may have multiple payouts for a product. For example they may have a personal payout for mortgage, and a department payout for mortgage. (MSC Team Lead) + - For each eligible payout, you may need to look at either department level goals or personal. + +PayoutCalculateService.cs + - Gathers goals, activity, payout profiles, and commissions for an employee-department pair for a specified time period and calculates payouts providing an audited view. + - Calculates Commission line items for a set of gathered department payout data and user department + - Would like to restrict this to products they have activities for, but that is hard with gap using other product types/etc. + +DashboardService.cs + - In order to not mix in the goal and activity repositories and keep the dependency tree clean, this function was moved to a new service + - To Do: DashboardService - Serve up report like structures for displaying information. + +DnaActivitySyncService.cs + - TODO: This ought to be converted to UTC, No way to know what to/from though, as this is source dependent + +EStatementService.cs + - We use LastSuccessfulRunTime instead of LastProcessedRecordId because delivery methods are not captured by activity number. + +GoalService.cs + - There is potential for some unexpected behaviour in this method if you try to save a department goal and a personnel goal for a employee in the department at the same time. + - The 'resulting' goal for that person will depend on which goal change is processed last. + - The client should not allow this behaviour, so we will not do anything about it here. This may be a future enhancement. + +UserService.cs + - To Do: check into Role Deprecation + +### Revised Changes - 1/2025 + +Mass Transit Changes + - Attempt to utilize LCU.ServiceFactory pattern to handle multiple DNAActivity processes + - Upgrade MassTransit from RealTime Queue logic + - Remove Hosted Services including TopicHostedServices, replace with services that can be scheduled in Dispatcher. + +Configuration Changes + - Moved and Renamed select configurations to follow design patterns more closely. + - Some of the File operations for Action, Allied processes were removed to be handled in Dispatcher. + +DateTimeExtensions.cs + * Comments Removed + Start of Month + - Midnight on the First of the Month' for any Given DateTime. + - The first second of a given DateTime's month. DateTime for same year and month, but at day 1 time 00:00:00 + End of Month + - One second before Midnight the next month' for any Given DateTime. + - The Final Second of a given DateTime's month. DateTime for same year and month, but on the last day (28,29,30,31) at time 12:59:59 + +NewAtmCheckCardActivity.cs + * Comments Removed + - ATM and Check Cards are 'Other' Type. They should only cause referrals to pay out and not pay out on their own. + +DateTimeService.cs + * Comments Removed + Method getNextBonusPeriod + - From Ben: The logic is that it will pay on the second paycheck in the month following the incentives being finalized. + - Landmark pays every 2nd week on Friday. + - The seed date must be a payday. + Method PayPeriodInMonth + - Given a pay period in a month, which one is it. + +ActivityDecisionService.cs + * Comments Removed + Method UpdateAndDecideDpEligibleLoanActivities + - TODO: Add paid out filter to this method + +ActivityService.cs + * Comments Removed + Method SaveActivityAsync + - Check for employee ID first as it can be required to find departmentId + Method LogGapInformation + - BE21 accounts typically contain Inc or Llc best measure to detect we don't save minor type + Method GetActivitiesAsync + - Get all activities from the repository. Can Take a while to run. + - If you are sending this to the client, it is highly recommended that you use the Paginated Version of this method ListByFilterAsync(). + Method GetDepartmentActivityForMonthRangeAsync + - Determine when the month range provided overlaps with the month range the user was assigned to this department + Method ValidateCanApplyActivityStateChange + - New accepted date must be restricted to only months that have not paid out or one month from current date + Method GetActivityAuthorizedCommands + - Determine what a user can do to an activity, this would in an ideal system return the list of possible commands for the user :-/ + - But for now, it can still be awesome, it returns the state transitions the user can do. + +IUserService.cs + * Comments Removed + Method SyncDayForceEmployeeExisting + - You can have more than one home dept, but not more than one "active / open ended". + +DNAActivityServices.cs + * Topics/RealTime Queue Logic Removed + All DNAActivityServices + - DNA Activity Services should be running every 10 minutes, these can be separate Dispatcher jobs + - Messaging can be added to total the amount of activities received to replace old "New Topic Received" comments + - The previous version of DnaActivityService under a hosted ran a method to enqueue all 7 service types + - If there were activities found it would run the TopicHostedService which logged topic received and then called the old method ofHandleIncomingTopic + +DayforceClientService.cs + * Comments Removed + Method GetHttpResponseAsync + - Only if response 401, we are taking the request uri from the response of first request and completing the get. + +TO DO: (at later date): + - We would want to remove the custom service exceptions in a future upgrade + - Refactor DBApplicationIntegrity service to return all results. + - Use LcuHttpClient with Dayforce Get data method. Remove TrustingHttpClient + - May want to look into an alternate endpoint or solution to avoid redirect with Dayforce report data. + - Remove SeedRoles logic, find alternative solution. + - Remove SystemAppUserConstants class and make that a configuration. + - Reorganize and remove service classes from the Persistence namespace. Example: ApplicationDbIntegrityService.cs + - Remove try/catch from the DAO classes to ensure the service layer is handling the try/catch appropriately. + - Remove extra logic in repository classes and move to appropriate service layer. + - Example: ProcessorHistoryRepository.cs - if (newHistoryRecord.LastSuccessfulRunTime < existingProcess.LastSuccessfulRunTime) + diff --git a/LCU.API.Operations.IncentiveAPI/SampleQueries.txt b/LCU.API.Operations.IncentiveAPI/SampleQueries.txt new file mode 100644 index 0000000..587e12b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/SampleQueries.txt @@ -0,0 +1,324 @@ +/* + Get Goals with their GoalChanges + */ +USE IncentiveManager; + +DECLARE @DEPARTMENT_ID INT = 63; +DECLARE @START_DATE DATETIME2 = '2022-01-01 00:00:00.0000000'; + +/* + Goals are joined to the goal change table as seen below in the sample query + Get all department goals for @DEPARTMENT_ID along with their changes in descending order where the start date is @START_DATE + If no joining data is found in the goal change table for a goal it has not been changed since it was initially created + */ +SELECT * +FROM Goals g + LEFT JOIN GoalChanges gc ON g.DepartmentId = gc.DepartmentId + AND g.Product = gc.Product + AND g.StartDate = gc.StartDate + AND g.EndDate = gc.EndDate + AND COALESCE(g.EmployeeId, '') = COALESCE(gc.PersonId, '') +WHERE g.StartDate = @START_DATE + AND g.DepartmentId = @DEPARTMENT_ID + AND g.EmployeeId IS NULL + AND gc.PersonId IS NULL -- If g.EmployeeId and gc.PersonId are null then the goal and its changes belong to a department, not an individual user +ORDER BY gc.Created DESC; + +/* + Get all non home department payouts for @PRODUCT in @PAYOUT_PERIOD + */ + +USE IncentiveManager; + +DECLARE @PRODUCT VARCHAR(255) = 'GAPInsurance'; +DECLARE @PAYOUT_PERIOD DATETIME2 = '2022-06-01 00:00:00.0000000'; + +WITH MultiplePayoutEmployees AS + (SELECT pli.EmployeeId, pli.ActivityPeriod, COUNT(pli.EmployeeId) AS PayoutCount + FROM PayoutLineItems pli + WHERE pli.Product = @PRODUCT + GROUP BY pli.EmployeeId, pli.ActivityPeriod + HAVING COUNT(pli.EmployeeId) > 1) +SELECT DISTINCT au.FullName, pli.* +FROM MultiplePayoutEmployees mpe + LEFT JOIN PayoutLineItems pli ON mpe.EmployeeId = pli.EmployeeId AND mpe.ActivityPeriod = pli.ActivityPeriod + LEFT JOIN AppUser au ON pli.EmployeeId = au.EmployeeId + LEFT JOIN DeptAssignments da ON pli.EmployeeId = da.EmployeeId + AND pli.DepartmentId = da.DepartmentId + AND @PAYOUT_PERIOD >= da.StartDate + AND (da.EndDate IS NULL OR @PAYOUT_PERIOD <= da.EndDate) +WHERE pli.Product = @PRODUCT + AND pli.ActivityPeriod = @PAYOUT_PERIOD + AND da.IsHome = 0 +ORDER BY pli.EmployeeId; + +/* + Delete all non home department payouts for @PRODUCT in @PAYOUT_PERIOD + */ +BEGIN TRANSACTION; + +-- COMMIT TRANSACTION; +-- ROLLBACK TRANSACTION; + +USE IncentiveManager; + +DECLARE @PRODUCT VARCHAR(255) = 'GAPInsurance'; +DECLARE @PAYOUT_PERIOD DATETIME2 = '2022-06-01 00:00:00.0000000'; +DECLARE @TARGET_PAYOUT_IDS TABLE(ID INT); + +SET NOCOUNT ON; -- Suppress unwanted count on var table insert +WITH MultiplePayoutEmployees AS +(SELECT pli.EmployeeId, pli.ActivityPeriod, COUNT(pli.EmployeeId) AS PayoutCount +FROM PayoutLineItems pli +WHERE pli.Product = @PRODUCT +GROUP BY pli.EmployeeId, pli.ActivityPeriod +HAVING COUNT(pli.EmployeeId) > 1), +TargetPayoutIds AS (SELECT pli.Id +FROM MultiplePayoutEmployees mpe +LEFT JOIN PayoutLineItems pli ON mpe.EmployeeId = pli.EmployeeId AND +mpe.ActivityPeriod = pli.ActivityPeriod +LEFT JOIN AppUser au ON pli.EmployeeId = au.EmployeeId +LEFT JOIN DeptAssignments da ON pli.EmployeeId = da.EmployeeId +AND pli.DepartmentId = da.DepartmentId +AND @PAYOUT_PERIOD >= da.StartDate +AND (da.EndDate IS NULL OR @PAYOUT_PERIOD <= da.EndDate) +WHERE pli.Product = @PRODUCT +AND pli.ActivityPeriod = @PAYOUT_PERIOD +AND da.IsHome = 0) +INSERT INTO @TARGET_PAYOUT_IDS SELECT Id FROM TargetPayoutIds; +SET NOCOUNT OFF; -- Re enable count after insert for DBA DELETE rows affected verification + +-- SELECT * FROM @TARGET_PAYOUT_IDS; +-- SELECT * FROM ActivityPayouts WHERE PayoutLineItemId IN (SELECT * FROM @TARGET_PAYOUT_IDS); +-- SELECT * FROM PayoutLineItems WHERE Id IN (SELECT * FROM @TARGET_PAYOUT_IDS); + +DELETE FROM ActivityPayouts WHERE PayoutLineItemId IN (SELECT * FROM @TARGET_PAYOUT_IDS); +DELETE FROM PayoutLineItems WHERE Id IN (SELECT * FROM @TARGET_PAYOUT_IDS); + +/* + Delete all duplicate goals for @BRANCH in @PAYOUT_PERIOD + */ + +-- (20 rows affected) +BEGIN TRANSACTION; + +-- COMMIT TRANSACTION; +-- ROLLBACK TRANSACTION; + +USE IncentiveManager; + +DECLARE @BRANCH VARCHAR(255) = '11'; +DECLARE @PAYOUT_PERIOD DATETIME2 = '2022-08-01 00:00:00.0000000'; + +WITH MultipleGoalEmployees AS +( +SELECT g.EmployeeId, g.StartDate, g.Product, COUNT(g.EmployeeId) AS GoalCount +FROM Goals g +WHERE g.StartDate = @PAYOUT_PERIOD +GROUP BY g.EmployeeId, g.StartDate, g.Product +HAVING COUNT(g.EmployeeId) > 1 +), +TargetItems AS( + SELECT au.FullName, ROW_NUMBER() OVER (PARTITION BY g.EmployeeId, g.StartDate, g.Product ORDER BY g.LastUpdated ASC) AS RowRank, g.* +FROM MultipleGoalEmployees mge +LEFT JOIN AppUser au ON mge.EmployeeId = au.EmployeeId +LEFT JOIN Goals g ON mge.EmployeeId = g.EmployeeId +AND mge.Product = g.Product +AND mge.StartDate = g.StartDate +WHERE g.DepartmentId IN (@BRANCH) +) +DELETE FROM Goals WHERE Id IN( +SELECT ti.Id FROM TargetItems ti +WHERE ti.RowRank = 1 +) + +/* + Sample Manual Data Update State Change Insert To Maintain Long Term Data Integrity + */ +INSERT INTO ActivityStateChanges (CreatedBy, Created, CreatedByType, ActivityId, State, Note, ActivityMatchId, AcceptedDate, CreatedByAppUserId) +VALUES ('Incentive Manager', GETUTCDATE(), 1, @TARGET_ACTIVITY_ID, 'Accepted', 'Auto-Decision: MBP Accepted', 0, @UPDATE_ACTIVITY_ACCEPTED_DATE, '00000000-0000-0000-0000-000000000000'); + +/* + Remove @TARGET_ACTIVITY_ID from system, this should only be a last resort for data issues since the system is supposed to be auditable +*/ + +USE IncentiveManager; + +DECLARE @TARGET_ACTIVITY_ID INT = 143613; + +BEGIN TRANSACTION; + +DELETE +FROM ActivityDetailUpdates +WHERE Id IN (SELECT adu.Id + FROM Activity a + LEFT JOIN ActivityDetail ad ON a.Id = ad.ActivityRef + LEFT JOIN ActivityDetailUpdates adu ON ad.Id = adu.ActivityDetailId + WHERE a.Id = @TARGET_ACTIVITY_ID); + +DELETE +FROM ActivityDetail +WHERE Id IN (SELECT ad.Id + FROM Activity a + LEFT JOIN ActivityDetail ad ON a.Id = ad.ActivityRef + WHERE a.Id = @TARGET_ACTIVITY_ID); + +DELETE +FROM dbo.ActivityMetaData +WHERE Id IN (SELECT amd.Id + FROM Activity a + LEFT JOIN ActivityMetaData amd ON a.Id = amd.ActivityRef + WHERE a.Id = @TARGET_ACTIVITY_ID); + +DELETE +FROM ActivityStateChanges +WHERE Id IN (SELECT ad.Id + FROM Activity a + LEFT JOIN ActivityStateChanges ad ON a.Id = ad.ActivityId + WHERE a.Id = @TARGET_ACTIVITY_ID); + +DELETE +FROM ActivityTransferActions +WHERE Id IN (SELECT ata.Id + FROM Activity a + LEFT JOIN ActivityTransferActions ata ON a.Id = ata.RequestedActivityId OR a.Id = ata.NewActivityId + WHERE a.Id = @TARGET_ACTIVITY_ID); + +DELETE +FROM PayoutLineItems +WHERE Id IN (SELECT pli.Id + FROM Activity a + LEFT JOIN ActivityPayouts ap ON a.Id = ap.ActivityId + LEFT JOIN PayoutLineItems pli ON ap.PayoutLineItemId = pli.Id + WHERE a.Id = @TARGET_ACTIVITY_ID); + +DELETE +FROM ActivityPayouts +WHERE ActivityId IN (SELECT ap.ActivityId + FROM Activity a + LEFT JOIN ActivityPayouts ap ON a.Id = ap.ActivityId + WHERE a.Id = @TARGET_ACTIVITY_ID); + +DELETE +FROM Activity +WHERE Id IN (SELECT a.Id + FROM Activity a + WHERE a.Id = @TARGET_ACTIVITY_ID); + +ROLLBACK TRANSACTION; +-- COMMIT TRANSACTION; + +/* + Sample Query To Manually Enter AppUser +*/ +DECLARE @USER_EMAIL VARCHAR(MAX) = 'EMPLOYEEEMAIL@LANDMARKCU.COM'; +DECLARE @USER_ID UNIQUEIDENTIFIER = NEWID(); + +INSERT INTO IncentiveManager.dbo.AppUser (Id, AdEmail, PersonId, EmployeeId, CreatedBy, Created, CreatedByType, LastUpdatedBy, LastUpdated, LastUpdatedByType, HomeDepartment, BaseRole, ManagerKey, Role, FullName, Terminated, CreatedByAppUserId, LastUpdatedByAppUserId) VALUES (@USER_ID,@USER_EMAIL, 0, N'X099', N'Incentive Manager', N'2021-10-16 02:01:19.2424619', 1, N'Incentive Manager', N'2022-09-14 23:50:39.2427930', 1, N'HEADQUARTERS - INFORMATION TECHNOLOGY', N'SOFTWARE ENG', N'11519', N'Admin', N'DEPRATT, ANDREW', null, N'00000000-0000-0000-0000-000000000000', N'00000000-0000-0000-0000-000000000000'); +INSERT INTO IncentiveManager.dbo.DeptAssignments (CreatedBy, Created, CreatedByType, LastUpdatedBy, LastUpdated, LastUpdatedByType, DepartmentId, StartDate, EndDate, Note, IsHome, Role, SlidingPayout, AppUserId, CreatedByAppUserId, LastUpdatedByAppUserId) VALUES (N'Incentive Manager', N'2021-10-16 02:01:19.2424607', 1, N'Incentive Manager', N'2022-09-14 23:50:39.2427934', 1, 2, N'2020-08-01 00:00:00.0000000', null, N'Auto Generated Home Assignment', 1, N'Admin', 100.00, @USER_ID, N'00000000-0000-0000-0000-000000000000', N'00000000-0000-0000-0000-000000000000'); + +/* + Update @TARGET_ACTIVITY_ID and associated payouts along with the final state change to have a different payout period and accepted date +*/ + +USE IncentiveManager; + +-- (1 row affected) +-- (1 row affected) +-- (1 row affected) +BEGIN TRANSACTION; +-- ROLLBACK TRANSACTION; +-- COMMIT TRANSACTION; + +DECLARE @NEW_ACTIVITY_PERIOD DATETIME2 = '2022-11-01 00:00:00.0000000'; +DECLARE @NEW_ACCEPTED_DATE DATETIME2 = '2022-11-29 12:00:00.0000000'; +DECLARE @TARGET_ACTIVITY_ID INT = 177281; + +UPDATE ActivityStateChanges SET AcceptedDate = @NEW_ACCEPTED_DATE +WHERE Id IN (SELECT TOP 1 ad.Id + FROM Activity a + LEFT JOIN ActivityStateChanges ad ON a.Id = ad.ActivityId + WHERE a.Id = @TARGET_ACTIVITY_ID + AND ad.State = 'Accepted' + ORDER BY ad.Created DESC); + +UPDATE PayoutLineItems SET ActivityPeriod = @NEW_ACTIVITY_PERIOD +WHERE Id IN (SELECT pli.Id + FROM Activity a + LEFT JOIN ActivityPayouts ap ON a.Id = ap.ActivityId + LEFT JOIN PayoutLineItems pli ON ap.PayoutLineItemId = pli.Id + WHERE a.Id = @TARGET_ACTIVITY_ID); + +UPDATE Activity SET Accepted = @NEW_ACCEPTED_DATE +WHERE Id IN (SELECT a.Id + FROM Activity a + WHERE a.Id = @TARGET_ACTIVITY_ID); + +-- SELECT * +-- FROM ActivityStateChanges +-- WHERE Id IN (SELECT TOP 1 ad.Id +-- FROM Activity a +-- LEFT JOIN ActivityStateChanges ad ON a.Id = ad.ActivityId +-- WHERE a.Id = @TARGET_ACTIVITY_ID +-- AND ad.State = 'Accepted' +-- ORDER BY ad.Created DESC); +-- SELECT * +-- FROM PayoutLineItems +-- WHERE Id IN (SELECT pli.Id +-- FROM Activity a +-- LEFT JOIN ActivityPayouts ap ON a.Id = ap.ActivityId +-- LEFT JOIN PayoutLineItems pli ON ap.PayoutLineItemId = pli.Id +-- WHERE a.Id = @TARGET_ACTIVITY_ID); +-- SELECT * +-- FROM Activity +-- WHERE Id IN (SELECT a.Id +-- FROM Activity a +-- WHERE a.Id = @TARGET_ACTIVITY_ID); + +/* + DELETE ActivityPayouts linked to @TARGET_ACTIVITY_ID so they can be recreated +*/ + +USE IncentiveManager; + +-- (1 row affected) +BEGIN TRANSACTION; +-- ROLLBACK TRANSACTION; +-- COMMIT TRANSACTION; + +DECLARE @TARGET_ACTIVITY_ID INT = 177281; + +DELETE PayoutLineItems +WHERE Id IN (SELECT pli.Id + FROM Activity a + LEFT JOIN ActivityPayouts ap ON a.Id = ap.ActivityId + LEFT JOIN PayoutLineItems pli ON ap.PayoutLineItemId = pli.Id + WHERE a.Id = @TARGET_ACTIVITY_ID); + +-- SELECT * +-- FROM PayoutLineItems +-- WHERE Id IN (SELECT pli.Id +-- FROM Activity a +-- LEFT JOIN ActivityPayouts ap ON a.Id = ap.ActivityId +-- LEFT JOIN PayoutLineItems pli ON ap.PayoutLineItemId = pli.Id +-- WHERE a.Id = @TARGET_ACTIVITY_ID); +-- SELECT * +-- FROM ActivityPayouts +-- WHERE ActivityId = @TARGET_ACTIVITY_ID; + +/* + UPDATE @TARGET_APPUSER_EMPLOYEE_ID to be @NEW_APPUSER_ROLE for local/QA dev +*/ + +DECLARE @TARGET_APPUSER_EMPLOYEE_ID VARCHAR(MAX) = '8675309'; +DECLARE @NEW_APPUSER_ROLE VARCHAR(MAX) = 'Admin'; + +UPDATE AppUser +SET Role = @NEW_APPUSER_ROLE +WHERE Id IN (SELECT au.Id FROM AppUser au WHERE au.EmployeeId IN (@TARGET_APPUSER_EMPLOYEE_ID)); +Update DeptAssignments +Set Role = @NEW_APPUSER_ROLE +WHERE Id IN (SELECT da.Id + FROM AppUser au + LEFT JOIN DeptAssignments da on au.Id = da.AppUserId + WHERE au.EmployeeId IN (@TARGET_APPUSER_EMPLOYEE_ID)); diff --git a/LCU.API.Operations.IncentiveAPI/azdo_pipelines/azure-k8s-pipeline.yaml b/LCU.API.Operations.IncentiveAPI/azdo_pipelines/azure-k8s-pipeline.yaml new file mode 100644 index 0000000..b47d203 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/azdo_pipelines/azure-k8s-pipeline.yaml @@ -0,0 +1,65 @@ +parameters: +- name: target_environment + displayName: Target Environment + type: string + default: qa + values: + - qa + - uat + - prd +- name: k8s_namespace + displayName: K8s Namespace to deploy to + type: string + default: landmarkcu + values: + - landmarkcu +variables: +- template: ./config-files/${{ parameters.target_environment }}-pvars.yaml +- name: source_branch_name + value: $[replace(replace(variables['Build.SourceBranch'], 'refs/heads/', ''), '/', '-')] +- name: docker_container_repository_name + value: landmarkcu/lcu.api.internal.incentivemanagerapi-incentivemanagerapi +resources: + repositories: + - repository: azdo_pipeline_templates + type: git + name: Infrastructure Platform/azdo-pipeline-templates + ref: main +stages: +# DOTNET BUILD AND NUGET PUSH +- template: dotnet-nuget-build-push.yaml@azdo_pipeline_templates + parameters: + azdo_agent_pool: $(pvar_azdo_agent_pool_nuget) + azure_artifacts_feed: 'LCU-Internal' +# DACPAC BUILD AND PUSH +- template: db-build-deploy.yaml@azdo_pipeline_templates + parameters: + azdo_agent_pool: $(pvar_azdo_agent_pool_db) + azdo_deployment_environment: $(pvar_azdo_deployment_environment_db) + sql_project_path: './LCU.API.Internal.Operations.IncentiveApiSql/LCU.API.Internal.Operations.IncentiveApiSql.sqlproj' + built_sql_dacpac_file_path: './LCU.API.Internal.Operations.IncentiveApiSql/bin/Release/LCU.API.Internal.Operations.IncentiveApiSql.dacpac' + name_of_artifact: LCU_API_Internal_Operations_IncentiveApiSql_dacpac + db_name: $(pvar_LCU_API_Internal_Operations_IncentiveApiSql_db_name) + sql_server_name: $(pvar_LCU_API_Internal_Operations_IncentiveApiSql_sql_server_name) +# DOCKER BUILD AND PUSH +- template: docker-build-push.yaml@azdo_pipeline_templates + parameters: + azdo_agent_pool: $(pvar_azdo_agent_pool) + docker_registry_service_connection: $(pvar_docker_registry_service_connection) + docker_container_repository_name: $(docker_container_repository_name) + dockerfile_path: ./Dockerfile + docker_image_tags: | + build-$(Build.BuildId) + $(source_branch_name) +# K8S DEPLOY +- template: k8s-deploy.yaml@azdo_pipeline_templates + parameters: + dependsOn: CI + condition: or(eq(variables['Build.SourceBranchName'], 'develop'), eq(variables['Build.SourceBranchName'], 'main')) + azdo_agent_pool: $(pvar_azdo_agent_pool) + kubectl_version: $(pvar_kubectl_version) + k8s_service_connection: $(pvar_k8s_service_connection) + k8s_namespace: ${{ parameters.k8s_namespace }} + k8s_kustomization_path: ./kustomize/overlays/${{ parameters.target_environment }} + docker_registry_service_connection: $(pvar_docker_registry_service_connection) + docker_image_to_deploy: $(docker_container_repository_name):build-$(Build.BuildId) diff --git a/LCU.API.Operations.IncentiveAPI/azdo_pipelines/azure-pipelines-feature-branches.yaml b/LCU.API.Operations.IncentiveAPI/azdo_pipelines/azure-pipelines-feature-branches.yaml new file mode 100644 index 0000000..ee44aef --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/azdo_pipelines/azure-pipelines-feature-branches.yaml @@ -0,0 +1,10 @@ +trigger: + branches: + include: + - '*' + exclude: + - 'main' + - 'develop' +extends: + template: azure-k8s-pipeline.yaml + diff --git a/LCU.API.Operations.IncentiveAPI/azdo_pipelines/azure-pipelines-prd.yaml b/LCU.API.Operations.IncentiveAPI/azdo_pipelines/azure-pipelines-prd.yaml new file mode 100644 index 0000000..7d13684 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/azdo_pipelines/azure-pipelines-prd.yaml @@ -0,0 +1,9 @@ +trigger: + branches: + include: + - 'main' +extends: + template: azure-k8s-pipeline.yaml + parameters: + target_environment: prd + k8s_namespace: landmarkcu diff --git a/LCU.API.Operations.IncentiveAPI/azdo_pipelines/azure-pipelines-qa.yaml b/LCU.API.Operations.IncentiveAPI/azdo_pipelines/azure-pipelines-qa.yaml new file mode 100644 index 0000000..f581dd4 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/azdo_pipelines/azure-pipelines-qa.yaml @@ -0,0 +1,9 @@ +trigger: + branches: + include: + - 'develop' +extends: + template: azure-k8s-pipeline.yaml + parameters: + target_environment: qa + k8s_namespace: landmarkcu diff --git a/LCU.API.Operations.IncentiveAPI/azdo_pipelines/azure-pipelines-uat.yaml b/LCU.API.Operations.IncentiveAPI/azdo_pipelines/azure-pipelines-uat.yaml new file mode 100644 index 0000000..4747234 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/azdo_pipelines/azure-pipelines-uat.yaml @@ -0,0 +1,9 @@ +trigger: + branches: + include: + - 'main' +extends: + template: azure-k8s-pipeline.yaml + parameters: + target_environment: uat + k8s_namespace: landmarkcu diff --git a/LCU.API.Operations.IncentiveAPI/azdo_pipelines/config-files/prd-pvars.yaml b/LCU.API.Operations.IncentiveAPI/azdo_pipelines/config-files/prd-pvars.yaml new file mode 100644 index 0000000..da4757d --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/azdo_pipelines/config-files/prd-pvars.yaml @@ -0,0 +1,12 @@ +--- +variables: + pvar_azdo_agent_pool: vmss-sub-df-lcu-apps-prd-lz-uscz + pvar_docker_registry_service_connection: Docker Registry LMCU + pvar_k8s_service_connection: svcon-sub-df-lcu-apps-prd-aks-k8s-uscz + pvar_kubectl_version: 1.25.9 + pvar_azdo_agent_pool_nuget: LCUOnPrem + pvar_azdo_agent_pool_db: LCUOnPrem + pvar_azdo_deployment_environment_db: DB-PRD + + pvar_LCU_API_Internal_Operations_IncentiveApiSql_db_name: IncentiveManager + pvar_LCU_API_Internal_Operations_IncentiveApiSql_sql_server_name: D1CLS01P diff --git a/LCU.API.Operations.IncentiveAPI/azdo_pipelines/config-files/qa-pvars.yaml b/LCU.API.Operations.IncentiveAPI/azdo_pipelines/config-files/qa-pvars.yaml new file mode 100644 index 0000000..df00bca --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/azdo_pipelines/config-files/qa-pvars.yaml @@ -0,0 +1,12 @@ +--- +variables: + pvar_azdo_agent_pool: vmss-sub-df-lcu-apps-qa-lz-uscz + pvar_docker_registry_service_connection: Docker Registry LMCU + pvar_k8s_service_connection: svcon-sub-df-lcu-apps-qa-aks-k8s-uscz + pvar_kubectl_version: 1.25.9 + pvar_azdo_agent_pool_nuget: LCUOnPrem + pvar_azdo_agent_pool_db: LCUOnPrem + pvar_azdo_deployment_environment_db: DB-QA + + pvar_LCU_API_Internal_Operations_IncentiveApiSql_db_name: IncentiveManager + pvar_LCU_API_Internal_Operations_IncentiveApiSql_sql_server_name: DAZCDTBWEB01Q diff --git a/LCU.API.Operations.IncentiveAPI/azdo_pipelines/config-files/uat-pvars.yaml b/LCU.API.Operations.IncentiveAPI/azdo_pipelines/config-files/uat-pvars.yaml new file mode 100644 index 0000000..63fe8eb --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/azdo_pipelines/config-files/uat-pvars.yaml @@ -0,0 +1,12 @@ +--- +variables: + pvar_azdo_agent_pool: vmss-sub-df-lcu-apps-uat-lz-uscz + pvar_docker_registry_service_connection: Docker Registry LMCU + pvar_k8s_service_connection: svcon-sub-df-lcu-apps-uat-aks-k8s-uscz + pvar_kubectl_version: 1.25.9 + pvar_azdo_agent_pool_nuget: LCUOnPrem + pvar_azdo_agent_pool_db: LCUOnPrem + pvar_azdo_deployment_environment_db: DB-UAT + + pvar_LCU_API_Internal_Operations_IncentiveApiSql_db_name: IncentiveManager + pvar_LCU_API_Internal_Operations_IncentiveApiSql_sql_server_name: D1CLS01U diff --git a/LCU.API.Operations.IncentiveAPI/build.sh b/LCU.API.Operations.IncentiveAPI/build.sh new file mode 100644 index 0000000..1bd205a --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/build.sh @@ -0,0 +1,12 @@ +#!/bin/bash +echo "Building Docker Image"; +dotnet clean +rm -rf WebApi/bin +rm -rf WebApi/obj +rm -rf Domain/bin +rm -rf Domain/obj +rm -rf Application/bin +rm -rf Application/obj +rm -rf Application.UnitTests/obj +rm -rf Application.UnitTests/bin +docker build -t local/incentive-api . diff --git a/LCU.API.Operations.IncentiveAPI/config-files/prd-pvars.yaml b/LCU.API.Operations.IncentiveAPI/config-files/prd-pvars.yaml new file mode 100644 index 0000000..19c41d9 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/config-files/prd-pvars.yaml @@ -0,0 +1,9 @@ +--- +variables: + pvar_azdo_agent_pool: vmss-sub-df-lcu-apps-prd-lz-uscz + pvar_docker_registry_service_connection: Docker Registry LMCU + pvar_k8s_service_connection: svcon-sub-df-lcu-apps-prd-aks-k8s-uscz + pvar_kubectl_version: 1.25.9 + pvar_azdo_agent_pool_nuget: LCUOnPrem + pvar_azdo_agent_pool_db: LCUOnPrem + pvar_azdo_deployment_environment_db: DB-PRD diff --git a/LCU.API.Operations.IncentiveAPI/config-files/qa-pvars.yaml b/LCU.API.Operations.IncentiveAPI/config-files/qa-pvars.yaml new file mode 100644 index 0000000..4c72616 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/config-files/qa-pvars.yaml @@ -0,0 +1,9 @@ +--- +variables: + pvar_azdo_agent_pool: vmss-sub-df-lcu-apps-qa-lz-uscz + pvar_docker_registry_service_connection: Docker Registry LMCU + pvar_k8s_service_connection: svcon-sub-df-lcu-apps-qa-aks-k8s-uscz + pvar_kubectl_version: 1.25.9 + pvar_azdo_agent_pool_nuget: LCUOnPrem + pvar_azdo_agent_pool_db: LCUOnPrem + pvar_azdo_deployment_environment_db: DB-QA diff --git a/LCU.API.Operations.IncentiveAPI/config-files/qa-pvars.yml b/LCU.API.Operations.IncentiveAPI/config-files/qa-pvars.yml new file mode 100644 index 0000000..fe36f36 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/config-files/qa-pvars.yml @@ -0,0 +1,4 @@ +--- +variables: + pvar_azdo_agent_pool: vmss-sub-df-lcu-apps-qa-lz-uscz + pvar_docker_registry_service_connection: Docker Registry LMCU diff --git a/LCU.API.Operations.IncentiveAPI/config-files/uat-pvars.yaml b/LCU.API.Operations.IncentiveAPI/config-files/uat-pvars.yaml new file mode 100644 index 0000000..95955c0 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/config-files/uat-pvars.yaml @@ -0,0 +1,9 @@ +--- +variables: + pvar_azdo_agent_pool: vmss-sub-df-lcu-apps-uat-lz-uscz + pvar_docker_registry_service_connection: Docker Registry LMCU + pvar_k8s_service_connection: svcon-sub-df-lcu-apps-uat-aks-k8s-uscz + pvar_kubectl_version: 1.25.9 + pvar_azdo_agent_pool_nuget: LCUOnPrem + pvar_azdo_agent_pool_db: LCUOnPrem + pvar_azdo_deployment_environment_db: DB-UAT diff --git a/LCU.API.Operations.IncentiveAPI/copy-test-results.sh b/LCU.API.Operations.IncentiveAPI/copy-test-results.sh new file mode 100644 index 0000000..23989f6 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/copy-test-results.sh @@ -0,0 +1,6 @@ +#!/bin/bash +echo "Copy Test Results Out Of Temporary Container"; +CONTAINER_ID=`docker images --filter "label=test=true" -q | awk '{ print $1 }'` +docker create --name testcontainer $CONTAINER_ID +docker cp testcontainer:/testresults ./testresults +docker rm testcontainer diff --git a/LCU.API.Operations.IncentiveAPI/img.png b/LCU.API.Operations.IncentiveAPI/img.png new file mode 100644 index 0000000..dc8d6fa Binary files /dev/null and b/LCU.API.Operations.IncentiveAPI/img.png differ diff --git a/LCU.API.Operations.IncentiveAPI/img_1.png b/LCU.API.Operations.IncentiveAPI/img_1.png new file mode 100644 index 0000000..943d41e Binary files /dev/null and b/LCU.API.Operations.IncentiveAPI/img_1.png differ diff --git a/LCU.API.Operations.IncentiveAPI/img_2.png b/LCU.API.Operations.IncentiveAPI/img_2.png new file mode 100644 index 0000000..3173e6d Binary files /dev/null and b/LCU.API.Operations.IncentiveAPI/img_2.png differ diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/base/Deployment.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/base/Deployment.yaml new file mode 100644 index 0000000..1426cc3 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/base/Deployment.yaml @@ -0,0 +1,98 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: incentive-manager-api +spec: + replicas: 1 + selector: + matchLabels: + app: incentive-manager-api + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + labels: + app: incentive-manager-api + spec: + containers: + - name: incentive-manager-api + image: incentive-manager-api + resources: + limits: + memory: "10Gi" + requests: + memory: "10Gi" + cpu: "40m" + ports: + - containerPort: 8080 + livenessProbe: + initialDelaySeconds: 5 + periodSeconds: 5 + httpGet: + path: /api/status + port: 8080 + readinessProbe: + initialDelaySeconds: 5 + periodSeconds: 5 + httpGet: + path: /api/status + port: 8080 + volumeMounts: + - name: volume-lcu-internal-cert + mountPath: /etc/ssl/certs/lcu-internal-cert.pem + subPath: lcu-internal-cert.pem + - name: volume-appsettings + mountPath: /app/config/mapped-appsettings + - name: incentive-manager-api-secrets + mountPath: /vault/secrets/incentive-manager-api-mass-queue-password.json + subPath: incentive-manager-api-mass-queue-password + readOnly: true + - name: incentive-manager-api-secrets + mountPath: /vault/secrets/incentive-manager-api-mass-queue-username.json + subPath: incentive-manager-api-mass-queue-username + readOnly: true + - name: incentive-manager-api-secrets + mountPath: /vault/secrets/incentive-manager-api-dayforce-username.json + subPath: incentive-manager-api-dayforce-username + readOnly: true + - name: incentive-manager-api-secrets + mountPath: /vault/secrets/incentive-manager-api-dayforce-password.json + subPath: incentive-manager-api-dayforce-password + readOnly: true + - name: incentive-manager-api-secrets + mountPath: /vault/secrets/incentive-manager-api-seq-api-key.json + subPath: incentive-manager-api-seq-api-key + readOnly: true + - name: incentive-manager-api-secrets + mountPath: /vault/secrets/incentive-manager-api-db-connection.json + subPath: incentive-manager-api-db-connection + readOnly: true + - name: incentive-manager-api-secrets + mountPath: /vault/secrets/incentive-manager-api-azure-valid-audience.json + subPath: incentive-manager-api-azure-valid-audience + readOnly: true + - name: incentive-manager-api-secrets + mountPath: /vault/secrets/incentive-manager-api-azure-client-id.json + subPath: incentive-manager-api-azure-client-id + readOnly: true + - name: incentive-manager-api-secrets + mountPath: /vault/secrets/incentive-manager-api-azure-tenant-id.json + subPath: incentive-manager-api-azure-tenant-id + readOnly: true + volumes: + - name: incentive-manager-api-secrets + csi: + driver: secrets-store.csi.k8s.io + readOnly: true + volumeAttributes: + secretProviderClass: incentive-manager-api-csi-key-vault-secrets-provider + - name: volume-lcu-internal-cert + configMap: + name: configmap-lcu-internal-cert + - name: volume-appsettings + configMap: + name: incentivemanagerapi-configmap diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/base/Service.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/base/Service.yaml new file mode 100644 index 0000000..6e531ea --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/base/Service.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: incentive-manager-api +spec: + selector: + app: incentive-manager-api + ports: + - port: 8080 + targetPort: 8080 diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/base/kustomization.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/base/kustomization.yaml new file mode 100644 index 0000000..9bde637 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/base/kustomization.yaml @@ -0,0 +1,5 @@ +resources: + - Deployment.yaml + - Service.yaml + - virtual-service.yaml + - secrets-provider-class.yaml diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/base/secrets-provider-class.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/base/secrets-provider-class.yaml new file mode 100644 index 0000000..d181fa3 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/base/secrets-provider-class.yaml @@ -0,0 +1,49 @@ +--- +apiVersion: secrets-store.csi.x-k8s.io/v1 +kind: SecretProviderClass +metadata: + name: incentive-manager-api-csi-key-vault-secrets-provider +spec: + provider: azure + parameters: + usePodIdentity: "false" + useVMManagedIdentity: "true" + tenantId: 63cd1554-2783-4a86-ae50-b9e6c21c9499 + objects: | + array: + - | + objectName: incentive-manager-api-azure-tenant-id + objectType: secret + objectVersion: "" + - | + objectName: incentive-manager-api-azure-client-id + objectType: secret + objectVersion: "" + - | + objectName: incentive-manager-api-azure-valid-audience + objectType: secret + objectVersion: "" + - | + objectName: incentive-manager-api-db-connection + objectType: secret + objectVersion: "" + - | + objectName: incentive-manager-api-seq-api-key + objectType: secret + objectVersion: "" + - | + objectName: incentive-manager-api-dayforce-username + objectType: secret + objectVersion: "" + - | + objectName: incentive-manager-api-dayforce-password + objectType: secret + objectVersion: "" + - | + objectName: incentive-manager-api-mass-queue-username + objectType: secret + objectVersion: "" + - | + objectName: incentive-manager-api-mass-queue-password + objectType: secret + objectVersion: "" diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/base/virtual-service.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/base/virtual-service.yaml new file mode 100644 index 0000000..2ab04bb --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/base/virtual-service.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: incentive-manager-api +spec: + gateways: + - istio-system/ingress-gateway + http: + - name: incentive-manager-api + match: + - uri: + prefix: / + route: + - destination: + host: incentive-manager-api + port: + number: 8080 diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/local/common/kustomization.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/local/common/kustomization.yaml new file mode 100644 index 0000000..d34b17d --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/local/common/kustomization.yaml @@ -0,0 +1,8 @@ +resources: + - ../../../base +patchesStrategicMerge: + - set_appsettings_deployment.yaml + - set_secrets_provider_class.yaml +images: +- name: incentive-manager-api + newName: landmarkcu/lcu.api.internal.incentivemanagerapi-incentivemanagerapi diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/local/common/set_appsettings_deployment.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/local/common/set_appsettings_deployment.yaml new file mode 100644 index 0000000..6517cdd --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/local/common/set_appsettings_deployment.yaml @@ -0,0 +1,12 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: incentive-manager-api +spec: + template: + spec: + containers: + - name: incentive-manager-api + env: + - name: ASPNETCORE_ENVIRONMENT + value: "Development" diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/local/common/set_secrets_provider_class.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/local/common/set_secrets_provider_class.yaml new file mode 100644 index 0000000..e09e08b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/local/common/set_secrets_provider_class.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: secrets-store.csi.x-k8s.io/v1 +kind: SecretProviderClass +metadata: + name: incentive-manager-api-csi-key-vault-secrets-provider +spec: + parameters: + userAssignedIdentityID: 3286ed9a-f85c-4c50-b742-dbd3c1c3fe1f + keyvaultName: kv-lz-apps-qa-uscz diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/local/kustomization.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/local/kustomization.yaml new file mode 100644 index 0000000..25a65b6 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/local/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - normal diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/local/normal/configmap.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/local/normal/configmap.yaml new file mode 100644 index 0000000..faf4253 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/local/normal/configmap.yaml @@ -0,0 +1,78 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: incentivemanagerapi-configmap +data: + primary.json: | + { + "AzureActiveDirectory": { + "Instance": "https://login.microsoftonline.com/", + "Domain": "landmarkcu.com" + }, + "Primary": { + "SeqUrl": "https://qa-seq.lcu-internal.com", + "ApplicationDbIntegrityCronSchedule": "0 0 * * *", + "IncentiveManagerClientUrl": "https://qa-incentive-manager.apps.azure.lcu-internal.com", + "FeatureGates": { + "EnableControllers": true + }, + "FeatureFlags": { + "RunApplicationDbIntegrityHostedService": true, + "RunImportCreditCardSalesConsumer": true, + "RunImportIndirectVehicleSalesConsumer": true, + "RunActionCreditCardProcessConsumer": true, + "RunActionVehicleProcessConsumer": true, + "RunIncentiveProcessesConsumer": true, + "RunNightlyIncentiveProcessesConsumer": true, + "RunReportingProcessConsumer": true, + "RunGoalDispersalProcessConsumer": true, + "RunPayoutIncentiveProcessConsumer": true, + "RunDayforceSyncProcessConsumer": true, + "RunGapMbpProcessConsumer": true + } + }, + "SqlConnectionConfiguration": { + "MaxRetryCount": 5, + "MaxRetryDelay": "0.00:00:30" + }, + "EmailRecipientsConfiguration": { + "TransferRequestInitiatedNotification": [ + "GeorgeWarner@landmarkcu.com", + "DebMcClarran@landmarkcu.com", + "WendySchenk@landmarkcu.com" + ], + "OverrideRecipients": true + }, + "ReportConfiguration": { + "ReportConfigs": [ + { + "Url": "https://d2dtbetl01q.lcu-internal.com/reports/report/IncentiveManager/IncentiveManagerActivities" + }, + { + "Url": "https://d2dtbetl01q.lcu-internal.com/reports/report/IncentiveManager/Admin/IncentiveManagerAssociateRecord" + }, + { + "Url": "https://d2dtbetl01q.lcu-internal.com/reports/report/IncentiveManager/Admin/IncentiveManagerAudit" + }, + { + "Url": "https://d2dtbetl01q.lcu-internal.com/reports/report/IncentiveManager/IncentiveManagerGoalProgress" + }, + { + "Url": "https://d2dtbetl01q.lcu-internal.com/reports/report/IncentiveManager/Admin/IncentiveManagerPayout" + } + ] + }, + "ConnectionConfiguration": { + "HostUrl": "localhost" + }, + "CoreApiWrapperConfiguration": { + "Url": "https://qa-core-api-wrapper-api.apps.azure.lcu-internal.com/api/core/" + }, + "FileOperationsApiConfiguration": { + "Url": "https://qa-fileoperationsapi.lcu-internal.com", + "RetryCount": 1 + }, + "NotificationApiConfiguration": { + "Url": "https://qa-notificationsapi.lcu-internal.com/api/" + } + } diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/local/normal/kustomization.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/local/normal/kustomization.yaml new file mode 100644 index 0000000..6dd0c3b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/local/normal/kustomization.yaml @@ -0,0 +1,5 @@ +resources: + - configmap.yaml + - ../common +patchesStrategicMerge: + - set_virtual_service.yaml diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/local/normal/set_virtual_service.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/local/normal/set_virtual_service.yaml new file mode 100644 index 0000000..1060b75 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/local/normal/set_virtual_service.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: incentive-manager-api +spec: + hosts: + - qa-incentive-manager-api.apps.azure.lcu-internal.com diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/prd/common/kustomization.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/prd/common/kustomization.yaml new file mode 100644 index 0000000..d34b17d --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/prd/common/kustomization.yaml @@ -0,0 +1,8 @@ +resources: + - ../../../base +patchesStrategicMerge: + - set_appsettings_deployment.yaml + - set_secrets_provider_class.yaml +images: +- name: incentive-manager-api + newName: landmarkcu/lcu.api.internal.incentivemanagerapi-incentivemanagerapi diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/prd/common/set_appsettings_deployment.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/prd/common/set_appsettings_deployment.yaml new file mode 100644 index 0000000..c117ba4 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/prd/common/set_appsettings_deployment.yaml @@ -0,0 +1,12 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: incentive-manager-api +spec: + template: + spec: + containers: + - name: incentive-manager-api + env: + - name: ASPNETCORE_ENVIRONMENT + value: "Production" diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/prd/common/set_secrets_provider_class.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/prd/common/set_secrets_provider_class.yaml new file mode 100644 index 0000000..80a9e49 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/prd/common/set_secrets_provider_class.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: secrets-store.csi.x-k8s.io/v1 +kind: SecretProviderClass +metadata: + name: incentive-manager-api-csi-key-vault-secrets-provider +spec: + parameters: + userAssignedIdentityID: 9eaefc49-e5d3-48d4-a2b9-b42cf2d8ca03 + keyvaultName: kv-lz-apps-prod-uscz diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/prd/kustomization.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/prd/kustomization.yaml new file mode 100644 index 0000000..25a65b6 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/prd/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - normal diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/prd/normal/configmap.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/prd/normal/configmap.yaml new file mode 100644 index 0000000..6663b0b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/prd/normal/configmap.yaml @@ -0,0 +1,81 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: incentivemanagerapi-configmap +data: + primary.json: | + { + "AzureActiveDirectory": { + "Instance": "https://login.microsoftonline.com/", + "Domain": "landmarkcu.com" + }, + "Primary": { + "SeqUrl": "https://seq.lcu-internal.com", + "ApplicationDbIntegrityCronSchedule": "0 0 * * *", + "IncentiveManagerClientUrl": "https://prd-incentive-manager.apps.azure.lcu-internal.com", + "FeatureGates": { + "EnableControllers": true + }, + "FeatureFlags": { + "RunApplicationDbIntegrityHostedService": true, + "RunImportCreditCardSalesConsumer": true, + "RunImportIndirectVehicleSalesConsumer": true, + "RunActionCreditCardProcessConsumer": true, + "RunActionVehicleProcessConsumer": true, + "RunIncentiveProcessesConsumer": true, + "RunNightlyIncentiveProcessesConsumer": true, + "RunReportingProcessConsumer": true, + "RunGoalDispersalProcessConsumer": true, + "RunPayoutIncentiveProcessConsumer": true, + "RunDayforceSyncProcessConsumer": true, + "RunGapMbpProcessConsumer": true + } + }, + "DayForceConfiguration": { + "DayForceManualReportRecipients": [ + "BenVandervort@landmarkcu.com" + ] + }, + "SqlConnectionConfiguration": { + "MaxRetryCount": 5, + "MaxRetryDelay": "0.00:00:30" + }, + "EmailRecipientsConfiguration": { + "TransferRequestInitiatedNotification": [ + "BenVandervort@landmarkcu.com" + ], + "OverrideRecipients": false + }, + "ReportConfiguration": { + "ReportConfigs": [ + { + "Url": "https://d2dtbetl01p.lcu-internal.com/reports/report/IncentiveManager/IncentiveManagerActivities" + }, + { + "Url": "https://d2dtbetl01p.lcu-internal.com/reports/report/IncentiveManager/Admin/IncentiveManagerAssociateRecord" + }, + { + "Url": "https://d2dtbetl01p.lcu-internal.com/reports/report/IncentiveManager/Admin/IncentiveManagerAudit" + }, + { + "Url": "https://d2dtbetl01p.lcu-internal.com/reports/report/IncentiveManager/IncentiveManagerGoalProgress" + }, + { + "Url": "https://d2dtbetl01p.lcu-internal.com/reports/report/IncentiveManager/Admin/IncentiveManagerPayout" + } + ] + }, + "ConnectionConfiguration": { + "HostUrl": "rabbitmq.lcu-internal.com" + }, + "CoreApiWrapperConfiguration": { + "Url": "https://prd-core-api-wrapper-api.apps.azure.lcu-internal.com/api/core/" + }, + "FileOperationsApiConfiguration": { + "Url": "https://fileoperationsapi.lcu-internal.com", + "RetryCount": 1 + }, + "NotificationApiConfiguration": { + "Url": "https://notificationsapi.lcu-internal.com/api/" + } + } diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/prd/normal/kustomization.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/prd/normal/kustomization.yaml new file mode 100644 index 0000000..6dd0c3b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/prd/normal/kustomization.yaml @@ -0,0 +1,5 @@ +resources: + - configmap.yaml + - ../common +patchesStrategicMerge: + - set_virtual_service.yaml diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/prd/normal/set_virtual_service.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/prd/normal/set_virtual_service.yaml new file mode 100644 index 0000000..1649a96 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/prd/normal/set_virtual_service.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: incentive-manager-api +spec: + hosts: + - prd-incentive-manager-api.apps.azure.lcu-internal.com diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/qa/common/kustomization.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/qa/common/kustomization.yaml new file mode 100644 index 0000000..d34b17d --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/qa/common/kustomization.yaml @@ -0,0 +1,8 @@ +resources: + - ../../../base +patchesStrategicMerge: + - set_appsettings_deployment.yaml + - set_secrets_provider_class.yaml +images: +- name: incentive-manager-api + newName: landmarkcu/lcu.api.internal.incentivemanagerapi-incentivemanagerapi diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/qa/common/set_appsettings_deployment.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/qa/common/set_appsettings_deployment.yaml new file mode 100644 index 0000000..6517cdd --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/qa/common/set_appsettings_deployment.yaml @@ -0,0 +1,12 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: incentive-manager-api +spec: + template: + spec: + containers: + - name: incentive-manager-api + env: + - name: ASPNETCORE_ENVIRONMENT + value: "Development" diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/qa/common/set_secrets_provider_class.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/qa/common/set_secrets_provider_class.yaml new file mode 100644 index 0000000..e09e08b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/qa/common/set_secrets_provider_class.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: secrets-store.csi.x-k8s.io/v1 +kind: SecretProviderClass +metadata: + name: incentive-manager-api-csi-key-vault-secrets-provider +spec: + parameters: + userAssignedIdentityID: 3286ed9a-f85c-4c50-b742-dbd3c1c3fe1f + keyvaultName: kv-lz-apps-qa-uscz diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/qa/kustomization.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/qa/kustomization.yaml new file mode 100644 index 0000000..25a65b6 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/qa/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - normal diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/qa/normal/configmap.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/qa/normal/configmap.yaml new file mode 100644 index 0000000..3b92a82 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/qa/normal/configmap.yaml @@ -0,0 +1,85 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: incentivemanagerapi-configmap +data: + primary.json: | + { + "AzureActiveDirectory": { + "Instance": "https://login.microsoftonline.com/", + "Domain": "landmarkcu.com" + }, + "Primary": { + "SeqUrl": "https://qa-seq.lcu-internal.com", + "ApplicationDbIntegrityCronSchedule": "0 0 * * *", + "IncentiveManagerClientUrl": "https://qa-incentive-manager.apps.azure.lcu-internal.com", + "FeatureGates": { + "EnableControllers": true + }, + "FeatureFlags": { + "RunApplicationDbIntegrityHostedService": true, + "RunImportCreditCardSalesConsumer": true, + "RunImportIndirectVehicleSalesConsumer": true, + "RunActionCreditCardProcessConsumer": true, + "RunActionVehicleProcessConsumer": true, + "RunIncentiveProcessesConsumer": true, + "RunNightlyIncentiveProcessesConsumer": true, + "RunReportingProcessConsumer": true, + "RunGoalDispersalProcessConsumer": true, + "RunPayoutIncentiveProcessConsumer": true, + "RunDayforceSyncProcessConsumer": true, + "RunGapMbpProcessConsumer": true + } + }, + "DayForceConfiguration": { + "DayForceManualReportRecipients": [ + "WendySchenk@landmarkcu.com", + "DebMcClarran@landmarkcu.com", + "FredDebelack@landmarkcu.com" + ] + }, + "SqlConnectionConfiguration": { + "MaxRetryCount": 5, + "MaxRetryDelay": "0.00:00:30" + }, + "EmailRecipientsConfiguration": { + "TransferRequestInitiatedNotification": [ + "GeorgeWarner@landmarkcu.com", + "DebMcClarran@landmarkcu.com", + "WendySchenk@landmarkcu.com" + ], + "OverrideRecipients": true + }, + "ReportConfiguration": { + "ReportConfigs": [ + { + "Url": "https://d2dtbetl01q.lcu-internal.com/reports/report/IncentiveManager/IncentiveManagerActivities" + }, + { + "Url": "https://d2dtbetl01q.lcu-internal.com/reports/report/IncentiveManager/Admin/IncentiveManagerAssociateRecord" + }, + { + "Url": "https://d2dtbetl01q.lcu-internal.com/reports/report/IncentiveManager/Admin/IncentiveManagerAudit" + }, + { + "Url": "https://d2dtbetl01q.lcu-internal.com/reports/report/IncentiveManager/IncentiveManagerGoalProgress" + }, + { + "Url": "https://d2dtbetl01q.lcu-internal.com/reports/report/IncentiveManager/Admin/IncentiveManagerPayout" + } + ] + }, + "ConnectionConfiguration": { + "HostUrl": "qa-rabbitmq.lcu-internal.com" + }, + "CoreApiWrapperConfiguration": { + "Url": "https://qa-core-api-wrapper-api.apps.azure.lcu-internal.com/api/core/" + }, + "FileOperationsApiConfiguration": { + "Url": "https://qa-fileoperationsapi.lcu-internal.com", + "RetryCount": 1 + }, + "NotificationApiConfiguration": { + "Url": "https://qa-notificationsapi.lcu-internal.com/api/" + } + } diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/qa/normal/kustomization.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/qa/normal/kustomization.yaml new file mode 100644 index 0000000..6dd0c3b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/qa/normal/kustomization.yaml @@ -0,0 +1,5 @@ +resources: + - configmap.yaml + - ../common +patchesStrategicMerge: + - set_virtual_service.yaml diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/qa/normal/set_virtual_service.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/qa/normal/set_virtual_service.yaml new file mode 100644 index 0000000..1060b75 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/qa/normal/set_virtual_service.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: incentive-manager-api +spec: + hosts: + - qa-incentive-manager-api.apps.azure.lcu-internal.com diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/uat/common/kustomization.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/uat/common/kustomization.yaml new file mode 100644 index 0000000..d34b17d --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/uat/common/kustomization.yaml @@ -0,0 +1,8 @@ +resources: + - ../../../base +patchesStrategicMerge: + - set_appsettings_deployment.yaml + - set_secrets_provider_class.yaml +images: +- name: incentive-manager-api + newName: landmarkcu/lcu.api.internal.incentivemanagerapi-incentivemanagerapi diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/uat/common/set_appsettings_deployment.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/uat/common/set_appsettings_deployment.yaml new file mode 100644 index 0000000..9281144 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/uat/common/set_appsettings_deployment.yaml @@ -0,0 +1,12 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: incentive-manager-api +spec: + template: + spec: + containers: + - name: incentive-manager-api + env: + - name: ASPNETCORE_ENVIRONMENT + value: "Staging" diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/uat/common/set_secrets_provider_class.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/uat/common/set_secrets_provider_class.yaml new file mode 100644 index 0000000..b45fe51 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/uat/common/set_secrets_provider_class.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: secrets-store.csi.x-k8s.io/v1 +kind: SecretProviderClass +metadata: + name: incentive-manager-api-csi-key-vault-secrets-provider +spec: + parameters: + userAssignedIdentityID: 64ba01ed-1ecc-4c9b-b56e-8d618f2b81ce + keyvaultName: kv-lz-apps-uat-uscz diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/uat/kustomization.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/uat/kustomization.yaml new file mode 100644 index 0000000..25a65b6 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/uat/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - normal diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/uat/normal/configmap.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/uat/normal/configmap.yaml new file mode 100644 index 0000000..c999fed --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/uat/normal/configmap.yaml @@ -0,0 +1,84 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: incentivemanagerapi-configmap +data: + primary.json: | + { + "AzureActiveDirectory": { + "Instance": "https://login.microsoftonline.com/", + "Domain": "landmarkcu.com" + }, + "Primary": { + "SeqUrl": "https://uat-seq.lcu-internal.com", + "ApplicationDbIntegrityCronSchedule": "0 0 * * *", + "IncentiveManagerClientUrl": "https://uat-incentive-manager.apps.azure.lcu-internal.com", + "FeatureGates": { + "EnableControllers": true + }, + "FeatureFlags": { + "RunApplicationDbIntegrityHostedService": true, + "RunImportCreditCardSalesConsumer": true, + "RunImportIndirectVehicleSalesConsumer": true, + "RunActionCreditCardProcessConsumer": true, + "RunActionVehicleProcessConsumer": true, + "RunIncentiveProcessesConsumer": true, + "RunNightlyIncentiveProcessesConsumer": true, + "RunReportingProcessConsumer": true, + "RunGoalDispersalProcessConsumer": true, + "RunPayoutIncentiveProcessConsumer": true, + "RunDayforceSyncProcessConsumer": true, + "RunGapMbpProcessConsumer": true + } + }, + "DayForceConfiguration": { + "DayForceManualReportRecipients": [ + "WendySchenk@landmarkcu.com", + "DebMcClarran@landmarkcu.com", + "FredDebelack@landmarkcu.com" + ] + }, + "SqlConnectionConfiguration": { + "MaxRetryCount": 5, + "MaxRetryDelay": "0.00:00:30" + }, + "EmailRecipientsConfiguration": { + "TransferRequestInitiatedNotification": [ + "freddebelack@landmarkcu.com", + "WendySchenk@landmarkcu.com" + ], + "OverrideRecipients": true + }, + "ReportConfiguration": { + "ReportConfigs": [ + { + "Url": "https://d2dtbetl01u.lcu-internal.com/reports/report/IncentiveManager/IncentiveManagerActivities" + }, + { + "Url": "https://d2dtbetl01u.lcu-internal.com/reports/report/IncentiveManager/Admin/IncentiveManagerAssociateRecord" + }, + { + "Url": "https://d2dtbetl01u.lcu-internal.com/reports/report/IncentiveManager/Admin/IncentiveManagerAudit" + }, + { + "Url": "https://d2dtbetl01u.lcu-internal.com/reports/report/IncentiveManager/IncentiveManagerGoalProgress" + }, + { + "Url": "https://d2dtbetl01u.lcu-internal.com/reports/report/IncentiveManager/Admin/IncentiveManagerPayout" + } + ] + }, + "ConnectionConfiguration": { + "HostUrl": "uat-rabbitmq.lcu-internal.com" + }, + "CoreApiWrapperConfiguration": { + "Url": "https://uat-core-api-wrapper-api.apps.azure.lcu-internal.com/api/core/" + }, + "FileOperationsApiConfiguration": { + "Url": "https://uat-fileoperationsapi.lcu-internal.com", + "RetryCount": 1 + }, + "NotificationApiConfiguration": { + "Url": "https://uat-notificationsapi.lcu-internal.com/api/" + } + } diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/uat/normal/kustomization.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/uat/normal/kustomization.yaml new file mode 100644 index 0000000..6dd0c3b --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/uat/normal/kustomization.yaml @@ -0,0 +1,5 @@ +resources: + - configmap.yaml + - ../common +patchesStrategicMerge: + - set_virtual_service.yaml diff --git a/LCU.API.Operations.IncentiveAPI/kustomize/overlays/uat/normal/set_virtual_service.yaml b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/uat/normal/set_virtual_service.yaml new file mode 100644 index 0000000..4681a47 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/kustomize/overlays/uat/normal/set_virtual_service.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: incentive-manager-api +spec: + hosts: + - uat-incentive-manager-api.apps.azure.lcu-internal.com diff --git a/LCU.API.Operations.IncentiveAPI/push.sh b/LCU.API.Operations.IncentiveAPI/push.sh new file mode 100644 index 0000000..9a5bdc9 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/push.sh @@ -0,0 +1,4 @@ +#!/bin/bash +echo "Pushing API Docker Image"; +docker tag local/incentive-api landmarkcu/incentive-api:0.0.1 +docker push landmarkcu/incentive-api:0.0.1 diff --git a/LCU.API.Operations.IncentiveAPI/run.sh b/LCU.API.Operations.IncentiveAPI/run.sh new file mode 100644 index 0000000..95d79f7 --- /dev/null +++ b/LCU.API.Operations.IncentiveAPI/run.sh @@ -0,0 +1,3 @@ +#!/bin/bash +echo "Running API Docker Container"; +docker run -p 8080:80 local/incentive-api