Skip to content

Commit f75f6a7

Browse files
Enables QA to set sticky location with justification (#933)
Allows QA officers to set sticky locations for candidates, requiring justification and call reference for audit purposes. Adds a dialog for setting the sticky location with input fields for justification and call reference, and displays the call reference in case notes. Updates authorization policies to allow QA access. Related to CFODEV-1532
1 parent 4ea3020 commit f75f6a7

5 files changed

Lines changed: 130 additions & 66 deletions

File tree

src/Application/Features/Candidates/Commands/SetCandidateStickyLocation.cs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,47 @@
11
using Cfo.Cats.Application.Common.Security;
22
using Cfo.Cats.Application.Common.Validators;
33
using Cfo.Cats.Application.SecurityConstants;
4+
using Cfo.Cats.Domain.ValueObjects;
45

56
namespace Cfo.Cats.Application.Features.Candidates.Commands;
67

78
public static class SetCandidateStickyLocation
89
{
9-
[RequestAuthorize(Policy = SecurityPolicies.SeniorInternal)]
10+
[RequestAuthorize(Policy = SecurityPolicies.Qa1)]
1011
public class Command : IRequest<Result>
1112
{
1213
public string? ParticipantId { get; set; }
1314
public string? Region { get; set; }
15+
16+
[Description("Justification")]
17+
public string? Justification { get;set; }
18+
19+
[Description("Call Reference")]
20+
public string? CallReference { get; set; }
21+
}
22+
23+
public class Handler(ICandidateService candidateService, IUnitOfWork unitOfWork) : IRequestHandler<Command, Result>
24+
{
25+
public async Task<Result> Handle(Command request, CancellationToken cancellationToken)
26+
{
27+
var participant = unitOfWork.DbContext.Participants.FirstOrDefault(x => x.Id == request.ParticipantId!);
28+
29+
if(participant is not null)
30+
{
31+
var callResult = await candidateService.SetStickyLocation(request.ParticipantId!, request.Region!);
32+
if(callResult is { Succeeded: true, Data: true })
33+
{
34+
participant.AddNote(new Note()
35+
{
36+
Message = request.Justification!,
37+
CallReference = request.CallReference!
38+
});
39+
return Result.Success();
40+
}
41+
return callResult;
42+
}
43+
return Result.Failure();
44+
}
1445
}
1546

1647
public class Validator : AbstractValidator<Command>
@@ -35,6 +66,16 @@ public Validator(IUnitOfWork unitOfWork)
3566
.Matches(ValidationConstants.AlphaNumeric)
3667
.WithMessage(string.Format(ValidationConstants.AlphaNumericMessage, "Region"));
3768

69+
RuleFor(x => x.Justification)
70+
.NotEmpty()
71+
.Matches(ValidationConstants.Notes)
72+
.WithMessage(string.Format(ValidationConstants.NotesMessage, "Justification"));
73+
74+
RuleFor(x => x.CallReference)
75+
.NotEmpty()
76+
.Matches(ValidationConstants.Numbers)
77+
.WithMessage(string.Format(ValidationConstants.NumbersMessage, "Call Reference"));
78+
3879
RuleSet(ValidationConstants.RuleSet.MediatR, () =>
3980
{
4081
RuleFor(x => x.ParticipantId)

src/Server.UI/Pages/Participants/Components/CaseNotes.razor

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,22 @@
5050
</MudText>
5151
<MudText Typo="Typo.body2">@note.CreatedByEmail</MudText>
5252
</CardHeaderContent>
53+
<CardHeaderActions>
54+
@if (note.CallReference is not null)
55+
{
56+
<MudChip T="string" Size="Size.Small" Color="Color.Primary">
57+
Call Reference @note.CallReference
58+
</MudChip>
59+
}
60+
</CardHeaderActions>
5361
</MudCardHeader>
5462
<MudCardContent>
5563
<MudText Typo="Typo.body1">
5664
<div class="word-wrap">
5765
@note.Message
5866
</div>
5967
</MudText>
60-
68+
6169
</MudCardContent>
6270
<MudCardActions>
6371
<MudTooltip Text="@note.Created.ToLocalTime().ToString("ddd, dd MMM yyyy 'at' HH:mm")">

src/Server.UI/Pages/Participants/Components/ParticipantActionMenu.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
protected override async Task OnInitializedAsync()
7373
{
7474
var state = await AuthState;
75-
_canForceUpdate = (await AuthService.AuthorizeAsync(state.User, SecurityPolicies.SeniorInternal)).Succeeded;
75+
_canForceUpdate = (await AuthService.AuthorizeAsync(state.User, SecurityPolicies.Qa1)).Succeeded;
7676
}
7777

7878
protected async Task Archive()
Lines changed: 16 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
@using Cfo.Cats.Application.Features.Candidates.Commands
22

3-
4-
@inject ICandidateService CandidateService
3+
@inherits OwningComponentBase<IMediator>
54

65
@if (Model is not null)
76
{
@@ -23,6 +22,21 @@
2322
<MudSelectItem T="string" Value="@("CAT9")">South East</MudSelectItem>
2423
</MudSelect>
2524
</MudItem>
25+
<MudItem xs="12">
26+
<MudTextField @bind-Value="Model.CallReference"
27+
Label="@Model.GetMemberDescription(x => x.CallReference)"
28+
For="@(() => Model.CallReference)"
29+
Required="true">
30+
31+
</MudTextField>
32+
</MudItem>
33+
<MudItem xs="12">
34+
<MudTextField @bind-Value="Model.Justification"
35+
Label="@Model.GetMemberDescription(x => x.Justification)"
36+
For="@(() => Model.Justification)"
37+
Lines="5"
38+
Required="true"/>
39+
</MudItem>
2640
</MudGrid>
2741
</MudForm>
2842
</MudPaper>
@@ -34,64 +48,3 @@
3448
</MudDialog>
3549
}
3650

37-
@code {
38-
39-
private SetCandidateStickyLocation.Command? Model { get; set; }
40-
41-
private MudForm? _form;
42-
private bool _saving;
43-
44-
45-
[CascadingParameter]
46-
private IMudDialogInstance MudDialog { get; set; } = default!;
47-
48-
49-
[EditorRequired] [Parameter] public string ParticipantId { get; set; } = default!;
50-
51-
protected override void OnInitialized()
52-
{
53-
Model = new SetCandidateStickyLocation.Command()
54-
{
55-
ParticipantId = ParticipantId
56-
};
57-
}
58-
59-
private async Task Submit()
60-
{
61-
try
62-
{
63-
_saving = true;
64-
65-
await _form!.Validate();
66-
67-
if (_form!.IsValid == false)
68-
{
69-
Snackbar.Add("Failed to set sticky location", Severity.Error);
70-
return;
71-
}
72-
73-
var result = await CandidateService.SetStickyLocation(Model!.ParticipantId!, Model!.Region!); //await GetNewMediator().Send(Model!);
74-
if (result)
75-
{
76-
MudDialog.Close(DialogResult.Ok(true));
77-
Snackbar.Add(ConstantString.SaveSuccess, Severity.Info);
78-
}
79-
else
80-
{
81-
Snackbar.Add("Failed to set sticky location", Severity.Error);
82-
}
83-
}
84-
finally
85-
{
86-
_saving = false;
87-
}
88-
}
89-
90-
private void Cancel()
91-
{
92-
MudDialog.Cancel();
93-
}
94-
95-
96-
97-
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using Cfo.Cats.Application.Features.Candidates.Commands;
2+
using Cfo.Cats.Infrastructure.Constants;
3+
4+
namespace Cfo.Cats.Server.UI.Pages.Participants.Components;
5+
6+
public partial class SetStickyLocationDialog
7+
{
8+
9+
private SetCandidateStickyLocation.Command? Model { get; set; }
10+
11+
private MudForm? _form;
12+
private bool _saving;
13+
14+
[CascadingParameter]
15+
private IMudDialogInstance MudDialog { get; set; } = default!;
16+
17+
[EditorRequired][Parameter] public string ParticipantId { get; set; } = default!;
18+
19+
protected override void OnInitialized()
20+
{
21+
Model = new SetCandidateStickyLocation.Command()
22+
{
23+
ParticipantId = ParticipantId
24+
};
25+
}
26+
27+
private async Task Submit()
28+
{
29+
try
30+
{
31+
_saving = true;
32+
33+
await _form!.Validate();
34+
35+
if (_form!.IsValid == false)
36+
{
37+
Snackbar.Add("Failed to set sticky location", Severity.Error);
38+
return;
39+
}
40+
41+
var result = await Service.Send(Model!);
42+
if (result.Succeeded)
43+
{
44+
MudDialog.Close(DialogResult.Ok(true));
45+
Snackbar.Add(ConstantString.SaveSuccess, Severity.Info);
46+
}
47+
else
48+
{
49+
Snackbar.Add("Failed to set sticky location", Severity.Error);
50+
}
51+
}
52+
finally
53+
{
54+
_saving = false;
55+
}
56+
}
57+
58+
private void Cancel()
59+
{
60+
MudDialog.Cancel();
61+
}
62+
}

0 commit comments

Comments
 (0)