PCRB webassembly

This commit is contained in:
Chase Tucker
2024-05-13 14:33:27 -07:00
parent 9b7e3ef897
commit 89790f4fc1
50 changed files with 5466 additions and 677 deletions

View File

@ -1,8 +1,14 @@
@inherits LayoutComponentBase
@using System.Text.Json
@using System.Text
@inherits LayoutComponentBase
@inject MesaFabApprovalAuthStateProvider authStateProvider
@inject IAuthenticationService authenticationService
@inject IConfiguration Configuration
@inject IMemoryCache cache
@inject IJSRuntime jsRuntime
@inject IHttpClientFactory httpClientFactory
@inject ISnackbar snackbar
@inject NavigationManager navManager
<MudThemeProvider />
@ -28,19 +34,22 @@
<MudDrawer @bind-Open="_drawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2">
<MudNavMenu Color="Color.Info" Bordered="true" Class="d-flex flex-column justify-center p-1 gap-1">
<MudButton Variant="Variant.Filled"
Color="Color.Tertiary"
Href="@Configuration["OldFabApprovalUrl"]"
Target="_blank"
StartIcon="@Icons.Material.Filled.Home">
Color="Color.Tertiary"
Href="@Configuration["OldFabApprovalUrl"]"
Target="_blank"
StartIcon="@Icons.Material.Filled.Home">
Return to Main Site
</MudButton>
<MudDivider Class="my-1" />
@if (authStateProvider.CurrentUser is not null) {
<MudNavGroup Title="Create New">
<MudNavLink OnClick="@(() => GoTo("mrb/new"))">Create New MRB</MudNavLink>
<MudNavLink OnClick="@(() => GoTo("pcrb/new"))">Create New PCRB</MudNavLink>
</MudNavGroup>
<MudNavLink OnClick="@(() => GoTo(""))" Icon="@Icons.Material.Filled.Dashboard">Dashboard</MudNavLink>
<MudNavLink OnClick="@(() => GoTo("mrb/all"))" Icon="@Icons.Material.Filled.Ballot">MRB List</MudNavLink>
<MudNavLink OnClick="@(() => GoTo("pcrb/all"))" Icon="@Icons.Material.Filled.Ballot">PCRB List</MudNavLink>
}
</MudNavMenu>
</MudDrawer>
@ -68,4 +77,48 @@
cache.Set("redirectUrl", page);
navManager.NavigateTo(page);
}
private async Task GoToExternal(string url, string content) {
IJSObjectReference windowModule = await jsRuntime.InvokeAsync<IJSObjectReference>("import", "./js/OpenInNewWindow.js");
await windowModule.InvokeAsync<object>("OpenInNewWindow", url, content);
}
private async Task GoToOldSite() {
try {
User? currentUser = authStateProvider.CurrentUser;
AuthTokens? authTokens = await authenticationService.GetAuthTokens();
if (currentUser is null || authTokens is null) {
await authStateProvider.Logout();
navManager.NavigateTo("login");
return;
}
AuthAttempt authAttempt = new() {
LoginID = currentUser.LoginID,
AuthTokens = authTokens
};
HttpClient httpClient = httpClientFactory.CreateClient("OldSite");
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "Account/ExternalAuthSetup");
request.Content = new StringContent(JsonSerializer.Serialize(authAttempt),
Encoding.UTF8,
"application/json");
HttpResponseMessage httpResponseMessage = await httpClient.SendAsync(request);
if (httpResponseMessage.IsSuccessStatusCode) {
snackbar.Add("Old site auth setup successful", Severity.Success);
} else {
snackbar.Add($"Old site auth setup failed, because {httpResponseMessage.ReasonPhrase}", Severity.Error);
}
await GoToExternal($"{Configuration["OldFabApprovalUrl"]}", "");
} catch (Exception ex) {
snackbar.Add($"Unable to go to old site, because {ex.Message}", Severity.Error);
}
}
}

View File

@ -10,6 +10,10 @@
<Watch Include="**\*.razor" />
</ItemGroup>
<ItemGroup>
<Watch Remove="Pages\Components\PCRBApproverForm.razor" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.8" />
@ -17,7 +21,7 @@
<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.Extensions.Caching.Memory" Version="8.0.0" />
<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" />
</ItemGroup>

View File

@ -25,7 +25,7 @@
}
if (QueryHelpers.ParseQuery(uri.Query).TryGetValue("redirectPath", out var redirectPath)) {
_redirectPath = System.Net.WebUtility.UrlDecode(redirectPath);
_redirectPath = redirectPath.ToString();
}
if (!string.IsNullOrWhiteSpace(_jwt) && !string.IsNullOrWhiteSpace(_refreshToken)) {
@ -35,11 +35,12 @@
string loginId = userService.GetLoginIdFromClaimsPrincipal(principal);
await authService.ClearCurrentUser();
await authService.ClearTokens();
await authService.SetLoginId(loginId);
await authService.SetTokens(_jwt, _refreshToken);
User? user = await userService.GetUserByLoginId(loginId);
await authService.SetCurrentUser(user);
await authService.SetCurrentUser(null);
await authStateProvider.StateHasChanged(principal);
}

View File

@ -2,7 +2,7 @@
<MudDialog>
<DialogContent>
<MudPaper Class="p-2">
<MudPaper Class="m-2 p-2">
<MudForm @bind-Errors="@errors">
<MudTextField T="string"
Label="Comments"

View File

@ -4,7 +4,7 @@
<MudDialog>
<DialogContent>
<MudPaper Class="p-2">
<MudPaper Class="m-2 p-2">
<MudForm @bind-Errors="@errors">
<MudTextField T="string"
Label="Comments"

View File

