PCRB follow up client side logic

This commit is contained in:
Chase Tucker
2025-03-19 10:01:35 -07:00
parent 4871668a90
commit cc4781b990
45 changed files with 3082 additions and 1008 deletions

View File

@ -15,15 +15,15 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.8" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.14" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.14" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.14" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.3.0" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.3.0" />
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="8.0.14" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
<PackageReference Include="MudBlazor" Version="7.6.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.1" />
<PackageReference Include="MudBlazor" Version="8.3.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.6.1" />
</ItemGroup>
<ItemGroup>

View File

@ -1,7 +1,9 @@
using MesaFabApproval.Client.Services;
using MesaFabApproval.Shared.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Caching.Memory;
using MudBlazor;
@ -13,8 +15,8 @@ public partial class AuthenticatedRedirect {
[Inject] MesaFabApprovalAuthStateProvider authStateProvider { get; set; }
[Inject] IAuthenticationService authService { get; set; }
[Inject] IUserService userService { get; set; }
[Inject] ISnackbar snackbar { get; set; }
[Inject] NavigationManager navigationManager { get; set; }
[Inject] IMemoryCache cache { get; set; }
private string? _jwt;
private string? _refreshToken;
@ -53,13 +55,16 @@ public partial class AuthenticatedRedirect {
await authStateProvider.StateHasChanged(principal);
}
AuthTokens authTokens = await authService.GetAuthTokens();
if (authStateProvider.CurrentUser is not null && !string.IsNullOrWhiteSpace(_redirectPath)) {
navigationManager.NavigateTo(_redirectPath);
} else {
await authStateProvider.Logout();
if (!string.IsNullOrWhiteSpace(_redirectPath)) {
navigationManager.NavigateTo($"login/{_redirectPath}");
cache.Set("redirectUrl", _redirectPath);
navigationManager.NavigateTo($"login?redirectPath={_redirectPath}");
} else {
navigationManager.NavigateTo("login");
}
@ -68,7 +73,8 @@ public partial class AuthenticatedRedirect {
await authStateProvider.Logout();
if (!string.IsNullOrWhiteSpace(_redirectPath)) {
navigationManager.NavigateTo($"login/{_redirectPath}");
cache.Set("redirectUrl", _redirectPath);
navigationManager.NavigateTo($"login?redirectPath={_redirectPath}");
} else {
navigationManager.NavigateTo("login");
}

View File

@ -37,7 +37,7 @@
</MudDialog>
@code {
[CascadingParameter] MudDialogInstance MudDialog { get; set; }
[CascadingParameter] IMudDialogInstance MudDialog { get; set; }
[Parameter]
public string comments { get; set; } = "";

View File

@ -54,7 +54,7 @@
</MudDialog>
@code {
[CascadingParameter] MudDialogInstance MudDialog { get; set; }
[CascadingParameter] IMudDialogInstance MudDialog { get; set; }
[Parameter]
public string comments { get; set; } = "";

View File

@ -143,7 +143,7 @@
</MudDialog>
@code {
[CascadingParameter] MudDialogInstance MudDialog { get; set; }
[CascadingParameter] IMudDialogInstance MudDialog { get; set; }
[Parameter]
public MRBAction mrbAction { get; set; }

View File

@ -43,7 +43,7 @@
</MudOverlay>
@code {
[CascadingParameter] MudDialogInstance MudDialog { get; set; }
[CascadingParameter] IMudDialogInstance MudDialog { get; set; }
[Parameter]
public User selectedUser { get; set; }

View File

@ -74,7 +74,7 @@
@code {
[CascadingParameter]
MudDialogInstance MudDialog { get; set; }
IMudDialogInstance MudDialog { get; set; }
[Parameter]
public required PCR3Document document { get; set; }

View File

@ -73,7 +73,7 @@
@code {
[CascadingParameter]
MudDialogInstance MudDialog { get; set; }
IMudDialogInstance MudDialog { get; set; }
[Parameter]
public int planNumber { get; set; } = 0;

View File

@ -61,7 +61,7 @@
@code {
[CascadingParameter]
MudDialogInstance MudDialog { get; set; }
IMudDialogInstance MudDialog { get; set; }
[Parameter]
public int planNumber { get; set; } = 0;

View File

@ -58,7 +58,7 @@
</MudDialog>
@code {
[CascadingParameter] MudDialogInstance MudDialog { get; set; }
[CascadingParameter] IMudDialogInstance MudDialog { get; set; }
[Parameter]
public int planNumber { get; set; } = 0;

View File

@ -43,7 +43,7 @@
</MudOverlay>
@code {
[CascadingParameter] MudDialogInstance MudDialog { get; set; }
[CascadingParameter] IMudDialogInstance MudDialog { get; set; }
[Parameter]
public User selectedUser { get; set; }

View File

@ -26,7 +26,7 @@
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<Approval,object>(x=>x.SubRoleCategoryItem)">
<MudTableSortLabel SortBy="new Func<Approval,object>(x=>GetRoleName(x))">
Role
</MudTableSortLabel>
</MudTh>
@ -47,7 +47,7 @@
<MudLink OnClick="@(() => FollowLink(context.IssueID))">@context.IssueID</MudLink>
}
</MudTd>
<MudTd DataLabel="Role">@context.SubRoleCategoryItem</MudTd>
<MudTd DataLabel="Role">@(GetRoleName(context))</MudTd>
<MudTd DataLabel="Assigned Date">@DateTimeUtilities.GetDateAsStringMinDefault(context.AssignedDate)</MudTd>
<MudTd DataLabel="Step">@context.Step</MudTd>
</RowTemplate>

View File

@ -118,7 +118,7 @@ public partial class Dashboard {
private void GoTo(string page) {
cache.Set("redirectUrl", page);
navigationManager.NavigateTo("/" + page);
navigationManager.NavigateTo(page);
}
private async Task GoToExternal(string url, string content) {
@ -154,4 +154,12 @@ public partial class Dashboard {
if (step < 0 || step > (PCRB.Stages.Length - 1)) return string.Empty;
else return PCRB.Stages[step];
}
private string GetRoleName(Approval approval) {
string roleName = approval.SubRoleCategoryItem;
if (string.IsNullOrWhiteSpace(roleName)) {
roleName = approval.RoleName;
}
return roleName;
}
}

View File

@ -1,6 +1,4 @@
@page "/login"
@page "/login/{redirectUrl}"
@page "/login/{redirectUrl}/{redirectUrlSub}"
@attribute [AllowAnonymous]
@inject MesaFabApprovalAuthStateProvider authStateProvider
@inject NavigationManager navManager
@ -46,68 +44,5 @@
}
</MudButton>
<MudDivider />
@* <MudButton
Variant="Variant.Filled"
Color="Color.Tertiary"
Class="m-1"
OnClick="LoginLocal" >
@if (processingLocal) {
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
<MudText>Processing</MudText>
} else {
<MudText>Log In (SSO)</MudText>
}
</MudButton> *@
</MudForm>
</MudPaper>
@code {
[Parameter]
public string? redirectUrl { get; set; }
[Parameter]
public string? redirectUrlSub { get; set; }
private bool success;
private bool processing = false;
private bool processingLocal = false;
private string[] errors = { };
private string? username;
private string? password;
private async Task SubmitLogin() {
processing = true;
if (string.IsNullOrWhiteSpace(username)) snackbar.Add("Username is required!", Severity.Error);
else if (string.IsNullOrWhiteSpace(password)) snackbar.Add("Password is required!", Severity.Error);
else {
await authStateProvider.LoginAsync(username, password);
if (!string.IsNullOrWhiteSpace(redirectUrl) && !string.IsNullOrWhiteSpace(redirectUrlSub)) {
navManager.NavigateTo($"{redirectUrl}/{redirectUrlSub}");
} else if (!string.IsNullOrWhiteSpace(redirectUrl)) {
navManager.NavigateTo(redirectUrl);
} else {
navManager.NavigateTo("dashboard");
}
}
processing = false;
}
private async Task SubmitIfEnter(KeyboardEventArgs e) {
if (e.Key == "Enter" && success) {
SubmitLogin();
}
}
private async Task LoginLocal() {
processingLocal = true;
await authStateProvider.LoginLocal();
if (!string.IsNullOrWhiteSpace(redirectUrl) && !string.IsNullOrWhiteSpace(redirectUrlSub)) {
navManager.NavigateTo($"{redirectUrl}/{redirectUrlSub}");
} else if (!string.IsNullOrWhiteSpace(redirectUrl)) {
navManager.NavigateTo(redirectUrl);
} else {
navManager.NavigateTo("dashboard");
}
processingLocal = false;
}
}

View File

@ -0,0 +1,53 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Caching.Memory;
using MudBlazor;
namespace MesaFabApproval.Client.Pages;
public partial class Login {
[Inject] NavigationManager navigationManager { get; set; }
[Inject] IMemoryCache cache { get; set; }
public string? _redirectPath { get; set; }
private bool success;
private bool processing = false;
private string[] errors = { };
private string? username;
private string? password;
protected override async Task OnParametersSetAsync() {
Uri uri = navigationManager.ToAbsoluteUri(navigationManager.Uri);
if (QueryHelpers.ParseQuery(uri.Query).TryGetValue("redirectPath", out var redirectPath)) {
_redirectPath = System.Net.WebUtility.UrlDecode(redirectPath);
}
if (string.IsNullOrWhiteSpace(_redirectPath)) {
_redirectPath = cache.Get<string>("redirectUrl");
}
}
private async Task SubmitLogin() {
processing = true;
if (string.IsNullOrWhiteSpace(username)) snackbar.Add("Username is required!", Severity.Error);
else if (string.IsNullOrWhiteSpace(password)) snackbar.Add("Password is required!", Severity.Error);
else {
await authStateProvider.LoginAsync(username, password);
if (!string.IsNullOrWhiteSpace(_redirectPath)) {
navManager.NavigateTo(_redirectPath);
} else {
navManager.NavigateTo("dashboard");
}
}
processing = false;
}
private async Task SubmitIfEnter(KeyboardEventArgs e) {
if (e.Key == "Enter" && success) {
SubmitLogin();
}
}
}

View File

@ -1,9 +1,4 @@
@page "/mrb/all"
@using System.Globalization
@inject IMRBService mrbService
@inject ISnackbar snackbar
@inject IMemoryCache cache
@inject NavigationManager navigationManager
<PageTitle>MRB</PageTitle>
@ -78,42 +73,3 @@
<MudOverlay @bind-Visible=inProcess DarkBackground="true" AutoClose="false">
<MudProgressCircular Color="Color.Info" Size="Size.Large" Indeterminate="true" />
</MudOverlay>
@code {
private bool inProcess = false;
private string searchString = "";
private IEnumerable<MRB> allMrbs = new List<MRB>();
protected override async Task OnParametersSetAsync() {
inProcess = true;
try {
if (mrbService is null) {
throw new Exception("MRB service not injected!");
} else {
allMrbs = await mrbService.GetAllMRBs(false);
}
} catch (Exception ex) {
snackbar.Add(ex.Message, Severity.Error);
}
inProcess = false;
}
private bool FilterFuncForTable(MRB mrb) => FilterFunc(mrb, searchString);
private bool FilterFunc(MRB mrb, string searchString) {
if (string.IsNullOrWhiteSpace(searchString))
return true;
if (mrb.Title.ToLower().Contains(searchString.Trim().ToLower()))
return true;
if (mrb.OriginatorName.ToLower().Contains(searchString.Trim().ToLower()))
return true;
if (mrb.MRBNumber.ToString().Contains(searchString.Trim()))
return true;
return false;
}
private void GoTo(string page) {
cache.Set("redirectUrl", page);
navigationManager.NavigateTo(page);
}
}

View File

@ -0,0 +1,55 @@
using MesaFabApproval.Client.Services;
using MesaFabApproval.Shared.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Caching.Memory;
using MudBlazor;
namespace MesaFabApproval.Client.Pages;
public partial class MRBAll {
[Inject] IMRBService mrbService { get; set; }
[Inject] ISnackbar snackbar { get; set; }
[Inject] IMemoryCache cache { get; set; }
[Inject] NavigationManager navigationManager { get; set; }
private bool inProcess = false;
private string searchString = "";
private IEnumerable<MRB> allMrbs = new List<MRB>();
protected override async Task OnParametersSetAsync() {
inProcess = true;
try {
cache.Set("redirectUrl", "mrb/all");
if (mrbService is null) {
throw new Exception("MRB service not injected!");
} else {
allMrbs = await mrbService.GetAllMRBs(false);
}
} catch (Exception ex) {
snackbar.Add(ex.Message, Severity.Error);
}
inProcess = false;
}
private bool FilterFuncForTable(MRB mrb) => FilterFunc(mrb, searchString);
private bool FilterFunc(MRB mrb, string searchString) {
if (string.IsNullOrWhiteSpace(searchString))
return true;
if (mrb.Title.ToLower().Contains(searchString.Trim().ToLower()))
return true;
if (mrb.OriginatorName.ToLower().Contains(searchString.Trim().ToLower()))
return true;
if (mrb.MRBNumber.ToString().Contains(searchString.Trim()))
return true;
return false;
}
private void GoTo(string page) {
cache.Set("redirectUrl", page);
navigationManager.NavigateTo(page);
}
}

View File

@ -65,6 +65,8 @@ public partial class MRBSingle {
protected override async Task OnParametersSetAsync() {
processing = true;
try {
cache.Set("redirectUrl", $"mrb/{mrbNumber}");
allActiveUsers = await userService.GetAllActiveUsers();
currentUser = authStateProvider.CurrentUser;
currentUrl = navigationManager.Uri;
@ -285,7 +287,7 @@ public partial class MRBSingle {
string? comments = "";
DialogParameters<Comments> parameters = new DialogParameters<Comments> { { x => x.comments, comments } };
var dialog = dialogService.Show<Comments>($"Approval Comments", parameters);
var dialog = await dialogService.ShowAsync<Comments>($"Approval Comments", parameters);
var result = await dialog.Result;
@ -412,7 +414,7 @@ public partial class MRBSingle {
if (currentUser is null) {
recallInProcess = false;
snackbar.Add("You must be logged in to recall this MRB", Severity.Error);
navigationManager.NavigateTo($"login/mrb/{mrb.MRBNumber}");
navigationManager.NavigateTo($"login?redirectPath=mrb/{mrb.MRBNumber}");
} else {
await mrbService.RecallMRB(mrb, currentUser);
mrbApprovals = await approvalService.GetApprovalsForIssueId(mrb.MRBNumber, true);

View File

@ -1,9 +1,4 @@
@page "/pcrb/all"
@using System.Globalization
@inject IPCRBService pcrbService
@inject ISnackbar snackbar
@inject IMemoryCache cache
@inject NavigationManager navigationManager
<PageTitle>PCRB</PageTitle>
@ -25,6 +20,7 @@
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Search"
IconSize="Size.Medium"
Immediate
Class="mt-0" />
</ToolBarContent>
<HeaderContent>
@ -43,19 +39,37 @@
Owner
</MudTableSortLabel>
</MudTh>
<MudTh>Stage</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<PCRB,object>(x=>x.Type)">
Type
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<PCRB, object>(x=>GetStageName(x.CurrentStep))">
Stage
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<PCRB,object>(x=>x.InsertTimeStamp)">
Submitted Date
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<PCRB,object>(x=>x.LastUpdateDate)">
Last Updated
<MudTableSortLabel SortBy="new Func<PCRB,object>(x=>x.ClosedDate)">
Completed Date
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<PCRB,object>(x=>x.ClosedDate)">
<MudTableSortLabel SortBy="new Func<PCRB, object>(x=>(x.FollowUps.FirstOrDefault() is null ?
DateTimeUtilities.MIN_DT:
x.FollowUps.First().FollowUpDate))">
Follow Up Date
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<PCRB, object>(x=>(x.FollowUps.FirstOrDefault() is null ?
DateTimeUtilities.MIN_DT:
x.FollowUps.First().CompletedDate))">
Closed Date
</MudTableSortLabel>
</MudTh>
@ -66,10 +80,20 @@
</MudTd>
<MudTd DataLabel="Title">@context.Title</MudTd>
<MudTd DataLabel="Owner">@context.OwnerName</MudTd>
<MudTd DataLabel="Type">@context.Type</MudTd>
<MudTd DataLabel="Stage">@(GetStageName(context.CurrentStep))</MudTd>
<MudTd DataLabel="Submitted Date">@DateTimeUtilities.GetDateAsStringMinDefault(context.InsertTimeStamp)</MudTd>
<MudTd DataLabel="Last Updated">@DateTimeUtilities.GetDateAsStringMinDefault(context.LastUpdateDate)</MudTd>
<MudTd DataLabel="Closed Date">@DateTimeUtilities.GetDateAsStringMaxDefault(context.ClosedDate)</MudTd>
<MudTd DataLabel="Completed Date">@DateTimeUtilities.GetDateAsStringMaxDefault(context.ClosedDate)</MudTd>
<MudTd DataLabel="Follow Up Date">@(DateTimeUtilities.GetDateAsStringMaxDefault(context.FollowUps
.FirstOrDefault()?.FollowUpDate
)
)
</MudTd>
<MudTd DataLabel="Closed Date">@(DateTimeUtilities.GetDateAsStringMaxDefault(context.FollowUps
.FirstOrDefault()?.CompletedDate
)
)
</MudTd>
</RowTemplate>
<PagerContent>
<MudTablePager />
@ -80,47 +104,3 @@
<MudOverlay @bind-Visible=inProcess DarkBackground="true" AutoClose="false">
<MudProgressCircular Color="Color.Info" Size="Size.Large" Indeterminate="true" />
</MudOverlay>
@code {
private bool inProcess = false;
private string searchString = "";
private IEnumerable<PCRB> allPCRBs = new List<PCRB>();
protected override async Task OnParametersSetAsync() {
inProcess = true;
try {
if (pcrbService is null) {
throw new Exception("PCRB service not injected!");
} else {
allPCRBs = await pcrbService.GetAllPCRBs(false);
}
} catch (Exception ex) {
snackbar.Add(ex.Message, Severity.Error);
}
inProcess = false;
}
private bool FilterFuncForTable(PCRB pcrb) => FilterFunc(pcrb, searchString);
private bool FilterFunc(PCRB pcrb, string searchString) {
if (string.IsNullOrWhiteSpace(searchString))
return true;
if (pcrb.Title.ToLower().Contains(searchString.Trim().ToLower()))
return true;
if (pcrb.OwnerName.ToLower().Contains(searchString.Trim().ToLower()))
return true;
if (pcrb.PlanNumber.ToString().Contains(searchString.Trim()))
return true;
return false;
}
private void GoTo(string page) {
cache.Set("redirectUrl", page);
navigationManager.NavigateTo(page);
}
private string GetStageName(int step) {
if (step >= PCRB.Stages.Length || step < 0) return "";
return PCRB.Stages[step];
}
}

View File

@ -0,0 +1,64 @@
using MesaFabApproval.Client.Services;
using MesaFabApproval.Shared.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Caching.Memory;
using MudBlazor;
namespace MesaFabApproval.Client.Pages;
public partial class PCRBAll {
[Inject] IPCRBService pcrbService { get; set; }
[Inject] ISnackbar snackbar { get; set; }
[Inject] IMemoryCache cache { get; set; }
[Inject] NavigationManager navigationManager { get; set; }
private bool inProcess = false;
private string searchString = "";
private IEnumerable<PCRB> allPCRBs = new List<PCRB>();
protected override async Task OnParametersSetAsync() {
inProcess = true;
try {
cache.Set("redirectUrl", "pcrb/all");
if (pcrbService is null) {
throw new Exception("PCRB service not injected!");
} else {
allPCRBs = await pcrbService.GetAllPCRBs(false);
}
} catch (Exception ex) {
snackbar.Add(ex.Message, Severity.Error);
}
inProcess = false;
}
private bool FilterFuncForTable(PCRB pcrb) => FilterFunc(pcrb, searchString);
private bool FilterFunc(PCRB pcrb, string searchString) {
if (string.IsNullOrWhiteSpace(searchString))
return true;
if (pcrb.Title.ToLower().Contains(searchString.Trim().ToLower()))
return true;
if (pcrb.OwnerName.ToLower().Contains(searchString.Trim().ToLower()))
return true;
if (pcrb.Type.ToLower().Contains(searchString.Trim().ToLower()))
return true;
if (GetStageName(pcrb.CurrentStep).ToLower().Contains(searchString.Trim().ToLower()))
return true;
if (pcrb.PlanNumber.ToString().Contains(searchString.Trim()))
return true;
return false;
}
private void GoTo(string page) {
cache.Set("redirectUrl", page);
navigationManager.NavigateTo(page);
}
private string GetStageName(int step) {
if (step >= PCRB.Stages.Length || step < 0) return "";
return PCRB.Stages[step];
}
}

View File

@ -6,20 +6,22 @@
<MudPaper Class="p-2 m-2 d-flex flex-row justify-content-between">
<MudIconButton Icon="@Icons.Material.Filled.ChevronLeft"
Variant="Variant.Outlined"
Color="Color.Dark"
OnClick="@ReturnToAllPcrbs"
Size="Size.Large" />
Variant="Variant.Outlined"
Color="Color.Dark"
OnClick="@ReturnToAllPcrbs"
Size="Size.Large" />
<MudText Typo="Typo.h3" Align="Align.Center">PCRB @planNumber</MudText>
<MudPaper Height="100%" Width="0.1%" Square="true" />
</MudPaper>
@if (pcrb is not null) {
<MudTimeline Class="mt-2 pt-2" TimelineOrientation="TimelineOrientation.Horizontal"
TimelinePosition="TimelinePosition.Bottom">
TimelinePosition="TimelinePosition.Bottom">
@for (int i = 0; i < PCRB.Stages.Length; i++) {
Color color;
if (pcrb.CurrentStep > i || pcrb.CurrentStep == (PCRB.Stages.Length - 1)) {
if (pcrb.CurrentStep > i ||
(i == (int)PCRB.StagesEnum.Complete && pcrb.CurrentStep == (int)PCRB.StagesEnum.Complete) ||
(i == (int)PCRB.StagesEnum.Closed && pcrb.CurrentStep == (int)PCRB.StagesEnum.Closed)) {
color = Color.Success;
} else if (pcrb.CurrentStep == i) {
color = Color.Info;
@ -35,14 +37,14 @@
}
</MudTimeline>
bool pcrbIsSubmitted = pcrb.CurrentStep > 0 && pcrb.InsertTimeStamp > DateTimeUtilities.MIN_DT;
bool pcrbIsComplete = pcrb.ClosedDate < DateTimeUtilities.MAX_DT && pcrb.CurrentStep == (PCRB.Stages.Length - 1);
bool pcrbIsSubmitted = pcrb.CurrentStep > (int)PCRB.StagesEnum.Draft && pcrb.InsertTimeStamp > DateTimeUtilities.MIN_DT;
bool pcrbIsComplete = pcrb.ClosedDate < DateTimeUtilities.MAX_DT && pcrb.CurrentStep >= (int)PCRB.StagesEnum.Complete;
bool userIsOriginator = pcrb.OwnerID == authStateProvider.CurrentUser?.UserID;
bool userIsAdmin = authStateProvider.CurrentUser is null ? false : authStateProvider.CurrentUser.IsAdmin;
bool userIsApprover = UserIsApprover();
@if ((!pcrbIsSubmitted && !string.IsNullOrWhiteSpace(pcrb.Title) && (userIsOriginator || userIsAdmin)) ||
(!pcrbIsSubmitted && pcrb.PlanNumber > 0 && (userIsOriginator || userIsAdmin))) {
(!pcrbIsSubmitted && pcrb.PlanNumber > 0 && (userIsOriginator || userIsAdmin))) {
<MudPaper Outlined="true"
Class="p-2 m-2 d-flex flex-wrap gap-3 justify-content-center align-content-center"
Elevation="10">
@ -61,9 +63,9 @@
}
@if (!pcrbIsSubmitted && pcrb.PlanNumber > 0 && (userIsOriginator || userIsAdmin)) {
<MudButton Variant="Variant.Filled"
Color="Color.Secondary"
Disabled="@deleteInProcess"
OnClick=DeletePCRB>
Color="Color.Secondary"
Disabled="@deleteInProcess"
OnClick=DeletePCRB>
@if (deleteInProcess) {
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
<MudText>Processing</MudText>
@ -91,114 +93,114 @@
}
<MudPaper Outlined="true"
Class="p-2 m-2 d-flex flex-wrap gap-3 justify-content-center align-content-start"
Elevation="10">
Class="p-2 m-2 d-flex flex-wrap gap-3 justify-content-center align-content-start"
Elevation="10">
<MudTextField @bind-Value=pcrb.PlanNumber
Text="@pcrb.PlanNumber.ToString()"
T="int"
Disabled="true"
Label="Change#"
Required
Variant="Variant.Outlined" />
Text="@pcrb.PlanNumber.ToString()"
T="int"
Disabled="true"
Label="Change#"
Required
Variant="Variant.Outlined" />
<MudTextField @bind-Value=pcrb.Title
Text="@pcrb.Title"
Disabled="@(pcrbIsSubmitted)"
T="string"
AutoGrow
AutoFocus
Immediate
Clearable
Required
Variant="Variant.Outlined"
Label="Project Name" />
Text="@pcrb.Title"
Disabled="@(pcrbIsSubmitted)"
T="string"
AutoGrow
AutoFocus
Immediate
Clearable
Required
Variant="Variant.Outlined"
Label="Project Name" />
<MudSelect T="User"
Label="Originator"
Variant="Variant.Outlined"
Required
Clearable
AnchorOrigin="Origin.BottomCenter"
ToStringFunc="@UserToNameConverter"
Disabled=@(pcrbIsSubmitted)
@bind-Value=@selectedOwner>
Label="Originator"
Variant="Variant.Outlined"
Required
Clearable
AnchorOrigin="Origin.BottomCenter"
ToStringFunc="@UserToNameConverter"
Disabled=@(pcrbIsSubmitted)
@bind-Value=@selectedOwner>
@foreach (User user in allActiveUsers.OrderBy(u => u.LastName)) {
<MudSelectItem T="User" Value="@(user)" />
}
</MudSelect>
<MudSelect T="string"
Variant="Variant.Outlined"
Required
Clearable
AnchorOrigin="Origin.BottomCenter"
Disabled="@pcrbIsSubmitted"
@bind-Value="@pcrb.ChangeLevel"
Text="@pcrb.ChangeLevel"
Label="Change Level">
Variant="Variant.Outlined"
Required
Clearable
AnchorOrigin="Origin.BottomCenter"
Disabled="@pcrbIsSubmitted"
@bind-Value="@pcrb.ChangeLevel"
Text="@pcrb.ChangeLevel"
Label="Change Level">
<MudSelectItem Value="@("Global - Class 1")" />
<MudSelectItem Value="@("Other Site + Mesa - Class 2")" />
<MudSelectItem Value="@("Mesa - Class 3")" />
</MudSelect>
<MudSelect T="string"
Variant="Variant.Outlined"
Required
Clearable
AnchorOrigin="Origin.BottomCenter"
Disabled="@pcrbIsSubmitted"
@bind-Value="@pcrb.Type"
Text="@pcrb.Type"
Label="Change Type">
Variant="Variant.Outlined"
Required
Clearable
AnchorOrigin="Origin.BottomCenter"
Disabled="@pcrbIsSubmitted"
@bind-Value="@pcrb.Type"
Text="@pcrb.Type"
Label="Change Type">
<MudSelectItem Value="@("Cost Savings")" />
<MudSelectItem Value="@("Process Efficiency")" />
<MudSelectItem Value="@("Process Improvement")" />
<MudSelectItem Value="@("Business Continuity")" />
</MudSelect>
<MudCheckBox Disabled="@pcrbIsSubmitted"
Color="Color.Tertiary"
@bind-Value=pcrb.IsITAR
Label="Export Controlled"
LabelPosition="LabelPosition.Start" />
Color="Color.Tertiary"
@bind-Value=pcrb.IsITAR
Label="Export Controlled"
LabelPosition="LabelPosition.Start" />
<MudTextField Disabled="true"
T="string"
Value="@DateTimeUtilities.GetDateAsStringMinDefault(pcrb.InsertTimeStamp)"
Label="Submit Date"
Variant="Variant.Outlined" />
T="string"
Value="@DateTimeUtilities.GetDateAsStringMinDefault(pcrb.InsertTimeStamp)"
Label="Submit Date"
Variant="Variant.Outlined" />
<MudTextField Disabled="true"
T="string"
Value="@DateTimeUtilities.GetDateAsStringMinDefault(pcrb.LastUpdateDate)"
Label="Last Update"
Variant="Variant.Outlined" />
T="string"
Value="@DateTimeUtilities.GetDateAsStringMinDefault(pcrb.LastUpdateDate)"
Label="Last Update"
Variant="Variant.Outlined" />
<MudTextField Disabled="true"
T="string"
Value="@DateTimeUtilities.GetDateAsStringMaxDefault(pcrb.ClosedDate)"
Label="Complete Date"
Variant="Variant.Outlined" />
T="string"
Value="@DateTimeUtilities.GetDateAsStringMaxDefault(pcrb.ClosedDate)"
Label="Complete Date"
Variant="Variant.Outlined" />
</MudPaper>
<MudPaper Outlined="true"
Class="p-2 m-2 d-flex flex-wrap gap-3 justify-content-center align-content-start"
Elevation="10">
Class="p-2 m-2 d-flex flex-wrap gap-3 justify-content-center align-content-start"
Elevation="10">
<MudTextField @bind-Value=pcrb.ChangeDescription
Text="@pcrb.ChangeDescription"
Disabled="@(pcrbIsSubmitted)"
T="string"
AutoGrow
Immediate
Clearable
Required
Variant="Variant.Outlined"
Label="Description Of Change" />
Text="@pcrb.ChangeDescription"
Disabled="@(pcrbIsSubmitted)"
T="string"
AutoGrow
Immediate
Clearable
Required
Variant="Variant.Outlined"
Label="Description Of Change" />
<MudTextField @bind-Value=pcrb.ReasonForChange
Text="@pcrb.ReasonForChange"
Disabled="@(pcrbIsSubmitted)"
T="string"
AutoGrow
Immediate
Clearable
Required
Variant="Variant.Outlined"
Label="Reason For Change" />
Text="@pcrb.ReasonForChange"
Disabled="@(pcrbIsSubmitted)"
T="string"
AutoGrow
Immediate
Clearable
Required
Variant="Variant.Outlined"
Label="Reason For Change" />
</MudPaper>
@if (pcrb.PlanNumber > 0 && pcrb.CurrentStep > 0) {
@if (pcrb.PlanNumber > 0 && pcrb.CurrentStep > (int)PCRB.StagesEnum.Draft) {
<MudExpansionPanels MultiExpansion="true">
@for (int i = 1; i < 4; i++) {
int current_i = i;
@ -229,7 +231,7 @@
int currentStagePendingApprovalsCount = currentStageApprovals.Where(a => a.ItemStatus == 0 && a.AssignedDate > DateTimeUtilities.MIN_DT).Count();
int currentStageApprovedApprovalsCount = currentStageApprovals.Where(a => a.ItemStatus == 1).Count();
int currentStageDeniedApprovalsCount = currentStageApprovals.Where(a => a.ItemStatus == -1).Count();
bool currentStageApproved = currentStageApprovedApprovalsCount >= 4 && currentStageUnsubmittedApprovalCount == 0 &&
currentStagePendingApprovalsCount == 0;
bool currentStageSubmitted = (currentStageApprovals.Count() > 0 && currentStagePendingApprovalsCount > 0 &&
@ -260,7 +262,7 @@
<TitleContent>
<MudText Typo="Typo.h4" Align="Align.Center">@($"PCR {current_i}")</MudText>
@if (previousStageSubmitted && (attachmentsMissing || actionItemsIncomplete ||
affectedDocumentsIncomplete || approvalsIncomplete)) {
affectedDocumentsIncomplete || approvalsIncomplete)) {
StringBuilder sb = new();
int missingSectionCount = 0;
sb.Append("Incomplete sections: ");
@ -299,31 +301,31 @@
</TitleContent>
<ChildContent>
<MudPaper Outlined="true"
Class="p-2 m-2 d-flex flex-column justify-start">
Class="p-2 m-2 d-flex flex-column justify-start">
<MudText Typo="Typo.h5" Align="Align.Center">Supporting Documents</MudText>
<MudTable Items="@attachments.Where(a => a.Step == current_i)"
Class="m-2"
Striped="true"
SortLabel="Sort By"
Hover="true">
Class="m-2"
Striped="true"
SortLabel="Sort By"
Hover="true">
<ToolBarContent>
<MudStack Row="true" Justify="Justify.Center" Spacing="1" Style="width: 100%">
@if (pcrb.ClosedDate >= DateTimeUtilities.MAX_DT && previousStageSubmitted && !currentStageSubmitted) {
@if (current_i == 1) {
<MudButton Variant="Variant.Filled"
Color="Color.Tertiary"
Href="https://plm.intra.infineon.com/Windchill/netmarkets/jsp/ext/infineon/dcoidreleased.jsp?obid=OR:wt.doc.WTDocument:1477717325"
Target="_blank">
Color="Color.Tertiary"
Href="https://plm.intra.infineon.com/Windchill/netmarkets/jsp/ext/infineon/dcoidreleased.jsp?obid=OR:wt.doc.WTDocument:1477717325"
Target="_blank">
Download PCRB Template
</MudButton>
}
<MudButton Variant="Variant.Filled"
Color="Color.Tertiary"
OnClick="@((e) => UploadAttachment(current_i))"
Disabled="@attachmentUploadInProcess"
StartIcon="@Icons.Material.Filled.AttachFile">
Color="Color.Tertiary"
OnClick="@((e) => UploadAttachment(current_i))"
Disabled="@attachmentUploadInProcess"
StartIcon="@Icons.Material.Filled.AttachFile">
@if (attachmentUploadInProcess) {
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
<MudText>Processing</MudText>
@ -354,8 +356,8 @@
<RowTemplate>
<MudTd DataLabel="File Name">
<a href="@(@$"{config["FabApprovalApiBaseUrl"]}/pcrb/attachmentFile?path={context.Path}&fileName={context.FileName}")"
download="@(context.FileName)"
target="_top">
download="@(context.FileName)"
target="_top">
@context.FileName
</a>
</MudTd>
@ -364,9 +366,9 @@
@if (pcrb.ClosedDate >= DateTimeUtilities.MAX_DT && previousStageSubmitted && !currentStageSubmitted) {
<MudTd Style="text-align:center;">
<MudButton Color="Color.Secondary"
Variant="Variant.Filled"
Disabled="@deleteAttachmentInProcess"
OnClick="@((e) => DeleteAttachment(context))">
Variant="Variant.Filled"
Disabled="@deleteAttachmentInProcess"
OnClick="@((e) => DeleteAttachment(context))">
@if (deleteAttachmentInProcess) {
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
<MudText>Deleting</MudText>
@ -389,24 +391,24 @@
</MudText>
}
<MudTable Items="@actionItems.Where(a => a.Step == current_i)"
Class="m-2"
Striped="true"
SortLabel="Sort By"
Hover="true">
Class="m-2"
Striped="true"
SortLabel="Sort By"
Hover="true">
<ToolBarContent>
<MudStack Row="true" Justify="Justify.Center" Spacing="1" Style="width: 100%">
@if (previousStageSubmitted && !currentStageSubmitted) {
<MudButton Variant="Variant.Filled"
Color="Color.Tertiary"
OnClick="@((e) => CreateNewActionItem(current_i))">
Color="Color.Tertiary"
OnClick="@((e) => CreateNewActionItem(current_i))">
<MudText>New Action Item</MudText>
</MudButton>
}
@if (currentStagePendingActionItemCount > 0) {
<MudButton Variant="Variant.Filled"
Color="Color.Tertiary"
Disabled="@processing"
OnClick="@((e) => CloseAllActionItems(current_i))">
Color="Color.Tertiary"
Disabled="@processing"
OnClick="@((e) => CloseAllActionItems(current_i))">
<MudText>Complete All Actions</MudText>
</MudButton>
}
@ -448,14 +450,14 @@
@if (pcrb.ClosedDate >= DateTimeUtilities.MAX_DT && context.ClosedByID == 0) {
<MudTd Style="text-align:center;">
<MudButton Color="Color.Tertiary"
Variant="Variant.Filled"
OnClick="@((e) => UpdateActionItem(context))">
Variant="Variant.Filled"
OnClick="@((e) => UpdateActionItem(context))">
<MudText>Update</MudText>
</MudButton>
@if (!currentStageSubmitted) {
<MudButton Color="Color.Secondary"
Variant="Variant.Filled"
OnClick="@((e) => DeleteActionItem(context))">
Variant="Variant.Filled"
OnClick="@((e) => DeleteActionItem(context))">
<MudText>Delete</MudText>
</MudButton>
}
@ -468,10 +470,10 @@
<MudText Typo="Typo.h5" Align="Align.Center">Affected Documents</MudText>
<MudTable Items="@pcr3Documents"
Class="m-2"
Striped="true"
SortLabel="Sort By"
Hover="true">
Class="m-2"
Striped="true"
SortLabel="Sort By"
Hover="true">
<HeaderContent>
<MudTh>
<MudTableSortLabel SortBy="new Func<PCR3Document, object>(x=>x.DocType)">
@ -513,8 +515,8 @@
context.GetEcnNumberString();
} else {
<MudLink
Href=@($"{config["OldFabApprovalUrl"]}/ECN/Edit?IssueID={context.GetEcnNumberString()}")
Target="_blank">
Href=@($"{config["OldFabApprovalUrl"]}/ECN/Edit?IssueID={context.GetEcnNumberString()}")
Target="_blank">
@context.GetEcnNumberString()
</MudLink>
}
@ -526,8 +528,8 @@
@if (pcrb.ClosedDate >= DateTimeUtilities.MAX_DT && !currentStageSubmitted) {
<MudTd Style="text-align:center;">
<MudButton Color="Color.Tertiary"
Variant="Variant.Filled"
OnClick="@((e) => UpdatePCR3Document(context))">
Variant="Variant.Filled"
OnClick="@((e) => UpdatePCR3Document(context))">
<MudText>Update</MudText>
</MudButton>
</MudTd>
@ -539,23 +541,23 @@
<MudDivider DividerType="DividerType.Middle" Class="my-1" />
<MudText Typo="Typo.h5" Align="Align.Center">Attendees</MudText>
<MudTable Items="@attendees.Where(a => a.Step == current_i)"
Class="m-2"
Striped="true"
SortLabel="Sort By"
Hover="true">
Class="m-2"
Striped="true"
SortLabel="Sort By"
Hover="true">
<ToolBarContent>
@if (pcrb.ClosedDate >= DateTimeUtilities.MAX_DT && !currentStageSubmitted) {
<MudStack Row="true" Justify="Justify.Center" Spacing="1" Style="width: 100%">
<MudButton Color="Color.Tertiary"
Variant="Variant.Filled"
Class="m-1"
OnClick="@((e) => AddAttendee(current_i))">
Variant="Variant.Filled"
Class="m-1"
OnClick="@((e) => AddAttendee(current_i))">
<MudText>Add Attendee</MudText>
</MudButton>
<MudButton Color="Color.Tertiary"
Variant="Variant.Filled"
Class="m-1"
OnClick="@((e) => MarkAllAttended(current_i))">
Variant="Variant.Filled"
Class="m-1"
OnClick="@((e) => MarkAllAttended(current_i))">
<MudText>Mark All Attended</MudText>
</MudButton>
</MudStack>
@ -577,9 +579,9 @@
</MudTd>
<MudTd DataLabel="Attended?">
<MudIconButton Disabled="@(pcrb.ClosedDate < DateTimeUtilities.MAX_DT || currentStageSubmitted)"
Icon="@(context.Attended ? Icons.Material.Filled.CheckBox : Icons.Material.Filled.CheckBoxOutlineBlank)"
Color="Color.Tertiary"
OnClick="@((e) => MarkAttended(context))" />
Icon="@(context.Attended ? Icons.Material.Filled.CheckBox : Icons.Material.Filled.CheckBoxOutlineBlank)"
Color="Color.Tertiary"
OnClick="@((e) => MarkAttended(context))" />
</MudTd>
</RowTemplate>
</MudTable>
@ -592,33 +594,33 @@
</MudText>
}
<MudTable Items="@approvals.Where(a => a.Step == current_i).OrderBy(a => a.CompletedDate)"
Class="m-2"
Striped="true"
SortLabel="Sort By"
Hover="true">
Class="m-2"
Striped="true"
SortLabel="Sort By"
Hover="true">
<ToolBarContent>
@if (pcrb.ClosedDate >= DateTimeUtilities.MAX_DT && previousStageApproved) {
<MudStack Row="true" Justify="Justify.Center" Spacing="1" Style="width: 100%">
@if (!currentStageSubmitted) {
<MudButton Variant="Variant.Filled"
Color="Color.Tertiary"
OnClick="@((e) => AddApprover(current_i))">
Color="Color.Tertiary"
OnClick="@((e) => AddApprover(current_i))">
<MudText>Add Approver</MudText>
</MudButton>
}
@if (previousStageSubmitted && !currentStageSubmitted && currentStageAttachments.Count() > 0 &&
!affectedDocumentsIncomplete && allActionItemsComplete &&
!previousStageHasOpenGatedActionItems && atLeastOneAttendeeAttended) {
!affectedDocumentsIncomplete && allActionItemsComplete &&
!previousStageHasOpenGatedActionItems && atLeastOneAttendeeAttended) {
@if (submitInProcess) {
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
<MudText>Submitting</MudText>
} else {
<MudButton Variant="Variant.Filled"
Color="Color.Tertiary"
Disabled="@submitInProcess"
OnClick="@((e) => SubmitForApproval(current_i))">
<MudText>Submit For Approval</MudText>
</MudButton>
Color="Color.Tertiary"
Disabled="@submitInProcess"
OnClick="@((e) => SubmitForApproval(current_i))">
<MudText>Submit For Approval</MudText>
</MudButton>
}
}
</MudStack>
@ -652,31 +654,31 @@
<MudTd DataLabel="Disposition Date">@DateTimeUtilities.GetDateAsStringMaxDefault(context.CompletedDate)</MudTd>
<MudTd DataLabel="Comments">@context.Comments</MudTd>
@if (pcrb.ClosedDate >= DateTimeUtilities.MAX_DT && (currentStageUnsubmittedApprovalCount > 0 ||
currentStagePendingApprovalsCount > 0)) {
currentStagePendingApprovalsCount > 0)) {
<MudTd Style="text-align:center;">
@if (context.ItemStatus == 0 && userIsAdmin) {
<MudButton Color="Color.Warning"
Variant="Variant.Filled"
Class="m-1"
OnClick="@((e) => UpdateApproval(context))">
Variant="Variant.Filled"
Class="m-1"
OnClick="@((e) => UpdateApproval(context))">
<MudText>Update</MudText>
</MudButton>
}
@if ((current_i < 3 || pcr3Documents.Where(d=>d.CompletedByID == 0).Count() == 0) &&
(authStateProvider.CurrentUser is not null && context.UserID == authStateProvider.CurrentUser.UserID) &&
context.ItemStatus == 0 && context.AssignedDate > DateTimeUtilities.MIN_DT) {
(authStateProvider.CurrentUser is not null && context.UserID == authStateProvider.CurrentUser.UserID) &&
context.ItemStatus == 0 && context.AssignedDate > DateTimeUtilities.MIN_DT) {
<MudButton Color="Color.Tertiary"
Variant="Variant.Filled"
Class="m-1"
Disabled="@processing"
OnClick="@((e) => ApprovePCR(current_i))">
Variant="Variant.Filled"
Class="m-1"
Disabled="@processing"
OnClick="@((e) => ApprovePCR(current_i))">
<MudText>Approve</MudText>
</MudButton>
<MudButton Color="Color.Secondary"
Variant="Variant.Filled"
Class="m-1"
Disabled="@processing"
OnClick="@((e) => DenyPCR(current_i))">
Variant="Variant.Filled"
Class="m-1"
Disabled="@processing"
OnClick="@((e) => DenyPCR(current_i))">
<MudText>Deny</MudText>
</MudButton>
}
@ -688,6 +690,195 @@
</ChildContent>
</MudExpansionPanel>
}
@if (pcrb.FollowUps.Count() > 0) {
<MudExpansionPanel Class="m-2" Expanded=@(pcrb.CurrentStep == (int)PCRB.StagesEnum.FollowUp &&
!pcrb.FollowUps.First().IsComplete)>
<TitleContent>
<MudText Typo="Typo.h4" Align="Align.Center">Follow Up</MudText>
</TitleContent>
<ChildContent>
<MudPaper Outlined="true"
Class="p-2 m-2 d-flex flex-column justify-start gap-2">
<MudPaper Outlined="true"
Class="p-1"
Elevation="15">
<MudText Typo="Typo.h5" Align="Align.Center">Supporting Documents</MudText>
<MudTable Items="@attachments.Where(a => a.Step == 5)"
Class="m-2"
Striped="true"
SortLabel="Sort By"
Hover="true">
<ToolBarContent>
<MudStack Row="true" Justify="Justify.Center" Spacing="1" Style="width: 100%">
@if (!pcrb.FollowUps.First().IsComplete && !pcrb.FollowUps.First().IsPendingApproval) {
<MudButton Variant="Variant.Filled"
Color="Color.Tertiary"
OnClick="@((e) => UploadAttachment(5))"
Disabled="@attachmentUploadInProcess"
StartIcon="@Icons.Material.Filled.AttachFile">
@if (attachmentUploadInProcess) {
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
<MudText>Processing</MudText>
}
else {
<MudText>Upload Document</MudText>
}
</MudButton>
}
@if (!pcrb.FollowUps.First().IsPendingApproval && !pcrb.FollowUps.First().IsComplete &&
attachments.Where(a => a.Step == 5).Count() > 0 &&
approvals.Where(a => a.Step == 5 &&
a.UserID == authStateProvider.CurrentUser.UserID &&
a.ItemStatus == 0).Count() > 0) {
<MudButton Variant="Variant.Filled"
Color="Color.Tertiary"
OnClick="SubmitFollowUpForApproval"
Disabled="@followUpSubmitInProcess">
@if (followUpSubmitInProcess) {
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
<MudText>Processing</MudText>
}
else {
<MudText>Submit For Closure</MudText>
}
</MudButton>
}
@if (pcrb.FollowUps.First().IsPendingApproval && !pcrb.FollowUps.First().IsComplete &&
attachments.Where(a => a.Step == 5).Count() > 0 &&
approvals.Where(a => a.Step == 5 &&
a.UserID == authStateProvider.CurrentUser.UserID).Count() > 0)
{
@if (userIsQA) {
<MudButton Variant="Variant.Filled"
Color="Color.Tertiary"
OnClick="ApproveFollowUp"
Disabled="@followUpApproveInProcess">
@if (followUpApproveInProcess) {
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
<MudText>Processing</MudText>
} else {
<MudText>Close</MudText>
}
</MudButton>
<MudButton Variant="Variant.Filled"
Color="Color.Secondary"
OnClick="@((e) => DenyFollowUp("Reject"))"
Disabled="@followUpDenyInProcess">
@if (followUpDenyInProcess) {
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
<MudText>Processing</MudText>
} else {
<MudText>Reject</MudText>
}
</MudButton>
} else
{
<MudButton Variant="Variant.Filled"
Color="Color.Secondary"
OnClick="@((e) => DenyFollowUp("Recall"))"
Disabled="@followUpDenyInProcess">
@if (followUpDenyInProcess) {
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
<MudText>Processing</MudText>
} else {
<MudText>Recall</MudText>
}
</MudButton>
}
}
</MudStack>
</ToolBarContent>
<HeaderContent>
<MudTh>
<MudTableSortLabel SortBy="new Func<PCRBAttachment, object>(x=>x.FileName)">
File Name
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<PCRBAttachment, object>(x=>x.UploadedBy is null ? string.Empty : x.UploadedBy.LastName)">
Uploaded By
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<PCRBAttachment, object>(x=>x.UploadDateTime)">
Uploaded Date
</MudTableSortLabel>
</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="File Name">
<a href="@(@$"{config["FabApprovalApiBaseUrl"]}/pcrb/attachmentFile?path={context.Path}&fileName={context.FileName}")"
download="@(context.FileName)"
target="_top">
@context.FileName
</a>
</MudTd>
<MudTd DataLabel="Uploaded By">@(context.UploadedBy is null ? string.Empty : context.UploadedBy.GetFullName())</MudTd>
<MudTd DataLabel="Uploaded Date">@context.UploadDateTime.ToString("yyyy-MM-dd HH:mm")</MudTd>
@if (!pcrb.FollowUps.First().IsComplete && !pcrb.FollowUps.First().IsPendingApproval)
{
<MudTd Style="text-align:center;">
<MudButton Color="Color.Secondary"
Variant="Variant.Filled"
Disabled="@deleteAttachmentInProcess"
OnClick="@((e) => DeleteAttachment(context))">
@if (deleteAttachmentInProcess)
{
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
<MudText>Deleting</MudText>
}
else
{
<MudText>Delete</MudText>
}
</MudButton>
</MudTd>
}
</RowTemplate>
</MudTable>
</MudPaper>
<MudPaper Outlined="true"
Class="p-2 d-flex flex-column flex-wrap">
<MudText Typo="Typo.h5" Align="Align.Center">Follow Up Date</MudText>
<MudDatePicker Label="Follow Up Date"
Date="pcrb.FollowUps.First().FollowUpDate"
MinDate="@(pcrb.ClosedDate)"
Color="@Color.Tertiary"
Disabled="@(!userIsQA ||
pcrb.FollowUps.Count() == 0 ||
pcrb.FollowUps.First().IsComplete ||
pcrb.FollowUps.First().IsPendingApproval)"
Rounded="true"
Error="false"
Elevation="6"
DateChanged="UpdateFollowUpDate"
Variant="Variant.Outlined" />
</MudPaper>
@if (followUpComments.Count() > 0) {
<MudPaper Outlined="true" Class="p-1" Elevation="15">
<MudText Typo="Typo.h5" Align="Align.Center">Revision Comments</MudText>
<MudTable Items="@followUpComments"
Class="m-2"
Striped="true"
Hover="true">
<HeaderContent>
<MudTh>Date</MudTh>
<MudTh>User</MudTh>
<MudTh>Comment</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Date">@context.CommentDate.ToString("MM/dd/yyyy hh:mm")</MudTd>
<MudTd DataLabel="User">@(context.User?.GetFullName() ?? string.Empty)</MudTd>
<MudTd DataLabel="Comment">@context.Comment</MudTd>
</RowTemplate>
</MudTable>
</MudPaper>
}
</MudPaper>
</ChildContent>
</MudExpansionPanel>
}
</MudExpansionPanels>
}
}

View File

@ -34,36 +34,39 @@ public partial class PCRBSingle {
private IEnumerable<PCRBAttachment> attachments = new List<PCRBAttachment>();
private IEnumerable<PCRBActionItem> actionItems = new List<PCRBActionItem>();
private IEnumerable<PCR3Document> pcr3Documents = new List<PCR3Document>();
private IEnumerable<PCRBFollowUpComment> followUpComments = new List<PCRBFollowUpComment>();
private DateTime? followUpDate;
private IEnumerable<int> qualityApproverUserIds = new List<int>();
private IEnumerable<User> allActiveUsers = new List<User>();
private User selectedOwner = null;
private bool userIsQA = false;
private bool processing = false;
private bool saveInProcess = false;
private bool deleteInProcess = false;
private bool submitInProcess = false;
private bool approvalInProcess = false;
private bool denialInProcess = false;
private bool recallInProcess = false;
private bool attachmentUploadInProcess = false;
private bool updateAttachmentInProcess = false;
private bool deleteAttachmentInProcess = false;
private bool addActionItemInProcess = false;
private string attachmentSearchString = "";
private string actionItemSearchString = "";
private bool followUpSubmitInProcess = false;
private bool followUpApproveInProcess = false;
private bool followUpDenyInProcess = false;
protected override async Task OnParametersSetAsync() {
processing = true;
try {
cache.Set("redirectUrl", $"pcrb/{planNumber}");
allActiveUsers = await userService.GetAllActiveUsers();
if (qualityApproverUserIds.Count() == 0)
qualityApproverUserIds = await GetQualityApproverUserIds();
userIsQA = qualityApproverUserIds.Contains(authStateProvider.CurrentUser?.UserID ?? -1);
if (!string.IsNullOrWhiteSpace(planNumber) && Int32.TryParse(planNumber, out planNumberInt)) {
pcrb = await pcrbService.GetPCRBByPlanNumber(planNumberInt, false);
approvals = await approvalService.GetApprovalsForIssueId(planNumberInt, false);
@ -71,6 +74,10 @@ public partial class PCRBSingle {
attachments = await pcrbService.GetAttachmentsByPlanNumber(planNumberInt, false);
actionItems = await pcrbService.GetActionItemsForPlanNumber(planNumberInt, false);
pcr3Documents = await pcrbService.GetPCR3DocumentsForPlanNumber(planNumberInt, false);
followUpComments = await pcrbService.GetFollowUpCommentsByPlanNumber(planNumberInt, false);
if (followUpDate is null)
followUpDate = pcrb.FollowUps.Count() > 0 ? pcrb.FollowUps.First().FollowUpDate : DateTimeUtilities.MAX_DT;
List<Task> createPCR3DocumentTasks = new();
if (pcr3Documents.Count() <= 0) {
@ -117,9 +124,9 @@ public partial class PCRBSingle {
if (pcrb.OwnerID > 0) selectedOwner = await userService.GetUserByUserId(pcrb.OwnerID);
if (pcrb.CurrentStep > 0 && pcrb.CurrentStep < 4) {
if (pcrb.CurrentStep > (int)PCRB.StagesEnum.Draft && pcrb.CurrentStep < (int)PCRB.StagesEnum.Complete) {
bool stageHasAdvanced = false;
for (int stage = pcrb.CurrentStep; stage < 4; stage++) {
for (int stage = pcrb.CurrentStep; stage < (int)PCRB.StagesEnum.Complete; stage++) {
int current_stage = stage;
if (pcrb.CurrentStep == current_stage) {
IEnumerable<Approval> currentStageApprovals = approvals.Where(a => a.Step == current_stage);
@ -128,7 +135,7 @@ public partial class PCRBSingle {
bool currentStageApproved = currentStageApprovedApprovalsCount >= 3 && currentStagePendingApprovalsCount == 0;
if (currentStageApproved) {
if (pcrb.CurrentStep == 3) {
if (pcrb.CurrentStep == (int)PCRB.StagesEnum.PCR3) {
int openActionItemCount = actionItems.Where(a => a.ClosedByID == 0).Count();
int openAffectedDocumentsCount = pcr3Documents.Where(d => d.CompletedByID == 0).Count();
@ -145,7 +152,7 @@ public partial class PCRBSingle {
}
if (stageHasAdvanced) {
if (pcrb.CurrentStep == 4) {
if (pcrb.CurrentStep == (int)PCRB.StagesEnum.Complete) {
pcrb.ClosedDate = DateTime.Now;
string message = $"PCRB# {pcrb.PlanNumber} - {pcrb.Title} is complete";
@ -165,6 +172,34 @@ public partial class PCRBSingle {
await OnParametersSetAsync();
}
}
if (pcrb.CurrentStep == (int)PCRB.StagesEnum.Complete && pcrb.FollowUps.Count() == 0) {
PCRBFollowUp followUp = new() {
PlanNumber = pcrb.PlanNumber,
Step = (int)PCRB.StagesEnum.FollowUp,
FollowUpDate = pcrb.ClosedDate.AddMonths(6)
};
await pcrbService.CreateFollowUp(followUp);
pcrb = await pcrbService.GetPCRBByPlanNumber(pcrb.PlanNumber, true);
if (pcrb.FollowUps.Count() == 0)
throw new Exception("unable to create follow up");
StateHasChanged();
await OnParametersSetAsync();
}
if (pcrb.CurrentStep == (int)PCRB.StagesEnum.Complete && pcrb.FollowUps.Count() > 0 &&
DateTime.Now >= pcrb.FollowUps.First().FollowUpDate.AddDays(-15)) {
pcrb.CurrentStep = (int)PCRB.StagesEnum.FollowUp;
await pcrbService.UpdatePCRB(pcrb);
pcrb = await pcrbService.GetPCRBByPlanNumber(pcrb.PlanNumber, true);
StateHasChanged();
await OnParametersSetAsync();
}
} else {
int ownerID = 0;
string ownerName = string.Empty;
@ -177,7 +212,7 @@ public partial class PCRBSingle {
pcrb = new() {
OwnerID = ownerID,
OwnerName = ownerName,
CurrentStep = 0
CurrentStep = (int)PCRB.StagesEnum.Draft
};
}
@ -215,7 +250,7 @@ public partial class PCRBSingle {
private bool PCRBReadyToSubmit(int step) {
bool readyToSubmit = GetIncompleteFields().Count() <= 0;
readyToSubmit = readyToSubmit && pcrb.CurrentStep > 0;
readyToSubmit = readyToSubmit && pcrb.CurrentStep > (int)PCRB.StagesEnum.Draft;
readyToSubmit = readyToSubmit && attachments.Where(a => a.Step == step).Count() > 0;
@ -241,7 +276,7 @@ public partial class PCRBSingle {
pcrb.OwnerID = selectedOwner.UserID;
pcrb.OwnerName = selectedOwner.GetFullName();
if (pcrb.CurrentStep == 0 && GetIncompleteFields().Count() <= 0)
if (pcrb.CurrentStep == (int)PCRB.StagesEnum.Draft && GetIncompleteFields().Count() <= 0)
pcrb.CurrentStep++;
if (initialPlanNumber <= 0) {
@ -300,34 +335,54 @@ public partial class PCRBSingle {
}
private async Task<IEnumerable<int>> GetQualityApproverUserIds() {
List<int> qualityApproverUserIds = new();
try {
int roleId = await approvalService.GetRoleIdForRoleName("Module Manager");
HashSet<int>? qualityApproverUserIds = cache.Get<HashSet<int>>("qualityApproverUserIds");
if (roleId <= 0) throw new Exception($"could not find Module Manager role ID");
if (qualityApproverUserIds is null || qualityApproverUserIds.Count() == 0) {
qualityApproverUserIds = new();
IEnumerable<SubRole> subRoles = await approvalService.GetSubRolesForSubRoleName("MMSubRole", roleId);
int roleId = await approvalService.GetRoleIdForRoleName("Module Manager");
foreach (SubRole subRole in subRoles) {
if (subRole.SubRoleCategoryItem.Equals("Quality", StringComparison.InvariantCultureIgnoreCase)) {
if (roleId <= 0) throw new Exception($"could not find Module Manager role ID");
IEnumerable<SubRole> subRoles = await approvalService.GetSubRolesForSubRoleName("MMSubRole", roleId);
foreach (SubRole subRole in subRoles) {
if (subRole.SubRoleCategoryItem.Equals("Quality", StringComparison.InvariantCultureIgnoreCase)) {
IEnumerable<User> subRoleMembers = await approvalService.GetApprovalGroupMembers(subRole.SubRoleID);
foreach (User user in subRoleMembers) qualityApproverUserIds.Add(user.UserID);
}
}
string roleName = "QA_PRE_APPROVAL";
string subRoleName = "QA_PRE_APPROVAL";
roleId = await approvalService.GetRoleIdForRoleName(roleName);
if (roleId <= 0) throw new Exception($"could not find {roleName} role ID");
subRoles = await approvalService.GetSubRolesForSubRoleName(subRoleName, roleId);
foreach (SubRole subRole in subRoles) {
IEnumerable<User> subRoleMembers = await approvalService.GetApprovalGroupMembers(subRole.SubRoleID);
foreach (User user in subRoleMembers) qualityApproverUserIds.Add(user.UserID);
}
}
string roleName = "QA_PRE_APPROVAL";
string subRoleName = "QA_PRE_APPROVAL";
roleName = "QA_FINAL_APPROVAL";
subRoleName = "QA_FINAL_APPROVAL";
roleId = await approvalService.GetRoleIdForRoleName(roleName);
roleId = await approvalService.GetRoleIdForRoleName(roleName);
if (roleId <= 0) throw new Exception($"could not find {roleName} role ID");
if (roleId <= 0) throw new Exception($"could not find {roleName} role ID");
subRoles = await approvalService.GetSubRolesForSubRoleName(subRoleName, roleId);
subRoles = await approvalService.GetSubRolesForSubRoleName(subRoleName, roleId);
foreach (SubRole subRole in subRoles) {
IEnumerable<User> subRoleMembers = await approvalService.GetApprovalGroupMembers(subRole.SubRoleID);
foreach (User user in subRoleMembers) qualityApproverUserIds.Add(user.UserID);
foreach (SubRole subRole in subRoles) {
IEnumerable<User> subRoleMembers = await approvalService.GetApprovalGroupMembers(subRole.SubRoleID);
foreach (User user in subRoleMembers) qualityApproverUserIds.Add(user.UserID);
}
cache.Set("qualityApproverUserIds", qualityApproverUserIds);
}
return qualityApproverUserIds;
@ -445,9 +500,7 @@ public partial class PCRBSingle {
Approval? latestQaPreApproval = currentStageApprovals
.Where(a => a.SubRoleCategoryItem.Equals("QA Pre Approver"))
.OrderByDescending(a => a.AssignedDate)
.FirstOrDefault();
if (latestQaPreApproval is null) throw new Exception("QA pre approval not found");
.FirstOrDefault() ?? throw new Exception("QA pre approval not found");
bool qaPreApprovalDenied = latestQaPreApproval.ItemStatus == -1;
if (qaPreApprovalDenied && currentStageUnsubmittedApprovalCount >= 3) {
@ -493,9 +546,8 @@ public partial class PCRBSingle {
await Task.WhenAll(createCopiedApprovalsTasks);
} else {
Approval? unassignedQaPreApproval = currentStageApprovals.Where(a => a.SubRoleCategoryItem.Equals("QA Pre Approver") &&
a.AssignedDate <= DateTimeUtilities.MIN_DT).FirstOrDefault();
if (unassignedQaPreApproval is null) throw new Exception("unassigned QA pre approval not found");
a.AssignedDate <= DateTimeUtilities.MIN_DT).FirstOrDefault() ??
throw new Exception("unassigned QA pre approval not found");
unassignedQaPreApproval.AssignedDate = DateTime.Now;
await approvalService.UpdateApproval(unassignedQaPreApproval);
@ -505,7 +557,7 @@ public partial class PCRBSingle {
await pcrbService.NotifyNewApprovals(pcrb);
if (pcrb.CurrentStep == 1) {
if (pcrb.CurrentStep == (int)PCRB.StagesEnum.PCR1) {
pcrb.InsertTimeStamp = DateTime.Now;
await pcrbService.UpdatePCRB(pcrb);
@ -525,23 +577,6 @@ public partial class PCRBSingle {
}
}
private async Task SetUserForApproval(Approval approval, User user) {
if (approval is null) throw new ArgumentNullException("approval cannot be null");
if (user is null) throw new ArgumentNullException("user cannot be null");
if (approval.CompletedDate < DateTimeUtilities.MAX_DT || approval.ItemStatus != 0)
throw new ArgumentException("cannot reassign a complete approval");
approval.UserID = user.UserID;
approval.User = user;
approval.NotifyDate = DateTimeUtilities.MIN_DT;
await approvalService.UpdateApproval(approval);
await approvalService.GetApprovalsForIssueId(approval.IssueID, true);
await pcrbService.NotifyNewApprovals(pcrb);
}
private async Task ApprovePCR(int step) {
if (!processing) {
try {
@ -1136,4 +1171,328 @@ public partial class PCRBSingle {
if (itemStatus > 0) return "Approved";
return "Pending";
}
private async Task UpdateFollowUpDate(DateTime? newFollowUpDate) {
if (followUpDate is not null || followUpDate <= DateTimeUtilities.MAX_DT) {
try {
if (newFollowUpDate is null)
throw new Exception("follow up date cannot be null");
if (authStateProvider.CurrentUser is null) {
snackbar.Add("You must log in to change the follow up date", Severity.Error);
await authStateProvider.Logout();
return;
}
DateTime oldFollowUpDate = pcrb.FollowUps.First().FollowUpDate;
followUpDate = newFollowUpDate;
pcrb.FollowUps.First().FollowUpDate = (DateTime)newFollowUpDate;
await pcrbService.UpdateFollowUp(pcrb.FollowUps.First());
pcrb = await pcrbService.GetPCRBByPlanNumber(pcrb.PlanNumber, true);
string comments = "";
DialogParameters<Comments> parameters = new DialogParameters<Comments> { { x => x.comments, comments } };
var dialog = await dialogService.ShowAsync<Comments>($"Follow Up Date Change Comment", parameters);
DialogResult? result = await dialog.Result;
if (result is null || result.Canceled || result.Data is null || string.IsNullOrWhiteSpace(result.Data?.ToString())) {
followUpDate = oldFollowUpDate;
pcrb.FollowUps.First().FollowUpDate = oldFollowUpDate;
await pcrbService.UpdateFollowUp(pcrb.FollowUps.First());
pcrb = await pcrbService.GetPCRBByPlanNumber(pcrb.PlanNumber, true);
throw new Exception("you must provide a comment");
}
comments = result.Data?.ToString() ?? string.Empty;
comments = comments.Trim();
StringBuilder commentBuilder = new();
commentBuilder.Append($"Changing follow up date from {oldFollowUpDate.ToString("MM/dd/yyyy")} ");
commentBuilder.Append($"to {pcrb.FollowUps.First().FollowUpDate.ToString("MM/dd/yyyy")}. Comments: {comments}");
PCRBFollowUpComment comment = new() {
PlanNumber = pcrb.FollowUps.First().PlanNumber,
FollowUpID = pcrb.FollowUps.First().ID,
Comment = commentBuilder.ToString(),
UserID = authStateProvider.CurrentUser.UserID
};
await pcrbService.CreateFollowUpComment(comment);
DateTime fifteenDaysFromNow = DateTime.Now.AddDays(15);
if (pcrb.FollowUps.First().FollowUpDate > fifteenDaysFromNow) {
IEnumerable<Approval> step5Approvals = approvals.Where(a => a.Step == 5 && a.ItemStatus == 0);
foreach (Approval approval in step5Approvals) {
await approvalService.DeleteApproval(approval.ApprovalID);
await approvalService.GetApprovalsForUserId(approval.UserID, true);
}
approvals = await approvalService.GetApprovalsForIssueId(pcrb.PlanNumber, true);
} else if (approvals.Where(a => a.Step == 5 && a.ItemStatus == 0).Count() == 0) {
Approval newApproval = new Approval {
IssueID = pcrb.PlanNumber,
RoleName = "PCRB Owner Follow Up",
SubRole = "PCRBOwnerFollowUp",
SubRoleCategoryItem = "PCRB Owner Follow Up",
UserID = pcrb.OwnerID,
SubRoleID = 999,
AssignedDate = DateTime.Now,
TaskID = pcrb.FollowUps.First().ID,
Step = 5,
NotifyDate = DateTime.Now
};
await approvalService.CreateApproval(newApproval);
}
commentBuilder.Clear();
commentBuilder.Append($"Effectiveness review date for PCRB# {pcrb.PlanNumber} - {pcrb.Title} has been changed to ");
commentBuilder.Append($"{pcrb.FollowUps.First().FollowUpDate.ToString("MM/dd/yyyy")}. ");
PCRBNotification notification = new() {
Message = commentBuilder.ToString(),
Subject = $"[PCRB Effectiveness Review Date Change] {pcrb.PlanNumber} - {pcrb.Title}",
PCRB = pcrb,
NotifyQaPreApprover = true
};
await pcrbService.NotifyOriginator(notification);
} catch (Exception ex) {
snackbar.Add($"Unable to update follow up date, because {ex.Message}", Severity.Error);
}
StateHasChanged();
await OnParametersSetAsync();
}
}
private async Task SubmitFollowUpForApproval() {
if (!followUpSubmitInProcess) {
try {
followUpSubmitInProcess = true;
if (pcrb.FollowUps.Count() > 0) {
PCRBFollowUp followUp = pcrb.FollowUps.First();
followUp.IsPendingApproval = true;
await pcrbService.UpdateFollowUp(followUp);
List<SubRole> allSubRoles = new();
int roleId = await approvalService.GetRoleIdForRoleName("Module Manager");
if (roleId <= 0) throw new Exception($"could not find Module Manager role ID");
List<SubRole> qualityMMSubRoles = (await approvalService.GetSubRolesForSubRoleName("MMSubRole", roleId)).ToList();
foreach (SubRole subRole in qualityMMSubRoles) {
if (subRole.SubRoleCategoryItem.Equals("Quality"))
allSubRoles.Add(subRole);
}
roleId = await approvalService.GetRoleIdForRoleName("QA_FINAL_APPROVAL");
if (roleId <= 0) throw new Exception($"could not find QA Final Approval role ID");
IEnumerable<SubRole> qaFinalApprovalSubRoles =
(await approvalService.GetSubRolesForSubRoleName("QA_FINAL_APPROVAL", roleId)).ToList();
foreach (SubRole subRole in qaFinalApprovalSubRoles)
allSubRoles.Add(subRole);
foreach (SubRole subRole in allSubRoles) {
IEnumerable<User> subRoleMembers = await approvalService.GetApprovalGroupMembers(subRole.SubRoleID);
foreach (User member in subRoleMembers) {
Approval approval = new() {
IssueID = pcrb.PlanNumber,
RoleName = subRole.SubRoleCategoryItem,
SubRole = subRole.SubRoleName,
UserID = member.UserID,
SubRoleID = subRole.SubRoleID,
AssignedDate = DateTime.Now,
Step = followUp.Step,
TaskID = followUp.ID
};
await approvalService.CreateApproval(approval);
approvals = await approvalService.GetApprovalsForIssueId(pcrb.PlanNumber, true);
approval = approvals.Where(a => a.TaskID == followUp.ID &&
a.RoleName.Equals(subRole.SubRoleCategoryItem) &&
a.UserID == member.UserID &&
a.Step == followUp.Step).First();
PCRBNotification notification = new() {
PCRB = pcrb,
Subject = $"[PCRB Follow Up] {pcrb.PlanNumber} - {pcrb.Title}",
Message = $"Follow up for PCRB# {pcrb.PlanNumber} - {pcrb.Title} has been submitted for closure.",
Approval = approval
};
await pcrbService.NotifyApprover(notification);
}
}
string comments = "Submitted for closure";
PCRBFollowUpComment comment = new() {
PlanNumber = followUp.PlanNumber,
FollowUpID = followUp.ID,
Comment = comments,
UserID = authStateProvider.CurrentUser.UserID
};
await pcrbService.CreateFollowUpComment(comment);
snackbar.Add("Follow up submitted for closure", Severity.Success);
} else {
throw new Exception("no follow ups available to mark as pending closure");
}
followUpSubmitInProcess = false;
} catch (Exception ex) {
followUpSubmitInProcess = false;
snackbar.Add($"Unable to submit follow up for closure, because {ex.Message}", Severity.Error);
}
StateHasChanged();
await OnParametersSetAsync();
}
}
private async Task ApproveFollowUp() {
if (!followUpApproveInProcess) {
try {
followUpApproveInProcess = true;
IEnumerable<Approval> step5Approvals = approvals.Where(a => a.Step == 5 && a.ItemStatus == 0);
foreach (Approval approval in step5Approvals) {
approval.ItemStatus = 1;
approval.Comments = "Follow up complete";
approval.CompletedDate = DateTime.Now;
await approvalService.UpdateApproval(approval);
}
foreach (PCRBFollowUp? followUp in pcrb.FollowUps.Where(f => !f.IsComplete)) {
followUp.IsComplete = true;
followUp.IsPendingApproval = false;
followUp.CompletedDate = DateTime.Now;
await pcrbService.UpdateFollowUp(followUp);
}
pcrb.CurrentStep = (int)PCRB.StagesEnum.Closed;
await pcrbService.UpdatePCRB(pcrb);
string comments = "Follow up complete";
PCRBFollowUpComment comment = new() {
PlanNumber = pcrb.PlanNumber,
FollowUpID = pcrb.FollowUps.First().ID,
Comment = comments,
UserID = authStateProvider.CurrentUser.UserID
};
await pcrbService.CreateFollowUpComment(comment);
PCRBNotification notification = new() {
PCRB = pcrb,
Message = $"Follow up for PCRB# {pcrb.PlanNumber} - {pcrb.Title} has been closed."
};
await pcrbService.NotifyOriginator(notification);
followUpApproveInProcess = false;
snackbar.Add("Follow up successfully approved", Severity.Success);
} catch (Exception ex) {
followUpApproveInProcess = false;
snackbar.Add($"Unable to approve follow up, because {ex.Message}", Severity.Error);
}
StateHasChanged();
await OnParametersSetAsync();
}
}
private async Task DenyFollowUp(string action) {
if (!followUpDenyInProcess) {
try {
string pastAction = action.ToLower().Equals("recall") ? "recalled" : "rejected";
followUpDenyInProcess = true;
string comments = "";
DialogParameters<Comments> parameters = new DialogParameters<Comments> { { x => x.comments, comments } };
var dialog = await dialogService.ShowAsync<Comments>($"Follow Up {action} Comment", parameters);
DialogResult? result = await dialog.Result;
if (result is null || result.Canceled || result.Data is null || string.IsNullOrWhiteSpace(result.Data?.ToString())) {
throw new Exception("you must provide a comment");
}
comments = result.Data?.ToString() ?? string.Empty;
comments = comments.Trim();
IEnumerable<Approval> step5Approvals = approvals.Where(a => a.Step == 5 &&
a.ItemStatus == 0 &&
a.UserID != pcrb.OwnerID);
foreach (Approval approval in step5Approvals) {
approval.ItemStatus = -1;
approval.CompletedDate = DateTime.Now;
approval.Comments = comments is null ? string.Empty : comments;
await approvalService.UpdateApproval(approval);
}
foreach (var followUp in pcrb.FollowUps.Where(f => f.IsPendingApproval)) {
followUp.IsPendingApproval = false;
await pcrbService.UpdateFollowUp(followUp);
}
StringBuilder messageBuilder = new();
messageBuilder.Append($"Follow up for PCRB# {pcrb.PlanNumber} - {pcrb.Title} has been {pastAction}. ");
messageBuilder.Append($"Please review the comments and make the necessary revisions. Comments: {comments}");
PCRBNotification notification = new() {
PCRB = pcrb,
Message = messageBuilder.ToString()
};
await pcrbService.NotifyOriginator(notification);
comments = $"Follow up {pastAction}. Comments: {comments}";
PCRBFollowUpComment comment = new() {
PlanNumber = pcrb.PlanNumber,
FollowUpID = pcrb.FollowUps.First().ID,
Comment = comments,
UserID = authStateProvider.CurrentUser.UserID
};
await pcrbService.CreateFollowUpComment(comment);
followUpDenyInProcess = false;
snackbar.Add($"Follow up successfully {pastAction}", Severity.Success);
} catch (Exception ex) {
followUpDenyInProcess = false;
snackbar.Add($"Unable to {action.ToLower()} follow up, because {ex.Message}", Severity.Error);
}
StateHasChanged();
await OnParametersSetAsync();
}
}
}

View File

@ -13,6 +13,7 @@ public interface IApprovalService {
Task<IEnumerable<User>> GetApprovalGroupMembers(int subRoleId);
Task CreateApproval(Approval approval);
Task UpdateApproval(Approval approval);
Task DeleteApproval(int approvalID);
Task Approve(Approval approval);
Task Deny(Approval approval);
Task<IEnumerable<Approval>> GetApprovalsForIssueId(int issueId, bool bypassCache);
@ -156,6 +157,20 @@ public class ApprovalService : IApprovalService {
await GetApprovalsForUserId(approval.UserID, true);
}
public async Task DeleteApproval(int approvalID) {
if (approvalID <= 0) throw new ArgumentException("Invalid approval ID");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Delete, $"approval?approvalID={approvalID}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode) {
throw new Exception($"Unable to delete approval, because {responseMessage.ReasonPhrase}");
}
}
public async Task Approve(Approval approval) {
if (approval is null) throw new ArgumentNullException("approval cannot be null");

View File

@ -2,6 +2,7 @@
using MesaFabApproval.Shared.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using MudBlazor;
@ -12,18 +13,22 @@ public class MesaFabApprovalAuthStateProvider : AuthenticationStateProvider, IDi
private readonly IAuthenticationService _authService;
private readonly IUserService _userService;
private readonly ISnackbar _snackbar;
private readonly NavigationManager _navigationManager;
public User? CurrentUser { get; private set; }
public MesaFabApprovalAuthStateProvider(IAuthenticationService authService,
ISnackbar snackbar,
IUserService userService) {
IUserService userService,
NavigationManager navigationManager) {
_authService = authService ??
throw new ArgumentNullException("IAuthenticationService not injected");
_snackbar = snackbar ??
throw new ArgumentNullException("ISnackbar not injected");
_userService = userService ??
throw new ArgumentNullException("IUserService not injected");
_navigationManager = navigationManager ??
throw new ArgumentNullException("NavigationManager not injected");
AuthenticationStateChanged += OnAuthenticationStateChangedAsync;
}

View File

@ -36,6 +36,7 @@ public interface IPCRBService {
Task UpdatePCR3Document(PCR3Document document);
Task<IEnumerable<PCR3Document>> GetPCR3DocumentsForPlanNumber(int planNumber, bool bypassCache);
Task NotifyNewApprovals(PCRB pcrb);
Task NotifyApprover(PCRBNotification notification);
Task NotifyApprovers(PCRBNotification notification);
Task NotifyOriginator(PCRBNotification notification);
Task NotifyResponsiblePerson(PCRBActionItemNotification notification);
@ -43,6 +44,10 @@ public interface IPCRBService {
Task<IEnumerable<PCRBFollowUp>> GetFollowUpsByPlanNumber(int planNumber, bool bypassCache);
Task UpdateFollowUp(PCRBFollowUp followUp);
Task DeleteFollowUp(int id);
Task CreateFollowUpComment(PCRBFollowUpComment comment);
Task<IEnumerable<PCRBFollowUpComment>> GetFollowUpCommentsByPlanNumber(int planNumber, bool bypassCache);
Task UpdateFollowUpComment(PCRBFollowUpComment comment);
Task DeleteFollowUpComment(int id);
}
public class PCRBService : IPCRBService {
@ -712,6 +717,26 @@ public class PCRBService : IPCRBService {
throw new Exception($"Unable to notify new PCRB approvers, because {responseMessage.ReasonPhrase}");
}
public async Task NotifyApprover(PCRBNotification notification) {
if (notification is null) throw new ArgumentNullException("notification cannot be null");
if (notification.PCRB is null) throw new ArgumentNullException("PCRB cannot be null");
if (notification.Approval is null) throw new ArgumentNullException("approval cannot be null");
if (string.IsNullOrWhiteSpace(notification.Message)) throw new ArgumentException("message cannot be null or empty");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Post, $"pcrb/notify/approver") {
Content = new StringContent(JsonSerializer.Serialize(notification),
Encoding.UTF8,
"application/json")
};
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode)
throw new Exception($"Unable to notify PCRB approver, because {responseMessage.ReasonPhrase}");
}
public async Task NotifyApprovers(PCRBNotification notification) {
if (notification is null) throw new ArgumentNullException("notification cannot be null");
if (notification.PCRB is null) throw new ArgumentNullException("PCRB cannot be null");
@ -812,7 +837,7 @@ public class PCRBService : IPCRBService {
new List<PCRBFollowUp>();
if (followUps.Count() > 0)
_cache.Set($"pcrbFollowUps{planNumber}", followUps, DateTimeOffset.Now.AddMinutes(5));
_cache.Set($"pcrbFollowUps{planNumber}", followUps, DateTimeOffset.Now.AddHours(1));
} else {
throw new Exception(responseMessage.ReasonPhrase);
}
@ -836,6 +861,8 @@ public class PCRBService : IPCRBService {
if (!responseMessage.IsSuccessStatusCode)
throw new Exception(responseMessage.ReasonPhrase);
await GetFollowUpsByPlanNumber(followUp.PlanNumber, true);
}
public async Task DeleteFollowUp(int id) {
@ -849,4 +876,93 @@ public class PCRBService : IPCRBService {
if (!responseMessage.IsSuccessStatusCode) throw new Exception(responseMessage.ReasonPhrase);
}
public async Task CreateFollowUpComment(PCRBFollowUpComment comment) {
if (comment is null) throw new ArgumentNullException("comment up cannot be null");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Post, $"pcrb/followUpComment") {
Content = new StringContent(JsonSerializer.Serialize(comment),
Encoding.UTF8,
"application/json")
};
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode)
throw new Exception(responseMessage.ReasonPhrase);
await GetFollowUpCommentsByPlanNumber(comment.PlanNumber, true);
}
public async Task<IEnumerable<PCRBFollowUpComment>> GetFollowUpCommentsByPlanNumber(int planNumber, bool bypassCache) {
if (planNumber <= 0) throw new ArgumentException($"{planNumber} is not a valid PCRB Plan#");
IEnumerable<PCRBFollowUpComment>? comments = null;
if (!bypassCache)
comments = _cache.Get<IEnumerable<PCRBFollowUpComment>>($"pcrbFollowUpComments{planNumber}");
if (comments is null) {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage =
new(HttpMethod.Get, $"pcrb/followUpComments?planNumber={planNumber}&bypassCache={bypassCache}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
comments = JsonSerializer.Deserialize<IEnumerable<PCRBFollowUpComment>>(responseContent, jsonSerializerOptions) ??
new List<PCRBFollowUpComment>();
if (comments.Count() > 0) {
foreach (PCRBFollowUpComment comment in comments)
comment.User = await _userService.GetUserByUserId(comment.UserID);
_cache.Set($"pcrbFollowUpComments{planNumber}", comments, DateTimeOffset.Now.AddHours(1));
}
} else {
throw new Exception(responseMessage.ReasonPhrase);
}
}
return comments;
}
public async Task UpdateFollowUpComment(PCRBFollowUpComment comment) {
if (comment is null) throw new ArgumentNullException("comment up cannot be null");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Put, $"pcrb/followUpComment") {
Content = new StringContent(JsonSerializer.Serialize(comment),
Encoding.UTF8,
"application/json")
};
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode)
throw new Exception(responseMessage.ReasonPhrase);
await GetFollowUpCommentsByPlanNumber(comment.PlanNumber, true);
}
public async Task DeleteFollowUpComment(int id) {
if (id <= 0) throw new ArgumentException($"{id} is not a valid PCRB follow up comment ID");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Delete, $"pcrb/followUpComment?id={id}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode) throw new Exception(responseMessage.ReasonPhrase);
}
}

View File

@ -111,7 +111,7 @@ public class ApiHttpClientHandler : DelegatingHandler {
string? redirectUrl = _cache.Get<string>("redirectUrl");
if (!string.IsNullOrWhiteSpace(redirectUrl)) {
_navigationManager.NavigateTo($"login/{redirectUrl}");
_navigationManager.NavigateTo($"login?redirectPath={redirectUrl}");
} else {
_navigationManager.NavigateTo("login");
}