Skip to content

Commit a8f8226

Browse files
author
Jani Giannoudis
committed
payrun start: added support for async payrun job executions
payrun jobs page: enhanced status query dialogs updated version to 0.9.0-beta.14
1 parent 04e2767 commit a8f8226

16 files changed

Lines changed: 148 additions & 98 deletions

Core/PayrollEngine.WebApp.Core.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
</PropertyGroup>
1313

1414
<ItemGroup>
15-
<PackageReference Include="PayrollEngine.Client.Core" Version="0.9.0-beta.13" />
15+
<PackageReference Include="PayrollEngine.Client.Core" Version="0.9.0-beta.14" />
1616
<PackageReference Include="Microsoft.AspNetCore.Components" Version="10.0.1" />
1717
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
1818
<PackageReference Include="Microsoft.JSInterop" Version="10.0.1" />

Directory.Build.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
<PropertyGroup>
44
<TargetFramework>net10.0</TargetFramework>
5-
<Version>0.9.0-beta.13</Version>
5+
<Version>0.9.0-beta.14</Version>
66
<FileVersion>0.9.0</FileVersion>
7-
<InformationalVersion>0.9.0-beta.13</InformationalVersion>
7+
<InformationalVersion>0.9.0-beta.14</InformationalVersion>
88
<Authors>Jani Giannoudis</Authors>
99
<Company>Software Consulting Giannoudis</Company>
1010
<Copyright>© 2026 Software Consulting Giannoudis</Copyright>