@ -4,10 +4,11 @@
<MudDialog>
<DialogContent>
<MudPaper Class="p-2">
<MudPaper Class="m-2 p-2">
<MudForm @bind-Errors="@errors">
<MudSelect T="string"
Label="Action"
Disabled="@(!string.IsNullOrWhiteSpace(mrbAction.Action))"
Required="true"
RequiredError="You must select an action!"
@bind-Value="@mrbAction.Action"
@ -19,29 +20,61 @@
<MudSelectItem Value="@("Unblock")" />
<MudSelectItem Value="@("Waiver")" />
</MudSelect>
@if (mrbAction.Action.Equals("Convert")) {
<MudTextField @bind-Value="@mrbAction.ConvertFrom"
Label="Convert From"
@if (mrbAction.Action.Equals("Convert", StringComparison.InvariantCultureIgnoreCase)) {
<MudSelect T="string"
Label="Convert From Customer"
Required
Variant="Variant.Outlined"
AnchorOrigin="Origin.BottomCenter"
@bind-Value=convertFromCustomer
Text="@convertFromCustomer">
@foreach (string customer in customerNames) {
<MudSelectItem Value="@(customer)" />
}
</MudSelect>
<MudTextField @bind-Value="@convertFromPart"
Label="Convert From Part Number"
Required
RequiredError="Conversion value required!"
Text="@mrbAction.ConvertFrom" />
<MudTextField @bind-Value="@mrbAction.ConvertTo"
Label="Convert To"
RequiredError="Part number required!"
Text="@convertFromPart" />
<MudSelect T="string"
Label="Convert To Customer"
Required
Variant="Variant.Outlined"
AnchorOrigin="Origin.BottomCenter"
@bind-Value=convertToCustomer
Text="@convertToCustomer">
@foreach (string customer in customerNames) {
<MudSelectItem Value="@(customer)" />
}
</MudSelect>
<MudTextField @bind-Value="@convertToPart"
Label="Convert To Part Number"
Required
RequiredError="Conversion value required!"
Text="@mrbAction.ConvertTo" />
RequiredError="Part number required!"
Text="@convertToPart" />
} else {
<MudSelect T="string"
Label="Affected Customer"
Required
Variant="Variant.Outlined"
AnchorOrigin="Origin.BottomCenter"
@bind-Value=mrbAction.Customer
Text="@mrbAction.Customer">
@foreach (string customer in customerNames) {
<MudSelectItem Value="@(customer)" />
}
</MudSelect>
<MudAutocomplete T="string"
Label="Part Number"
Variant="Variant.Outlined"
AnchorOrigin="Origin.BottomCenter"
@bind-Value=mrbAction.PartNumber
Text="@mrbAction.PartNumber"
SearchFunc="@PartNumberSearch"
ResetValueOnEmptyText
CoerceText />
}
<MudSelect T="string"
Label="Affected Customer"
Required
Variant="Variant.Outlined"
AnchorOrigin="Origin.BottomCenter"
@bind-Value=mrbAction.Customer
Text="@mrbAction.Customer">
@foreach (string customer in customerNames) {
<MudSelectItem Value="@(customer)" />
}
</MudSelect>
<MudTextField T="int"
InputType="@InputType.Number"
Label="Qty"
@ -49,15 +82,6 @@
RequiredError="You must supply a quantity!"
@bind-Value=mrbAction.Quantity
Text="@mrbAction.Quantity.ToString()" />
<MudAutocomplete T="string"
Label="Part Number"
Variant="Variant.Outlined"
AnchorOrigin="Origin.BottomCenter"
@bind-Value=mrbAction.PartNumber
Text="@mrbAction.PartNumber"
SearchFunc="@PartNumberSearch"
ResetValueOnEmptyText
CoerceText />
<MudAutocomplete T="string"
Label="Batch Number / Lot Number"
Variant="Variant.Outlined"
@ -130,6 +154,11 @@
private IEnumerable<string> customerNames = new List<string>();
private string convertFromCustomer = string.Empty;
private string convertFromPart = string.Empty;
private string convertToCustomer = string.Empty;
private string convertToPart = string.Empty;
private bool isVisible { get; set; }
private string[] errors = { };
private bool processingSave = false;
@ -145,10 +174,34 @@
actions = (await mrbService.GetMRBActionsForMRB(mrbAction.MRBNumber, false)).OrderByDescending(a => a.ActionID);
if (actions is not null && actions.Count() > 0) {
if (string.IsNullOrWhiteSpace(mrbAction.Action))
mrbAction.Action = actions.First().Action;
if (string.IsNullOrWhiteSpace(mrbAction.Customer))
mrbAction.Customer = actions.First().Customer;
if (string.IsNullOrWhiteSpace(mrbAction.LotNumber))
mrbAction.LotNumber = actions.First().LotNumber;
if (string.IsNullOrWhiteSpace(mrbAction.PartNumber))
mrbAction.PartNumber = actions.First().PartNumber;
if (string.IsNullOrWhiteSpace(mrbAction.Justification))
mrbAction.Justification = actions.First().Justification;
if (mrbAction.Quantity == 0)
mrbAction.Quantity = actions.First().Quantity;
if (mrbAction.Action.Equals("Convert", StringComparison.InvariantCultureIgnoreCase)) {
string[] convertFrom = actions.First().ConvertFrom.Split(" ");
if (convertFrom.Length > 1) {
convertFromCustomer = convertFrom[0];
foreach (string partStr in convertFrom.Skip(1))
convertFromPart += partStr;
}
string[] convertTo = actions.First().ConvertTo.Split(" ");
if (convertTo.Length > 1) {
convertToCustomer = convertTo[0];
foreach (string partStr in convertTo.Skip(1))
convertToPart += partStr;
}
}
}
}
@ -165,9 +218,17 @@
actionIsValid = actionIsValid && !string.IsNullOrWhiteSpace(mrbAction.Customer) &&
!string.IsNullOrWhiteSpace(mrbAction.PartNumber) &&
!string.IsNullOrWhiteSpace(mrbAction.LotNumber);
actionIsValid = actionIsValid && mrbAction.Quantity > 0;
if (mrbAction.Action.Equals("Scrap"))
return actionIsValid && !string.IsNullOrWhiteSpace(mrbAction.Justification);
if (mrbAction.Action.Equals("Convert", StringComparison.InvariantCultureIgnoreCase)) {
actionIsValid = actionIsValid && !string.IsNullOrWhiteSpace(convertFromCustomer) &&
!string.IsNullOrWhiteSpace(convertFromPart) &&
!string.IsNullOrWhiteSpace(convertToCustomer) &&
!string.IsNullOrWhiteSpace(convertToPart);
}
if (mrbAction.Action.Equals("Scrap", StringComparison.InvariantCultureIgnoreCase))
actionIsValid = actionIsValid && !string.IsNullOrWhiteSpace(mrbAction.Justification);
return actionIsValid;
}
@ -177,6 +238,11 @@
try {
if (!FormIsValid()) throw new Exception("You must complete the form before saving!");
if (mrbAction.Action.Equals("Convert", StringComparison.InvariantCultureIgnoreCase)) {
mrbAction.ConvertFrom = $"{convertFromCustomer} {convertFromPart}";
mrbAction.ConvertTo = $"{convertToCustomer} {convertToPart}";
}
if (mrbAction.MRBNumber > 0) {
if (mrbAction.ActionID <= 0) {
await mrbService.CreateMRBAction(mrbAction);

View File

@ -5,7 +5,7 @@
<MudDialog>
<DialogContent>
@if (availableApprovers is not null) {
<MudPaper Class="p-2">
<MudPaper Class="m-2 p-2">
<MudSelect T="User"
Label="Select a User"
Required

View File

@ -0,0 +1,151 @@
@inject MesaFabApprovalAuthStateProvider authStateProvider
@inject NavigationManager navigationManager
@inject IPCRBService pcrbService
@inject IUserService userService
@inject IECNService ecnService
@inject ISnackbar snackbar
<MudDialog>
<DialogContent>
<MudPaper Class="m-2 p-2">
<MudForm @bind-Errors="@errors">
<MudTextField T="string"
Label="Document Type"
@bind-Value="@document.DocType"
@bind-Text="@document.DocType"
Disabled
AutoGrow />
<MudTextField Label="Document Numbers - Rev. & Title"
@bind-Value="@document.DocNumbers"
@bind-Text="@document.DocNumbers"
Immediate
AutoGrow
AutoFocus />
@if (DocNumberIsNA()) {
<MudTextField Label="Comments"
@bind-Value="@document.Comment"
@bind-Text="@document.Comment"
Required
RequiredError="You must provide a comment"
Immediate
AutoGrow />
} else {
<MudTextField @bind-Value="@document.ECNNumber"
Required
RequiredError="You must provide a valid ECN#"
Clearable
Variant="Variant.Outlined"
InputType="@InputType.Number"
Validation="@(new Func<int, Task<string>>(ECNNoIsValid))"
Label="ECN#"
Immediate
AutoGrow />
}
<MudCheckBox Label="Complete"
Color="Color.Tertiary"
@bind-Value=complete
LabelPosition="LabelPosition.Start" />
</MudForm>
</MudPaper>
</DialogContent>
<DialogActions>
@if ((DocNumberIsNA() && !string.IsNullOrWhiteSpace(document.Comment)) ||
(!DocNumberIsNA() && ecnNoIsValid)) {
<MudButton Variant="Variant.Filled"
Color="Color.Tertiary"
Class="m1-auto"
OnClick=Save>
@if (saveInProcess) {
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
<MudText>Processing</MudText>
} else {
<MudText>Save</MudText>
}
</MudButton>
}
<MudButton Variant="Variant.Filled"
Class="grey text-black m1-auto"
OnClick=Cancel>
Cancel
</MudButton>
</DialogActions>
</MudDialog>
@code {
[CascadingParameter]
MudDialogInstance MudDialog { get; set; }
[Parameter]
public required PCR3Document document { get; set; }
private string[] errors = { };
private bool complete = false;
private bool saveInProcess = false;
private bool ecnNoIsValid = true;
protected override async Task OnParametersSetAsync() {
complete = document.CompletedByID > 0;
}
private async Task Save() {
saveInProcess = true;
try {
if (authStateProvider.CurrentUser is null) {
await authStateProvider.Logout();
navigationManager.NavigateTo("login");
return;
}
if (!complete) {
document.CompletedByID = 0;
document.CompletedBy = null;
document.CompletedDate = DateTimeUtilities.MAX_DT;
}
if (complete && document.CompletedByID <= 0) {
document.CompletedByID = authStateProvider.CurrentUser.UserID;
document.CompletedBy = authStateProvider.CurrentUser;
document.CompletedDate = DateTime.Now;
}
if (!DocNumberIsNA() && !ecnNoIsValid)
throw new Exception($"{document.ECNNumber} is not a valid ECN#");
if (DocNumberIsNA() && string.IsNullOrWhiteSpace(document.Comment))
throw new Exception("you must provide a comment");
await pcrbService.UpdatePCR3Document(document);
await pcrbService.GetPCR3DocumentsForPlanNumber(document.PlanNumber, true);
saveInProcess = false;
MudDialog.Close(DialogResult.Ok(document));
} catch (Exception ex) {
saveInProcess = false;
snackbar.Add($"Unable to save document, because {ex.Message}", Severity.Error);
}
}
private void Cancel() {
MudDialog.Close(DialogResult.Cancel());
}
private bool DocNumberIsNA() {
if (document.DocNumbers.ToLower().Equals("na") ||
document.DocNumbers.ToLower().Equals("n/a")) {
return true;
}
return false;
}
private async Task<string> ECNNoIsValid(int ecnNumber) {
string? result = await ecnService.ECNNumberIsValidStr(ecnNumber);
if (result is null) ecnNoIsValid = true;
else ecnNoIsValid = false;
StateHasChanged();
return result;
}
}

View File

@ -0,0 +1,201 @@
@inject MesaFabApprovalAuthStateProvider authStateProvider
@inject NavigationManager navigationManager
@inject IPCRBService pcrbService
@inject IUserService userService
@inject ISnackbar snackbar
<MudDialog>
<DialogContent>
<MudPaper Class="m-2 p-2">
<MudForm @bind-Errors="@errors">
<MudTextField T="string"
Label="Action"
Required
RequiredError="Enter action item"
@bind-Value="@name"
@bind-Text="@name"
Immediate
Clearable
AutoGrow
AutoFocus />
<MudCheckBox Label="Gating"
Color="Color.Tertiary"
@bind-Value=gating
LabelPosition="LabelPosition.Start" />
<MudCheckBox Label="Closed"
Color="Color.Tertiary"
@bind-Value="@closedStatus"
LabelPosition="LabelPosition.Start" />
@if (closedStatus) {
<MudDatePicker Label="Closed Date"
Color="Color.Tertiary"
@bind-Date="@closedDate"
Clearable
MinDate="@DateTimeUtilities.MIN_DT"
MaxDate="@DateTimeUtilities.MAX_DT"
Placeholder="Select a closed date" />
}
<MudSelect T="User"
Label="Responsible Person"
Variant="Variant.Outlined"
Required
RequiredError="You must select a responsible person"
Clearable
AnchorOrigin="Origin.BottomCenter"
ToStringFunc="@UserToNameConverter"
@bind-Value=@responsiblePerson>
@foreach (User user in allActiveUsers.OrderBy(u => u.FirstName)) {
<MudSelectItem T="User" Value="@(user)" />
}
</MudSelect>
</MudForm>
</MudPaper>
</DialogContent>
<DialogActions>
<MudButton Variant="Variant.Filled"
Color="Color.Tertiary"
Class="m1-auto"
OnClick=Save>
@if (saveActionItemInProcess) {
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
<MudText>Processing</MudText>
} else {
<MudText>Submit</MudText>
}
</MudButton>
<MudButton Variant="Variant.Filled"
Class="grey text-black m1-auto"
OnClick=Cancel>
Cancel
</MudButton>
</DialogActions>
</MudDialog>
@code {
[CascadingParameter]
MudDialogInstance MudDialog { get; set; }
[Parameter]
public int planNumber { get; set; } = 0;
[Parameter]
public int step { get; set; } = 0;
[Parameter]
public PCRBActionItem? actionItem { get; set; } = null;
[Parameter]
public IEnumerable<User> allActiveUsers { get; set; }
private string[] errors = { };
private string name = "";
private bool gating = false;
private DateTime? closedDate = DateTimeUtilities.MAX_DT;
private bool closedStatus = false;
private User? responsiblePerson = null;
private bool saveActionItemInProcess = false;
protected override async Task OnParametersSetAsync() {
if (authStateProvider.CurrentUser is null) {
await authStateProvider.Logout();
navigationManager.NavigateTo("login");
}
if (planNumber <= 0) {
snackbar.Add($"{planNumber} is not a valid PCRB plan#", Severity.Error);
MudDialog.Close(DialogResult.Cancel());
}
if (allActiveUsers is null || allActiveUsers.Count() <= 0)
allActiveUsers = await userService.GetAllActiveUsers();
if (actionItem is not null) {
name = actionItem.Name;
gating = actionItem.Gating;
closedStatus = actionItem.ClosedStatus;
closedDate = actionItem.ClosedDate;
if (closedDate.Equals(DateTimeUtilities.MAX_DT)) {
closedDate = DateTime.Now;
}
if (actionItem.ResponsiblePersonID > 0) {
if (actionItem.ResponsiblePerson is not null) responsiblePerson = actionItem.ResponsiblePerson;
else responsiblePerson = await userService.GetUserByUserId(actionItem.ResponsiblePersonID);
actionItem.ResponsiblePerson = responsiblePerson;
}
} else {
name = "";
gating = false;
closedStatus = false;
closedDate = DateTime.Now;
responsiblePerson = null;
}
}
private async Task Save() {
saveActionItemInProcess = true;
try {
if (authStateProvider.CurrentUser is null) {
await authStateProvider.Logout();
navigationManager.NavigateTo("login");
}
if (string.IsNullOrWhiteSpace(name)) throw new Exception("name missing");
if (actionItem is null) {
actionItem = new() {
Name = name,
UploadedBy = authStateProvider.CurrentUser,
UploadedByID = authStateProvider.CurrentUser.UserID,
PlanNumber = planNumber,
Step = step,
Gating = gating,
ClosedStatus = closedStatus,
ResponsiblePerson = responsiblePerson,
ResponsiblePersonID = responsiblePerson.UserID,
ClosedDate = closedDate,
ClosedBy = closedDate is null || closedDate >= DateTimeUtilities.MAX_DT ? null : authStateProvider.CurrentUser,
ClosedByID = closedStatus ? authStateProvider.CurrentUser.UserID : 0
};
if (actionItem.ClosedStatus == false) {
actionItem.ClosedDate = DateTimeUtilities.MAX_DT;
}
await pcrbService.CreateNewActionItem(actionItem);
} else {
actionItem.Name = name;
actionItem.Gating = gating;
actionItem.ClosedStatus = closedStatus;
actionItem.ClosedDate = closedDate;
if (closedStatus) {
actionItem.ClosedBy = authStateProvider.CurrentUser;
actionItem.ClosedByID = authStateProvider.CurrentUser.UserID;
} else {
actionItem.ClosedDate = DateTimeUtilities.MAX_DT;
}
actionItem.ResponsiblePerson = responsiblePerson;
if (responsiblePerson is not null)
actionItem.ResponsiblePersonID = responsiblePerson.UserID;
await pcrbService.UpdateActionItem(actionItem);
}
saveActionItemInProcess = false;
MudDialog.Close(DialogResult.Ok(actionItem));
} catch (Exception ex) {
saveActionItemInProcess = false;
snackbar.Add($"Unable to save action item, because {ex.Message}", Severity.Error);
}
}
private void Cancel() {
MudDialog.Close(DialogResult.Cancel());
}
private Func<User, string> UserToNameConverter = u => u is null ? string.Empty : u.GetFullName();
private Func<PCRBAttachment, string> AttachmentToFileNameConverter = a => a is null ? "" : a.FileName;
}

View File

@ -0,0 +1,250 @@
@inject MesaFabApprovalAuthStateProvider authStateProvider
@inject NavigationManager navigationManager
@inject IPCRBService pcrbService
@inject IUserService userService
@inject IApprovalService approvalService
@inject ISnackbar snackbar
<MudDialog>
<DialogContent>
<MudPaper Class="m-2 p-2">
<MudForm @bind-Errors="@errors">
<MudSelect T="string"
Label="Job Title"
Required
RequiredError="You must provide a job title"
ValueChanged="@SelectedJobTitleChanged"
@bind-Text="@selectedJobTitle"
Immediate
Disabled="@(approval is not null && !string.IsNullOrWhiteSpace(selectedJobTitle))"
Clearable
AutoFocus >
@foreach (string jt in availableJobTitles) {
<MudSelectItem Value="@jt">@jt</MudSelectItem>
}
</MudSelect>
<MudSelect T="User"
Label="Select a User"
Required
Clearable
Disabled="@(string.IsNullOrWhiteSpace(selectedJobTitle))"
Variant="Variant.Outlined"
AnchorOrigin="Origin.BottomCenter"
@bind-Value=selectedUser
ToStringFunc="@UserToNameConverter">
@foreach (User user in availableUsers) {
<MudSelectItem Value="@user">@user.GetFullName()</MudSelectItem>
}
</MudSelect>
</MudForm>
</MudPaper>
</DialogContent>
<DialogActions>
<MudButton Variant="Variant.Filled"
Color="Color.Tertiary"
Class="m1-auto"
OnClick=Save>
@if (saveAttendeeInProcess) {
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
<MudText>Processing</MudText>
} else {
<MudText>Submit</MudText>
}
</MudButton>
<MudButton Variant="Variant.Filled"
Class="grey text-black m1-auto"
OnClick=Cancel>
Cancel
</MudButton>
</DialogActions>
</MudDialog>
@code {
[CascadingParameter]
MudDialogInstance MudDialog { get; set; }
[Parameter]
public int planNumber { get; set; } = 0;
[Parameter]
public int step { get; set; } = 0;
[Parameter]
public Approval? approval { get; set; } = null;
private HashSet<string> availableJobTitles = new();
private Dictionary<string, SubRole> jobTitleToSubRoleMap = new();
private Dictionary<string, IEnumerable<User>> jobTitleToAvailableUsersMap = new();
private HashSet<User> availableUsers = new();
private string[] errors = { };
private string selectedJobTitle = "";
private User? selectedUser = null;
private bool saveAttendeeInProcess = false;
protected override async Task OnParametersSetAsync() {
if (authStateProvider.CurrentUser is null) {
await authStateProvider.Logout();
navigationManager.NavigateTo("login");
}
if (planNumber <= 0) {
snackbar.Add($"{planNumber} is not a valid PCRB plan#", Severity.Error);
MudDialog.Close(DialogResult.Cancel());
}
await GetAttendees();
if (approval is not null) {
selectedJobTitle = approval.RoleName;
if (approval.UserID > 0) {
if (approval.User is not null) {
selectedUser = approval.User;
} else {
selectedUser = await userService.GetUserByUserId(approval.UserID);
approval.User = selectedUser;
}
}
SelectedJobTitleChanged(selectedJobTitle);
} else {
selectedJobTitle = "";
selectedUser = null;
}
}
private async Task Save() {
saveAttendeeInProcess = true;
try {
if (authStateProvider.CurrentUser is null) {
await authStateProvider.Logout();
navigationManager.NavigateTo("login");
}
if (string.IsNullOrWhiteSpace(selectedJobTitle)) throw new Exception("job title missing");
if (selectedUser is null) throw new Exception("attendee not selected");
PCRB pcrb = await pcrbService.GetPCRBByPlanNumber(planNumber, false);
if (approval is null) {
jobTitleToSubRoleMap.TryGetValue($"{selectedJobTitle}{selectedUser.UserID}", out SubRole? subRole);
if (subRole is null) throw new Exception($"no approval role found for job title {selectedJobTitle}");
approval = new() {
RoleName = subRole.SubRoleCategoryItem,
SubRole = subRole.SubRoleName,
SubRoleID = subRole.SubRoleID,
IssueID = planNumber,
Step = step,
User = selectedUser,
UserID = selectedUser.UserID,
AssignedDate = DateTimeUtilities.MIN_DT
};
await approvalService.CreateApproval(approval);
} else {
int originalUserId = approval.UserID;
approval.UserID = selectedUser.UserID;
approval.User = selectedUser;
if (originalUserId != approval.UserID) {
if (approval.AssignedDate > DateTimeUtilities.MIN_DT)
approval.AssignedDate = DateTime.Now;
approval.NotifyDate = DateTimeUtilities.MIN_DT;
}
await approvalService.UpdateApproval(approval);
if (originalUserId != approval.UserID)
await pcrbService.NotifyNewApprovals(pcrb);
}
saveAttendeeInProcess = false;
MudDialog.Close(DialogResult.Ok(approval));
} catch (Exception ex) {
saveAttendeeInProcess = false;
snackbar.Add($"Unable to save attendee, because {ex.Message}", Severity.Error);
}
}
private void Cancel() {
MudDialog.Close(DialogResult.Cancel());
}
private Func<User, string> UserToNameConverter = u => u is null ? string.Empty : u.GetFullName();
private async Task GetAttendees() {
int roleId = await approvalService.GetRoleIdForRoleName("Module Manager");
if (roleId <= 0) throw new Exception($"could not find Director role ID");
availableJobTitles.Clear();
jobTitleToAvailableUsersMap.Clear();
jobTitleToSubRoleMap.Clear();
IEnumerable<SubRole> subRoles = await approvalService.GetSubRolesForSubRoleName("MMSubRole", roleId);
HashSet<string> defaultSubRoleCategoryItems = new() { "Si Production", "Si Engineering", "Quality" };
HashSet<string> unusedSubRoleCategoryItems = new() { "GaN Engineering", "GaN Operations", "Integration" };
foreach (SubRole subRole in subRoles) {
if (approval is null) {
if (!defaultSubRoleCategoryItems.Contains(subRole.SubRoleCategoryItem) &&
!unusedSubRoleCategoryItems.Contains(subRole.SubRoleCategoryItem)) {
availableJobTitles.Add(subRole.SubRoleCategoryItem);
IEnumerable<User> subRoleMembers = await approvalService.GetApprovalGroupMembers(subRole.SubRoleID);
jobTitleToAvailableUsersMap.Add(subRole.SubRoleCategoryItem, subRoleMembers);
foreach (User member in subRoleMembers) {
jobTitleToSubRoleMap.Add($"{subRole.SubRoleCategoryItem}{member.UserID}", subRole);
}
}
} else {
if (!unusedSubRoleCategoryItems.Contains(subRole.SubRoleCategoryItem)) {
IEnumerable<User> subRoleMembers = await approvalService.GetApprovalGroupMembers(subRole.SubRoleID);
jobTitleToAvailableUsersMap.Add(subRole.SubRoleCategoryItem, subRoleMembers);
foreach (User member in subRoleMembers)
availableUsers.Add(member);
}
}
}
}
private void SelectedJobTitleChanged(string jobTitle) {
selectedJobTitle = jobTitle;
selectedUser = null;
availableUsers.Clear();
if (approval is null) {
if (jobTitleToAvailableUsersMap.TryGetValue(jobTitle, out IEnumerable<User>? jobTitleMembers)) {
if (jobTitleMembers is not null) {
foreach (User member in jobTitleMembers)
availableUsers.Add(member);
}
}
} else {
foreach (IEnumerable<User> memberList in jobTitleToAvailableUsersMap.Values) {
if (memberList is not null) {
foreach(User member in memberList) {
availableUsers.Add(member);
}
}
}
}
StateHasChanged();
}
}

View File

@ -0,0 +1,140 @@
@inject MesaFabApprovalAuthStateProvider authStateProvider
@inject NavigationManager navigationManager
@inject IPCRBService pcrbService
@inject ISnackbar snackbar
<MudDialog>
<DialogContent>
<MudPaper Class="m-2 p-2">
<MudForm @bind-Errors="@errors">
<MudTextField T="string"
Label="File Name"
Disabled
Immediate
@bind-Value="@fileName"
@bind-Text="@fileName"
AutoGrow />
<MudFileUpload T="IBrowserFile"
FilesChanged="AddFile"
Required
Disabled="@(!string.IsNullOrWhiteSpace(fileName))"
RequiredError="You must select a file">
<ActivatorContent>
<MudButton Variant="Variant.Filled"
Color="Color.Tertiary"
style="margin: auto;"
StartIcon="@Icons.Material.Filled.AttachFile">
@if (addFileInProcess) {
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
<MudText>Processing</MudText>
} else {
<MudText>Add File</MudText>
}
</MudButton>
</ActivatorContent>
</MudFileUpload>
</MudForm>
</MudPaper>
</DialogContent>
<DialogActions>
<MudButton Variant="Variant.Filled"
Color="Color.Tertiary"
Class="m1-auto"
OnClick=Submit>
@if (processing) {
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
<MudText>Processing</MudText>
}
else {
<MudText>Submit</MudText>
}
</MudButton>
<MudButton Variant="Variant.Filled"
Class="grey text-black m1-auto"
OnClick=Cancel>
Cancel
</MudButton>
</DialogActions>
</MudDialog>
@code {
[CascadingParameter] MudDialogInstance MudDialog { get; set; }
[Parameter]
public int planNumber { get; set; } = 0;
[Parameter]
public int step { get; set; } = 0;
private string[] errors = { };
private string fileName = "";
private IBrowserFile? file = null;
private bool addFileInProcess = false;
private bool processing = false;
protected override async Task OnParametersSetAsync() {
if (planNumber <= 0) {
snackbar.Add($"{planNumber} is not a valid PCRB plan#", Severity.Error);
MudDialog.Close(DialogResult.Cancel());
}
if (step <= 0) {
snackbar.Add($"{step} is not a valid PCRB stage#", Severity.Error);
MudDialog.Close(DialogResult.Cancel());
}
if (authStateProvider.CurrentUser is null) {
await authStateProvider.Logout();
navigationManager.NavigateTo("login");
}
}
private void AddFile(IBrowserFile newFile) {
addFileInProcess = true;
file = newFile;
fileName = newFile.Name;
addFileInProcess = false;
}
private async Task Submit() {
processing = true;
try {
if (authStateProvider.CurrentUser is null) {
await authStateProvider.Logout();
navigationManager.NavigateTo("login");
return;
}
if (string.IsNullOrWhiteSpace(fileName))
throw new Exception("file name missing");
if (file is null)
throw new Exception("file is missing");
PCRBAttachment attachment = new() {
Step = step,
UploadDateTime = DateTime.Now,
UploadedByID = authStateProvider.CurrentUser.UserID,
PlanNumber = planNumber,
File = file,
FileName = fileName
};
await pcrbService.UploadAttachment(attachment);
processing = false;
MudDialog.Close(DialogResult.Ok(attachment));
} catch (Exception ex) {
snackbar.Add($"Unable to save document, because {ex.Message}", Severity.Error);
processing = false;
}
}
private void Cancel() {
MudDialog.Close(DialogResult.Cancel());
}
}

View File

@ -7,6 +7,7 @@
@inject NavigationManager navigationManager
@inject ISnackbar snackbar
@inject IMRBService mrbService
@inject IPCRBService pcrbService
@inject IECNService ecnService
@inject ICAService caService
@inject IJSRuntime jsRuntime
@ -88,6 +89,7 @@
<MudSpacer />
<MudTextField @bind-Value="mrbSearchString"
Placeholder="Search"
Immediate
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Search"
IconSize="Size.Medium"
@ -147,21 +149,100 @@
</MudOverlay>
}
</MudTabPanel>
<MudTabPanel Text="My PCRBs" Style="min-width:50%; text-align: center;">
@if (stateProvider.CurrentUser is not null && myPCRBs is not null && !myPcrbsProcessing) {
<MudPaper Outlined="true"
Class="p-2 m-2 d-flex flex-column justify-center">
<MudText Typo="Typo.h4" Align="Align.Center">My PCRBs</MudText>
<MudDivider DividerType="DividerType.Middle" Class="my-2" />
<MudTable Items="@myPCRBs"
Class="m-2"
Striped
SortLabel="Sort by"
Filter="new Func<PCRB, bool>(FilterFuncForPCRBTable)">
<ToolBarContent>
<MudSpacer />
<MudTextField @bind-Value="pcrbSearchString"
Placeholder="Search"
Adornment="Adornment.Start"
Immediate
AdornmentIcon="@Icons.Material.Filled.Search"
IconSize="Size.Medium"
Class="mt-0" />
</ToolBarContent>
<HeaderContent>
<MudTh>
<MudTableSortLabel InitialDirection="SortDirection.Descending" SortBy="new Func<PCRB,object>(x=>x.PlanNumber)">
PCRB#
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<PCRB,object>(x=>x.Title)">
Title
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<PCRB,object>(x=>x.CurrentStep)">
Current Step
</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>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<PCRB,object>(x=>x.ClosedDate)">
Completed Date
</MudTableSortLabel>
</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="PCRB#">
<MudLink OnClick="@(() => GoTo($"pcrb/{context.PlanNumber}"))">@context.PlanNumber</MudLink>
</MudTd>
<MudTd DataLabel="Title">@context.Title</MudTd>
<MudTd DataLabel="Current Step">@(GetCurrentPCRBStep(context.CurrentStep))</MudTd>
<MudTd DataLabel="Submitted Date">@DateTimeUtilities.GetDateAsStringMinDefault(context.InsertTimeStamp)</MudTd>
<MudTd DataLabel="Last Updated">@DateTimeUtilities.GetDateAsStringMinDefault(context.LastUpdateDate)</MudTd>
<MudTd DataLabel="Completed Date">@DateTimeUtilities.GetDateAsStringMaxDefault(context.ClosedDate)</MudTd>
</RowTemplate>
<PagerContent>
<MudTablePager />
</PagerContent>
</MudTable>
</MudPaper>
} else {
<MudOverlay Visible DarkBackground="true" AutoClose="false">
<MudText Align="Align.Center" Typo="Typo.h3">Processing</MudText>
<MudProgressCircular Color="Color.Info" Size="Size.Large" Indeterminate="true" />
</MudOverlay>
}
</MudTabPanel>
</MudTabs>
</MudPaper>
@code {
private IEnumerable<Approval> approvalList = new List<Approval>();
private IEnumerable<MRB> myMRBs = new List<MRB>();
private IEnumerable<PCRB> myPCRBs = new List<PCRB>();
private IEnumerable<int> ecnNumbers = new HashSet<int>();
private IEnumerable<int> caNumbers = new HashSet<int>();
private IEnumerable<int> mrbNumbers = new HashSet<int>();
private IEnumerable<int> pcrbNumbers = new HashSet<int>();
private bool myApprovalsProcessing = false;
private bool myMrbsProcessing = false;
private bool myPcrbsProcessing = false;
private string mrbSearchString = "";
private string pcrbSearchString = "";
protected async override Task OnParametersSetAsync() {
try {
@ -178,9 +259,17 @@
.ToList()
.OrderByDescending(x => x.SubmittedDate);
myMrbsProcessing = false;
myPcrbsProcessing = true;
myPCRBs = (await pcrbService.GetAllPCRBs(false)).Where(p => p.OwnerID == stateProvider.CurrentUser.UserID)
.ToList()
.OrderByDescending(p => p.InsertTimeStamp);
myPcrbsProcessing = false;
}
} catch (Exception ex) {
myApprovalsProcessing = false;
myMrbsProcessing = false;
myPcrbsProcessing = false;
snackbar.Add($"Unable to load the dashboard, because {ex.Message}", Severity.Error);
}
}
@ -191,12 +280,15 @@
bool isEcn = false;
bool isCa = false;
bool isMrb = false;
bool isPcrb = false;
if (ecnNumbers.Contains(issueId))
isEcn = true;
if (caNumbers.Contains(issueId))
isCa = true;
if (mrbNumbers.Contains(issueId))
isMrb = true;
if (pcrbNumbers.Contains(issueId))
isPcrb = true;
if (!isEcn && !isCa && !isMrb) {
Task<bool> isEcnTask = ecnService.ECNNumberIsValid(issueId);
@ -208,6 +300,9 @@
Task<bool> isMrbTask = mrbService.NumberIsValid(issueId);
tasks.Add(isMrbTask);
Task<bool> isPcrbTask = pcrbService.IdIsValid(issueId);
tasks.Add(isPcrbTask);
await Task.WhenAll(tasks);
if (isEcnTask.Result) {
@ -216,12 +311,15 @@
isCa = true;
} else if (isMrbTask.Result) {
isMrb = true;
} else if (isPcrbTask.Result) {
isPcrb = true;
}
}
if (isEcn) await GoToExternal($"{Configuration["OldFabApprovalUrl"]}/ECN/Edit?IssueID={issueId}", "");
if (isCa) await GoToExternal($"{Configuration["OldFabApprovalUrl"]}/CorrectiveAction/Edit?IssueID={issueId}", "");
if (isMrb) GoTo($"mrb/{issueId}");
if (isPcrb) GoTo($"pcrb/{issueId}");
}
private void GoTo(string page) {
@ -245,4 +343,21 @@
return true;
return false;
}
private bool FilterFuncForPCRBTable(PCRB pcrb) => PCRBFilterFunc(pcrb, pcrbSearchString);
private bool PCRBFilterFunc(PCRB pcrb, string searchString) {
if (string.IsNullOrWhiteSpace(searchString))
return true;
if (pcrb.Title.ToLower().Contains(searchString.Trim().ToLower()))
return true;
if (pcrb.PlanNumber.ToString().Contains(searchString.Trim()))
return true;
return false;
}
private string GetCurrentPCRBStep(int step) {
if (step < 0 || step > (PCRB.Stages.Length - 1)) return string.Empty;
else return PCRB.Stages[step];
}
}

File diff suppressed because it is too large Load Diff

View File

@ -30,7 +30,7 @@
<HeaderContent>
<MudTh>
<MudTableSortLabel InitialDirection="SortDirection.Descending" SortBy="new Func<PCRB,object>(x=>x.PlanNumber)">
Plan#
Change#
</MudTableSortLabel>
</MudTh>
<MudTh>
@ -43,6 +43,7 @@
Owner
</MudTableSortLabel>
</MudTh>
<MudTh>Stage</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<PCRB,object>(x=>x.InsertTimeStamp)">
Submitted Date
@ -65,6 +66,7 @@
</MudTd>
<MudTd DataLabel="Title">@context.Title</MudTd>
<MudTd DataLabel="Owner">@context.OwnerName</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>
@ -116,4 +118,9 @@
cache.Set("redirectUrl", page);
navigationManager.NavigateTo(page);
}
private string GetStageName(int step) {
if (step >= PCRB.Stages.Length || step < 0) return "";
return PCRB.Stages[step];
}
}

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,9 @@ WebAssemblyHostBuilder builder = WebAssemblyHostBuilder.CreateDefault(args);
string _apiBaseUrl = builder.Configuration["FabApprovalApiBaseUrl"] ??
throw new NullReferenceException("FabApprovalApiBaseUrl not found in config");
string _oldSiteUrl = builder.Configuration["OldFabApprovalUrl"] ??
throw new NullReferenceException("OldFabApprovalUrl not found in config");
builder.Services.AddTransient<ApiHttpClientHandler>();
builder.Services
@ -30,6 +33,12 @@ builder.Services
})
.AddHttpMessageHandler<ApiHttpClientHandler>();
builder.Services
.AddHttpClient("OldSite", client => {
client.BaseAddress = new Uri(_oldSiteUrl);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*"));
});
builder.Services.AddMemoryCache();
builder.Services.AddMudServices(config => {

View File

@ -289,7 +289,7 @@ public class ApprovalService : IApprovalService {
if (members is null || members.Count() <= 0)
throw new Exception($"unable to find group members for sub role {subRoleId}");
_cache.Set($"approvalMembers{subRoleId}", members, DateTimeOffset.Now.AddMinutes(15));
_cache.Set($"approvalMembers{subRoleId}", members, DateTimeOffset.Now.AddMinutes(2));
} else {
throw new Exception($"Unable to get group members, because {responseMessage.ReasonPhrase}");
}

View File

@ -165,9 +165,7 @@ public class AuthenticationService : IAuthenticationService {
await _localStorageService.AddItem("MesaFabApprovalUserId", loginId);
}
public async Task SetCurrentUser(User user) {
if (user is null) throw new ArgumentNullException("User cannot be null");
public async Task SetCurrentUser(User? user) {
_cache.Set<User>("MesaFabApprovalCurrentUser", user);
await _localStorageService.AddItem<User>("MesaFabApprovalCurrentUser", user);
}
@ -182,8 +180,10 @@ public class AuthenticationService : IAuthenticationService {
public async Task<User> GetCurrentUser() {
User? currentUser = null;
currentUser = _cache.Get<User>("MesaFabApprovalCurrentUser") ??
await _localStorageService.GetItem<User>("MesaFabApprovalCurrentUser");
currentUser = _cache.Get<User>("MesaFabApprovalCurrentUser");
if (currentUser is null)
currentUser = await _localStorageService.GetItem<User>("MesaFabApprovalCurrentUser");
return currentUser;
}

View File

@ -46,6 +46,16 @@ public class MesaFabApprovalAuthStateProvider : AuthenticationStateProvider, IDi
CurrentUser = await _authService.GetCurrentUser();
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(principal)));
if (CurrentUser is null) {
string loginId = _userService.GetLoginIdFromClaimsPrincipal(principal);
User? user = await _userService.GetUserByLoginId(loginId);
await _authService.SetCurrentUser(user);
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(principal)));
}
}
public async Task LoginAsync(string loginId, string password) {

View File

@ -1,8 +1,11 @@
using System.Text;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using MesaFabApproval.Shared.Models;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Caching.Memory;
using MudBlazor;
@ -11,28 +14,52 @@ namespace MesaFabApproval.Client.Services;
public interface IPCRBService {
Task<string> IdIsValid(string id);
Task<bool> IdIsValid(int id);
Task<IEnumerable<PCRB>> GetAllPCRBs(bool bypassCache);
Task CreateNewPCRB(PCRB pcrb);
Task<PCRB> GetPCRBByPlanNumber(int planNumber, bool bypassCache);
Task<PCRB> GetPCRBByTitle(string title, bool bypassCache);
Task UpdatePCRB(PCRB pcrb);
Task DeletePCRB(int planNumber);
Task UploadAttachment(PCRBAttachment attachment);
Task<IEnumerable<PCRBAttachment>> GetAttachmentsByPlanNumber(int planNumber, bool bypassCache);
Task UpdateAttachment(PCRBAttachment attachment);
Task DeleteAttachment(PCRBAttachment attachment);
Task CreateNewActionItem(PCRBActionItem actionItem);
Task UpdateActionItem(PCRBActionItem actionItem);
Task DeleteActionItem(int id);
Task<IEnumerable<PCRBActionItem>> GetActionItemsForPlanNumber(int planNumber, bool bypassCache);
Task CreateNewAttendee(PCRBAttendee attendee);
Task UpdateAttendee(PCRBAttendee attendee);
Task DeleteAttendee(int id);
Task<IEnumerable<PCRBAttendee>> GetAttendeesByPlanNumber(int planNumber, bool bypassCache);
Task CreatePCR3Document(PCR3Document document);
Task UpdatePCR3Document(PCR3Document document);
Task<IEnumerable<PCR3Document>> GetPCR3DocumentsForPlanNumber(int planNumber, bool bypassCache);
Task NotifyNewApprovals(PCRB pcrb);
Task NotifyApprovers(PCRBNotification notification);
Task NotifyOriginator(PCRBNotification notification);
Task NotifyResponsiblePerson(PCRBActionItemNotification notification);
}
public class PCRBService : IPCRBService {
private readonly IMemoryCache _cache;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ISnackbar _snackbar;
private readonly IUserService _userService;
public PCRBService(IMemoryCache cache, IHttpClientFactory httpClientFactory, ISnackbar snackbar) {
public PCRBService(IMemoryCache cache,
IHttpClientFactory httpClientFactory,
ISnackbar snackbar,
IUserService userService) {
_cache = cache ?? throw new ArgumentNullException("IMemoryCache not injected");
_httpClientFactory = httpClientFactory ?? throw new ArgumentNullException("IHttpClientFactory not injected");
_snackbar = snackbar ?? throw new ArgumentNullException("ISnackbar not injected");
_userService = userService ?? throw new ArgumentNullException("IUserService not injected");
}
public async Task<string> IdIsValid(string id) {
bool isMatch = true;
if (string.IsNullOrWhiteSpace(id)) isMatch = false;
try {
@ -63,6 +90,37 @@ public class PCRBService : IPCRBService {
return null;
}
public async Task<bool> IdIsValid(int id) {
bool isMatch = true;
if (id <= 0) isMatch = false;
try {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Get, $"pcrb/getByPlanNumber?planNumber={id}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
PCRB? pcrb = JsonSerializer.Deserialize<PCRB>(responseContent, jsonSerializerOptions);
if (pcrb is null) isMatch = false;
} else {
isMatch = false;
}
} catch (Exception) {
isMatch = false;
}
return isMatch;
}
public async Task<IEnumerable<PCRB>> GetAllPCRBs(bool bypassCache) {
try {
IEnumerable<PCRB>? allPCRBs = null;
@ -219,7 +277,7 @@ public class PCRBService : IPCRBService {
}
public async Task UpdatePCRB(PCRB pcrb) {
if (pcrb is null) throw new ArgumentNullException("MRB cannot be null");
if (pcrb is null) throw new ArgumentNullException("PCRB cannot be null");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
@ -251,7 +309,7 @@ public class PCRBService : IPCRBService {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Delete, $"pcrb/delete?planNumber={planNumber}");
HttpRequestMessage requestMessage = new(HttpMethod.Delete, $"pcrb?planNumber={planNumber}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
@ -260,4 +318,450 @@ public class PCRBService : IPCRBService {
IEnumerable<PCRB> allPCRBs = await GetAllPCRBs(true);
_cache.Set("allPCRBs", allPCRBs);
}
public async Task UploadAttachment(PCRBAttachment attachment) {
if (attachment is null) throw new ArgumentNullException("attachment cannot be null");
if (attachment.File is null) throw new ArgumentNullException("file cannot be null");
if (attachment.File.Size <= 0) throw new ArgumentException("file size must be greater than zero");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Post, $"pcrb/attachment");
using MultipartFormDataContent content = new MultipartFormDataContent();
try {
long maxFileSize = 1024L * 1024L * 1024L * 2L;
StreamContent fileContent = new StreamContent(attachment.File.OpenReadStream(maxFileSize));
FileExtensionContentTypeProvider contentTypeProvider = new FileExtensionContentTypeProvider();
const string defaultContentType = "application/octet-stream";
if (!contentTypeProvider.TryGetContentType(attachment.File.Name, out string? contentType)) {
contentType = defaultContentType;
}
fileContent.Headers.ContentType = new MediaTypeHeaderValue(contentType);
content.Add(content: fileContent, name: "\"file\"", fileName: attachment.File.Name);
} catch (Exception ex) {
_snackbar.Add(ex.Message);
}
content.Add(new StringContent(attachment.PlanNumber.ToString()), "PlanNumber");
content.Add(new StringContent(attachment.FileName), "FileName");
content.Add(new StringContent(attachment.UploadedByID.ToString()), "UploadedByID");
content.Add(new StringContent(attachment.Title), "Title");
content.Add(new StringContent(attachment.UploadDateTime.ToString("yyyy-MM-dd HH:mm:ss")), "UploadDateTime");
content.Add(new StringContent(attachment.Step.ToString()), "Step");
requestMessage.Content = content;
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode)
throw new Exception(responseMessage.ReasonPhrase);
await GetAttachmentsByPlanNumber(attachment.PlanNumber, true);
}
public async Task<IEnumerable<PCRBAttachment>> GetAttachmentsByPlanNumber(int planNumber, bool bypassCache) {
if (planNumber <= 0) throw new ArgumentException($"{planNumber} is not a valid PCRB Plan#");
IEnumerable<PCRBAttachment>? attachments = null;
if (!bypassCache)
attachments = _cache.Get<IEnumerable<PCRBAttachment>>($"pcrbAttachments{planNumber}");
if (attachments is null) {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Get, $"pcrb/attachments?planNumber={planNumber}&bypassCache={bypassCache}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
attachments = JsonSerializer.Deserialize<IEnumerable<PCRBAttachment>>(responseContent, jsonSerializerOptions) ??
new List<PCRBAttachment>();
if (attachments.Count() > 0) {
foreach (PCRBAttachment attachment in attachments) {
attachment.UploadedBy = await _userService.GetUserByUserId(attachment.UploadedByID);
}
_cache.Set($"pcrbAttachments{planNumber}", attachments, DateTimeOffset.Now.AddMinutes(5));
}
} else {
throw new Exception(responseMessage.ReasonPhrase);
}
}
return attachments;
}
public async Task UpdateAttachment(PCRBAttachment attachment) {
if (attachment is null) throw new ArgumentNullException("attachment cannot be null");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Put, $"pcrb/attachment");
requestMessage.Content = new StringContent(JsonSerializer.Serialize(attachment),
Encoding.UTF8,
"application/json");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode)
throw new Exception(responseMessage.ReasonPhrase);
}
public async Task DeleteAttachment(PCRBAttachment attachment) {
if (attachment is null) throw new ArgumentNullException("attachment cannot be null");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Delete, $"pcrb/attachment");
requestMessage.Content = new StringContent(JsonSerializer.Serialize(attachment),
Encoding.UTF8,
"application/json");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode)
throw new Exception(responseMessage.ReasonPhrase);
}
public async Task CreateNewActionItem(PCRBActionItem actionItem) {
if (actionItem is null) throw new ArgumentNullException("action item cannot be null");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Post, $"pcrb/actionItem");
requestMessage.Content = new StringContent(JsonSerializer.Serialize(actionItem),
Encoding.UTF8,
"application/json");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode)
throw new Exception(responseMessage.ReasonPhrase);
await GetActionItemsForPlanNumber(actionItem.PlanNumber, true);
}
public async Task UpdateActionItem(PCRBActionItem actionItem) {
if (actionItem is null) throw new ArgumentNullException("action item cannot be null");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Put, $"pcrb/actionItem");
requestMessage.Content = new StringContent(JsonSerializer.Serialize(actionItem),
Encoding.UTF8,
"application/json");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode)
throw new Exception(responseMessage.ReasonPhrase);
}
public async Task DeleteActionItem(int id) {
if (id <= 0) throw new ArgumentException($"{id} is not a valid PCRB action item ID");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Delete, $"pcrb/actionItem?id={id}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode) throw new Exception(responseMessage.ReasonPhrase);
}
public async Task<IEnumerable<PCRBActionItem>> GetActionItemsForPlanNumber(int planNumber, bool bypassCache) {
if (planNumber <= 0) throw new ArgumentException($"{planNumber} is not a valid PCRB Plan#");
IEnumerable<PCRBActionItem>? actionItems = null;
if (!bypassCache)
actionItems = _cache.Get<IEnumerable<PCRBActionItem>>($"pcrbActionItems{planNumber}");
if (actionItems is null) {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Get, $"pcrb/actionItems?planNumber={planNumber}&bypassCache={bypassCache}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
actionItems = JsonSerializer.Deserialize<IEnumerable<PCRBActionItem>>(responseContent, jsonSerializerOptions) ??
new List<PCRBActionItem>();
if (actionItems.Count() > 0) {
foreach (PCRBActionItem actionItem in actionItems) {
actionItem.UploadedBy = await _userService.GetUserByUserId(actionItem.UploadedByID);
if (actionItem.ResponsiblePersonID > 0)
actionItem.ResponsiblePerson = await _userService.GetUserByUserId(actionItem.ResponsiblePersonID);
if (actionItem.ClosedByID > 0)
actionItem.ClosedBy = await _userService.GetUserByUserId(actionItem.ClosedByID);
}
_cache.Set($"pcrbActionItems{planNumber}", actionItems, DateTimeOffset.Now.AddMinutes(5));
}
} else {
throw new Exception(responseMessage.ReasonPhrase);
}
}
return actionItems;
}
public async Task CreateNewAttendee(PCRBAttendee attendee) {
if (attendee is null) throw new ArgumentNullException("attendee cannot be null");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Post, $"pcrb/attendee");
requestMessage.Content = new StringContent(JsonSerializer.Serialize(attendee),
Encoding.UTF8,
"application/json");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode)
throw new Exception(responseMessage.ReasonPhrase);
await GetAttendeesByPlanNumber(attendee.PlanNumber, true);
}
public async Task UpdateAttendee(PCRBAttendee attendee) {
if (attendee is null) throw new ArgumentNullException("attendee cannot be null");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Put, $"pcrb/attendee");
requestMessage.Content = new StringContent(JsonSerializer.Serialize(attendee),
Encoding.UTF8,
"application/json");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode)
throw new Exception(responseMessage.ReasonPhrase);
}
public async Task DeleteAttendee(int id) {
if (id <= 0) throw new ArgumentException($"{id} is not a valid PCRB attendee ID");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Delete, $"pcrb/attendee?id={id}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode) throw new Exception(responseMessage.ReasonPhrase);
}
public async Task<IEnumerable<PCRBAttendee>> GetAttendeesByPlanNumber(int planNumber, bool bypassCache) {
if (planNumber <= 0) throw new ArgumentException($"{planNumber} is not a valid PCRB Plan#");
IEnumerable<PCRBAttendee>? attendees = null;
if (!bypassCache)
attendees = _cache.Get<IEnumerable<PCRBAttendee>>($"pcrbAttendees{planNumber}");
if (attendees is null) {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Get, $"pcrb/attendees?planNumber={planNumber}&bypassCache={bypassCache}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
attendees = JsonSerializer.Deserialize<IEnumerable<PCRBAttendee>>(responseContent, jsonSerializerOptions) ??
new List<PCRBAttendee>();
if (attendees.Count() > 0) {
foreach (PCRBAttendee attendee in attendees)
attendee.Attendee = await _userService.GetUserByUserId(attendee.AttendeeID);
_cache.Set($"pcrbAttendees{planNumber}", attendees, DateTimeOffset.Now.AddMinutes(5));
}
} else {
throw new Exception(responseMessage.ReasonPhrase);
}
}
return attendees;
}
public async Task CreatePCR3Document(PCR3Document document) {
if (document is null) throw new ArgumentNullException("document cannot be null");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Post, $"pcrb/pcr3Document");
requestMessage.Content = new StringContent(JsonSerializer.Serialize(document),
Encoding.UTF8,
"application/json");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode)
throw new Exception(responseMessage.ReasonPhrase);
await GetPCR3DocumentsForPlanNumber(document.PlanNumber, true);
}
public async Task UpdatePCR3Document(PCR3Document document) {
if (document is null) throw new ArgumentNullException("document cannot be null");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Put, $"pcrb/pcr3Document");
requestMessage.Content = new StringContent(JsonSerializer.Serialize(document),
Encoding.UTF8,
"application/json");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode)
throw new Exception(responseMessage.ReasonPhrase);
}
public async Task<IEnumerable<PCR3Document>> GetPCR3DocumentsForPlanNumber(int planNumber, bool bypassCache) {
if (planNumber <= 0) throw new ArgumentException($"{planNumber} is not a valid PCRB Plan#");
IEnumerable<PCR3Document>? documents = null;
if (!bypassCache)
documents = _cache.Get<IEnumerable<PCR3Document>>($"pcr3Documents{planNumber}");
if (documents is null) {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Get, $"pcrb/pcr3Documents?planNumber={planNumber}&bypassCache={bypassCache}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
documents = JsonSerializer.Deserialize<IEnumerable<PCR3Document>>(responseContent, jsonSerializerOptions) ??
new List<PCR3Document>();
if (documents.Count() > 0) {
foreach (PCR3Document document in documents) {
if (document.CompletedByID > 0)
document.CompletedBy = await _userService.GetUserByUserId(document.CompletedByID);
}
_cache.Set($"pcr3Documents{planNumber}", documents, DateTimeOffset.Now.AddMinutes(5));
}
} else {
throw new Exception(responseMessage.ReasonPhrase);
}
}
return documents;
}
public async Task NotifyNewApprovals(PCRB pcrb) {
if (pcrb is null) throw new ArgumentNullException("PCRB cannot be null");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Post, $"pcrb/notify/new-approvals");
requestMessage.Content = new StringContent(JsonSerializer.Serialize(pcrb),
Encoding.UTF8,
"application/json");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode)
throw new Exception($"Unable to notify new PCRB approvers, 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");
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/approvers");
requestMessage.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 approvers, because {responseMessage.ReasonPhrase}");
}
public async Task NotifyOriginator(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 (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/originator");
requestMessage.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 originator, because {responseMessage.ReasonPhrase}");
}
public async Task NotifyResponsiblePerson(PCRBActionItemNotification notification) {
if (notification is null) throw new ArgumentNullException("notification 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/responsiblePerson");
requestMessage.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 responsible person, because {responseMessage.ReasonPhrase}");
}
}