Presentation/Component/DialogServiceExtensions.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ public async Task<bool> ShowMessageBoxAsync(string title, string message, string
3232
string noText = null, string cancelText = null,
3333
string icon = Icons.Material.Filled.Info,
3434
Color iconColor = Color.Info,
35-
Color submitColor = Color.Primary,
36-
Variant submitVariant = Variant.Outlined)
35+
Color submitColor = Color.Tertiary,
36+
Variant? submitVariant = null)
3737
{
3838
var options = new MessageBoxOptions
3939
{
@@ -42,6 +42,7 @@ public async Task<bool> ShowMessageBoxAsync(string title, string message, string
4242
NoText = noText,
4343
Message = message
4444
};
45+
submitVariant ??= Globals.ButtonVariant;
4546
var parameters = new DialogParameters
4647
{
4748
{ nameof(MessageBoxDialog.Options), options },
@@ -71,8 +72,8 @@ public async Task<bool> ShowMessageBoxAsync(string title, MarkupString message,
7172
string noText = null, string cancelText = null,
7273
string icon = Icons.Material.Filled.Info,
7374
Color iconColor = Color.Info,
74-
Color submitColor = Color.Primary,
75-
Variant submitVariant = Variant.Outlined)
75+
Color submitColor = Color.Tertiary,
76+
Variant? submitVariant = null)
7677
{
7778
var options = new MessageBoxOptions
7879
{
@@ -81,6 +82,7 @@ public async Task<bool> ShowMessageBoxAsync(string title, MarkupString message,
8182
NoText = noText,
8283
MarkupMessage = message
8384
};
85+
submitVariant ??= Globals.ButtonVariant;
8486
var parameters = new DialogParameters
8587
{
8688
{ nameof(MessageBoxDialog.Options), options },
@@ -108,7 +110,7 @@ await dialogService.ShowMessageBoxAsync(title, message,
108110
icon: Icons.Material.Filled.Delete,
109111
iconColor: Color.Error,
110112
submitColor: Color.Error,
111-
submitVariant: Variant.Filled);
113+
submitVariant: Globals.ButtonVariant);
112114

113115
/// <summary>
114116
/// Show delete message box with markup text
@@ -125,7 +127,7 @@ await dialogService.ShowMessageBoxAsync(title, message,
125127
icon: Icons.Material.Filled.Delete,
126128
iconColor: Color.Error,
127129
submitColor: Color.Error,
128-
submitVariant: Variant.Filled);
130+
submitVariant: Globals.ButtonVariant);
129131

130132
/// <summary>
131133
/// Show error message box

Presentation/Component/MessageBoxDialog.razor

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
<DialogContent>
33
<MudStack Row="true">
44
<MudIcon Icon="@(Icon ?? Icons.Material.Filled.Info)"
5-
Color="@IconColor"
6-
Style="font-size: 2rem;" />
5+
Color="@IconColor"
6+
Style="font-size: 2rem; align-self: center;" />
77
<MudText Style="align-self: center ">
88
@Options.Message
99
@Options.MarkupMessage
@@ -13,30 +13,40 @@
1313
<DialogActions>
1414
@if (Options.CancelText != null)
1515
{
16-
<MudButton Color="Color.Primary" OnClick="Cancel">@Options.CancelText</MudButton>
16+
<MudButton Variant="@Globals.ButtonVariant"
17+
OnClick="Cancel">
18+
@Options.CancelText
19+
</MudButton>
1720
}
1821
@if (Options.NoText != null)
1922
{
20-
<MudButton Color="Color.Primary" OnClick="Cancel">@Options.NoText</MudButton>
23+
<MudButton Variant="@Globals.ButtonVariant"
24+
OnClick="Cancel">
25+
@Options.NoText
26+
</MudButton>
2127
}
2228
@if (Options.YesText != null)
2329
{
24-
<MudButton Color="@SubmitColor" Variant="@SubmitVariant" OnClick="Submit">@Options.YesText</MudButton>
30+
<MudButton Color="@SubmitColor"
31+
Variant="@SubmitVariant"
32+
OnClick="Submit">
33+
@Options.YesText
34+
</MudButton>
2535
}
2636
</DialogActions>
2737
</MudDialog>
2838

2939
<style>
3040
.dialog {
31-
min-width: 24em;
41+
min-width: 24em;
3242
}
3343
3444
.title {
35-
background-color: lightgrey;
45+
background-color: lightgrey;
3646
}
3747
3848
.titleDark {
39-
background-color: darkslateblue;
49+
background-color: darkslateblue;
4050
}
4151
</style>
4252

@@ -46,8 +56,8 @@
4656
[Parameter] public MessageBoxOptions Options { get; set; }
4757
[Parameter] public string Icon { get; set; }
4858
[Parameter] public Color IconColor { get; set; } = Color.Info;
49-
[Parameter] public Color SubmitColor { get; set; } = Color.Primary;
50-
[Parameter] public Variant SubmitVariant { get; set; }
59+
[Parameter] public Color SubmitColor { get; set; } = Color.Tertiary;
60+
[Parameter] public Variant SubmitVariant { get; set; } = Globals.ButtonVariant;
5161

5262
[Inject]
5363
private IThemeService ThemeService { get; set; }

Presentation/PayrollEngine.WebApp.Presentation.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
<ItemGroup>
1010
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.1" />
11-
<PackageReference Include="PayrollEngine.Document" Version="0.9.0-beta.13" />
11+
<PackageReference Include="PayrollEngine.Document" Version="0.9.0-beta.14" />
1212
<PackageReference Include="MudBlazor" Version="8.15.0" />
1313
<PackageReference Include="NPOI" Version="2.7.5" />
1414
</ItemGroup>

Presentation/Payrun/PayrunStartDialog.razor

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
@using Employee = PayrollEngine.WebApp.ViewModel.Employee
99
@using Log = PayrollEngine.Log
1010

11-
<MudDialog Style="min-width: 30em;" TitleClass="mud-theme-primary pe-dialog-title">
11+
<MudDialog Style="min-width: 35em;" TitleClass="mud-theme-primary pe-dialog-title">
1212
<DialogContent>
1313
<MudStack Spacing="0">
1414
<MudSimpleTable>
@@ -78,8 +78,8 @@
7878
<div class="py-8">
7979
@if (Executing)
8080
{
81-
<MudText Class="pb-4">
82-
@Localizer.PayrunJob.JobExecuting
81+
<MudText Class="pb-4 px-4">
82+
@StatusMessage
8383
</MudText>
8484
<MudProgressLinear Color="Color.Info" Rounded="true"
8585
Class="mud-table-loading-progress"
@@ -88,7 +88,7 @@
8888
@if (Failed)
8989
{
9090
<MudText Class="pb-4" Color="Color.Error">
91-
@Localizer.PayrunJob.JobFailed
91+
@ErrorMessage
9292
</MudText>
9393
}
9494
@if (Completed)
@@ -114,6 +114,9 @@
114114
<MudButton Variant="@Globals.ButtonVariant" Color="Color.Info"
115115
StartIcon="@Icons.Material.Filled.NavigateNext"
116116
OnClick="CloseViewResults">@Localizer.PayrunResult.PayrunResults</MudButton>
117+
}
118+
@if (Completed || Failed || Executing)
119+
{
117120
<MudButton Variant="@Globals.ButtonVariant" Color="Color.Tertiary"
118121
OnClick="Close">@Localizer.Dialog.Close</MudButton>
119122
}
@@ -142,6 +145,9 @@
142145
private bool Completed { get; set; }
143146
private bool Failed { get; set; }
144147

148+
private string StatusMessage { get; set; }
149+
private string ErrorMessage { get; set; }
150+
145151
private void Cancel() => MudDialog.Cancel();
146152

147153
private void CloseViewResults()
@@ -192,36 +198,55 @@
192198

193199
// new payrun job
194200
var jobInvocation = new PayrunJobInvocation
195-
{
196-
PayrunId = PayrunId,
197-
UserId = User.Id,
198-
Name = Setup.JobName,
199-
Forecast = Setup.ForecastName,
200-
PeriodStart = jobPeriod,
201-
EvaluationDate = Setup.EvaluationDate,
202-
EmployeeIdentifiers = employeeIdentifiers,
203-
Attributes = Setup.Parameters.ToDictionary(p => p.Name, p => p.GetValueByValueType()),
204-
Reason = Setup.Reason,
201+
{
202+
PayrunId = PayrunId,
203+
UserId = User.Id,
204+
Name = Setup.JobName,
205+
Forecast = Setup.ForecastName,
206+
PeriodStart = jobPeriod,
207+
EvaluationDate = Setup.EvaluationDate,
208+
EmployeeIdentifiers = employeeIdentifiers,
209+
Attributes = Setup.Parameters.ToDictionary(p => p.Name, p => p.GetValueByValueType()),
210+
Reason = Setup.Reason,
205211
// automatic retro mode
206-
RetroPayMode = RetroPayMode.ValueChange
207-
};
212+
RetroPayMode = RetroPayMode.ValueChange
213+
};
208214

209215
// add payrun job
210-
var result = await PayrunJobService.StartJobAsync<PayrunJob>(new(Tenant.Id), jobInvocation);
211-
if (result == null)
216+
var job = await PayrunJobService.StartJobAsync<PayrunJob>(new(Tenant.Id), jobInvocation);
217+
if (job == null)
212218
{
213219
await UserNotification.ShowErrorMessageBoxAsync(Localizer, Localizer.PayrunJob.PayrunJob, Localizer.PayrunJob.JobFailed);
214220
}
221+
else
222+
{
223+
// wait for completed job
224+
while (job.JobEnd == null)
225+
{
226+
// status update
227+
var percent = job.TotalEmployeeCount > 0 && job.ProcessedEmployeeCount > 0 ?
228+
(job.ProcessedEmployeeCount * 100) / job.TotalEmployeeCount : 0;
229+
StatusMessage = Localizer.PayrunJob.JobProcessing(percent);
230+
StateHasChanged();
231+
232+
// delay for a second
233+
Task.Delay(1000).Wait();
234+
235+
// retry
236+
job = await PayrunJobService.GetAsync<PayrunJob>(new(Tenant.Id), job.Id);
237+
}
238+
}
215239

216240
// expected exit
217241
Executing = false;
218-
Completed = true;
242+
Completed = job?.JobEnd != null;
219243
StateHasChanged();
220244
}
221245
catch (Exception exception)
222246
{
223247
// failed
224-
Log.Error(exception, exception.GetBaseMessage());
248+
ErrorMessage = exception.GetBaseMessage();
249+
Log.Error(exception, ErrorMessage);
225250
Executing = false;
226251
Failed = true;
227252
StateHasChanged();

Presentation/Regulation/Component/ActionDialog.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@
274274
<div class="d-flex flex-grow-1 justify-end pl-8">
275275
<MudButton OnClick="ToggleActionsAsync"
276276
Variant="@Globals.ButtonVariant"
277-
StartIcon="@(ActionState is ActionWorkState.Hidden ? Icons.Material.Outlined.ExpandMore : ActionState is ActionWorkState.Loading ? Icons.Material.Outlined.Pending : Icons.Material.Outlined.ExpandLess)">
277+
StartIcon="@(ActionState is ActionWorkState.Hidden ? Icons.Material.Outlined.ExpandMore : ActionState is ActionWorkState.Loading ? Icons.Material.Outlined.HourglassBottom : Icons.Material.Outlined.ExpandLess)">
278278
@switch (ActionState)
279279
{
280280
case ActionWorkState.Hidden:

Server/Components/Pages/PayrunJobs.razor.cs

Lines changed: 29 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -192,11 +192,14 @@ private async Task ChangeLegalStatusAsync(PayrunJobStatus jobStatus)
192192
}
193193

194194
// confirmation
195+
var source = Localizer.Enum(LegalJob.JobStatus);
196+
var target = Localizer.Enum(jobStatus);
195197
if (!await DialogService.ShowMessageBoxAsync(
196-
Localizer.PayrunJob.PayrunJob,
197-
new MarkupString($"<br /><b>&#xbb;{jobStatus}&#xab;</b> {Localizer.PayrunJob.PayrunJob} {LegalJob.Name}?<br /><br />"),
198-
Localizer.Dialog.Ok,
199-
Localizer.Dialog.Cancel))
198+
title: Localizer.PayrunJob.PayrunJob,
199+
message: new MarkupString($"<br />{Localizer.PayrunJob.ChangeStatusQuery(source, target)}<br /><br />"),
200+
yesText: target,
201+
noText: Localizer.Dialog.Cancel,
202+
submitColor: jobStatus is PayrunJobStatus.Cancel or PayrunJobStatus.Abort ? Color.Error : Color.Tertiary))
200203
{
201204
return;
202205
}
@@ -581,7 +584,7 @@ await DialogService.ShowAsync<PayrunJobDialog>(
581584
/// <param name="setup">The payrun job setup</param>
582585
private async Task ResetJobSetupAsync(PayrunJobSetup setup)
583586
{
584-
setup.PeriodDate = Date.Today;
587+
await SetJobPeriodDateAsync(setup, Date.Today);
585588
setup.JobName = $"{Localizer.Payrun.Payrun} {setup.PeriodDate?.ToCompactString()}";
586589
setup.ForecastName = null;
587590
setup.SelectedEmployees = null;
@@ -603,7 +606,7 @@ private async Task ResetJobSetupAsync(PayrunJobSetup setup)
603606
/// <param name="setup">The jo setup</param>
604607
private async Task ApplyToSetupAsync(PayrunJob job, PayrunJobSetup setup)
605608
{
606-
setup.PeriodDate = job.PeriodStart;
609+
await SetJobPeriodDateAsync(setup, job.PeriodStart);
607610
setup.EvaluationDate = job.EvaluationDate;
608611
setup.JobName = job.Name;
609612
setup.Reason = job.CreatedReason;
@@ -861,46 +864,35 @@ private DateTime? LegalSetupPeriodDate
861864
{
862865
// ReSharper disable once UnusedMember.Local
863866
get => LegalSetup.PeriodDate;
864-
set
865-
{
866-
LegalSetup.PeriodDate = value;
867-
InvokeAsync(UpdateLegalSetupPeriodAsync);
868-
}
869-
}
870-
871-
private async Task UpdateLegalSetupPeriodAsync()
872-
{
873-
var period = await CalendarService.GetPeriodAsync(
874-
tenantId: Tenant.Id,
875-
cultureName: PageCulture.Name,
876-
calendarName: JobCalendar,
877-
periodMoment: LegalSetup.PeriodDate);
878-
LegalSetup.Period = period;
879-
StateHasChanged();
867+
set => InvokeAsync(() => SetJobPeriodDateAsync(LegalSetup, value));
880868
}
881869

882870
private DateTime? ForecastSetupPeriodDate
883871
{
884872
// ReSharper disable once UnusedMember.Local
885873
get => ForecastSetup.PeriodDate;
886-
set
887-
{
888-
ForecastSetup.PeriodDate = value;
889-
InvokeAsync(UpdateForecastSetupPeriodAsync);
890-
}
874+
set => InvokeAsync(() => SetJobPeriodDateAsync(ForecastSetup, value));
891875
}
892876

893-
private async Task UpdateForecastSetupPeriodAsync()
877+
private async Task SetJobPeriodDateAsync(PayrunJobSetup setup, DateTime? date)
894878
{
895-
var period = await CalendarService.GetPeriodAsync(
896-
tenantId: Tenant.Id,
897-
cultureName: PageCulture.Name,
898-
calendarName: JobCalendar,
899-
periodMoment: ForecastSetup.PeriodDate);
900-
ForecastSetup.Period = period;
901-
StateHasChanged();
879+
if (date == setup.PeriodDate)
880+
{
881+
return;
882+
}
883+
setup.PeriodDate = date;
884+
await UpdateJobPeriodAsync(setup);
902885
}
903886

887+
private async Task UpdateJobPeriodAsync(PayrunJobSetup setup) =>
888+
setup.Period = setup.PeriodDate != null ?
889+
await CalendarService.GetPeriodAsync(
890+
tenantId: Tenant.Id,
891+
cultureName: PageCulture.Name,
892+
calendarName: JobCalendar,
893+
periodMoment: setup.PeriodDate) :
894+
new();
895+
904896
#endregion
905897

906898
#region Users
@@ -1028,9 +1020,8 @@ protected override async Task OnPageAfterRenderAsync(bool firstRender)
10281020
if (firstRender && HasFeature(Feature.Forecasts))
10291021
{
10301022
await SetupForecastHistoryAsync();
1031-
await UpdateLegalSetupPeriodAsync();
1032-
await UpdateForecastSetupPeriodAsync();
1033-
1023+
await UpdateJobPeriodAsync(LegalSetup);
1024+
await UpdateJobPeriodAsync(ForecastSetup);
10341025
}
10351026
await base.OnPageAfterRenderAsync(firstRender);
10361027
}

0 commit comments

Comments
 (0)